All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.CheckConformance Maven / Gradle / Ivy

/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.Node;
import com.google.protobuf.Descriptors;
import com.google.protobuf.TextFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Provides a framework for checking code against a set of user configured conformance rules. The
 * rules are specified by the ConformanceConfig proto, which allows for both standard checks
 * (forbidden properties, variables, or dependencies) and allow for more complex checks using custom
 * rules than specify
 *
 * 

Conformance violations are both reported as compiler errors, and are also reported separately * to the {cI gue@link ErrorManager} */ @GwtIncompatible("com.google.protobuf") public final class CheckConformance implements Callback, CompilerPass { static final DiagnosticType CONFORMANCE_ERROR = DiagnosticType.error("JSC_CONFORMANCE_ERROR", "Violation: {0}{1}{2}"); static final DiagnosticType CONFORMANCE_VIOLATION = DiagnosticType.warning("JSC_CONFORMANCE_VIOLATION", "Violation: {0}{1}{2}"); static final DiagnosticType CONFORMANCE_POSSIBLE_VIOLATION = DiagnosticType.warning("JSC_CONFORMANCE_POSSIBLE_VIOLATION", "Possible violation: {0}{1}{2}"); static final DiagnosticType INVALID_REQUIREMENT_SPEC = DiagnosticType.error( "JSC_INVALID_REQUIREMENT_SPEC", "Invalid requirement. Reason: {0}\nRequirement spec:\n{1}"); private final AbstractCompiler compiler; private final ImmutableList rules; public static interface Rule { /** Perform conformance check */ void check(NodeTraversal t, Node n); } /** @param configs The rules to check. */ CheckConformance(AbstractCompiler compiler, ImmutableList configs) { this.compiler = compiler; // Initialize the map of functions to inspect for renaming candidates. this.rules = initRules(compiler, configs); } @Override public void process(Node externs, Node root) { if (!rules.isEmpty()) { NodeTraversal.traverseRoots(compiler, this, externs, root); } } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { // Don't inspect extern files return !n.isScript() || !t.getInput().getSourceFile().isExtern(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { for (int i = 0, len = rules.size(); i < len; i++) { Rule rule = rules.get(i); rule.check(t, n); } } /** Build the data structures need by this pass from the provided configurations. */ private static ImmutableList initRules( AbstractCompiler compiler, ImmutableList configs) { ImmutableList.Builder builder = ImmutableList.builder(); List requirements = mergeRequirements(compiler, configs); for (Requirement requirement : requirements) { Rule rule = initRule(compiler, requirement); if (rule != null) { builder.add(rule); } } return builder.build(); } private static final ImmutableSet EXTENDABLE_FIELDS = ImmutableSet.of( "config_file", "extends", "only_apply_to", "only_apply_to_regexp", "whitelist", "whitelist_regexp", "allowlist", "allowlist_regexp", "value"); /** * Gets requirements from all configs. Merges allowlists/whitelists of requirements with 'extends' * equal to 'rule_id' of other rule. */ static List mergeRequirements( AbstractCompiler compiler, List configs) { List builders = new ArrayList<>(); Map extendable = new HashMap<>(); for (ConformanceConfig config : configs) { for (Requirement requirement : config.getRequirementList()) { Requirement.Builder builder = requirement.toBuilder(); if (requirement.hasRuleId()) { if (requirement.getRuleId().isEmpty()) { reportInvalidRequirement(compiler, requirement, "empty rule_id"); continue; } if (extendable.containsKey(requirement.getRuleId())) { reportInvalidRequirement( compiler, requirement, "two requirements with the same rule_id: " + requirement.getRuleId()); continue; } extendable.put(requirement.getRuleId(), builder); } if (!requirement.hasExtends()) { builders.add(builder); } } } for (ConformanceConfig config : configs) { for (Requirement requirement : config.getRequirementList()) { if (requirement.hasExtends()) { Requirement.Builder existing = extendable.get(requirement.getExtends()); if (existing == null) { reportInvalidRequirement( compiler, requirement, "no requirement with rule_id: " + requirement.getExtends()); continue; } for (Descriptors.FieldDescriptor field : requirement.getAllFields().keySet()) { if (!EXTENDABLE_FIELDS.contains(field.getName())) { reportInvalidRequirement( compiler, requirement, "extending rules allow only " + EXTENDABLE_FIELDS); } } if (requirement.getValueCount() > 0 && !existing.getAllowExtendingValue()) { reportInvalidRequirement( compiler, requirement, "extending rule may not specify 'value' if base rule does not allow it"); } existing .addAllWhitelist(requirement.getWhitelistList()) .addAllWhitelistRegexp(requirement.getWhitelistRegexpList()) .addAllAllowlist(requirement.getAllowlistList()) .addAllAllowlistRegexp(requirement.getAllowlistRegexpList()) .addAllOnlyApplyTo(requirement.getOnlyApplyToList()) .addAllOnlyApplyToRegexp(requirement.getOnlyApplyToRegexpList()) .addAllWhitelistEntry(requirement.getWhitelistEntryList()) .addAllAllowlistEntry(requirement.getAllowlistEntryList()) .addAllValue(requirement.getValueList()) .addAllConfigFile(requirement.getConfigFileList()); } } } List requirements = new ArrayList<>(builders.size()); for (Requirement.Builder builder : builders) { removeDuplicates(builder); requirements.add(builder.build()); } return requirements; } private static void removeDuplicates(Requirement.Builder requirement) { final Set list1 = ImmutableSet.copyOf(requirement.getWhitelistList()); requirement.clearWhitelist().addAllWhitelist(list1); final Set allowlist = ImmutableSet.copyOf(requirement.getAllowlistList()); requirement.clearAllowlist().addAllAllowlist(allowlist); final Set list2 = ImmutableSet.copyOf(requirement.getWhitelistRegexpList()); requirement.clearWhitelistRegexp().addAllWhitelistRegexp(list2); final Set allowlistRegexp = ImmutableSet.copyOf(requirement.getAllowlistRegexpList()); requirement.clearAllowlistRegexp().addAllAllowlistRegexp(allowlistRegexp); final Set list3 = ImmutableSet.copyOf(requirement.getOnlyApplyToList()); requirement.clearOnlyApplyTo().addAllOnlyApplyTo(list3); final Set list4 = ImmutableSet.copyOf(requirement.getOnlyApplyToRegexpList()); requirement.clearOnlyApplyToRegexp().addAllOnlyApplyToRegexp(list4); } private static Rule initRule(AbstractCompiler compiler, Requirement requirement) { try { switch (requirement.getType()) { case CUSTOM: return new ConformanceRules.CustomRuleProxy(compiler, requirement); case NO_OP: return new ConformanceRules.NoOp(compiler, requirement); case BANNED_CODE_PATTERN: return new ConformanceRules.BannedCodePattern(compiler, requirement); case BANNED_DEPENDENCY: return new ConformanceRules.BannedDependency(compiler, requirement); case BANNED_DEPENDENCY_REGEX: return new ConformanceRules.BannedDependencyRegex(compiler, requirement); case BANNED_NAME: case BANNED_NAME_CALL: return new ConformanceRules.BannedName(compiler, requirement); case BANNED_PROPERTY: case BANNED_PROPERTY_READ: case BANNED_PROPERTY_WRITE: case BANNED_PROPERTY_NON_CONSTANT_WRITE: case BANNED_PROPERTY_CALL: return new ConformanceRules.BannedProperty(compiler, requirement); case RESTRICTED_NAME_CALL: return new ConformanceRules.RestrictedNameCall(compiler, requirement); case RESTRICTED_METHOD_CALL: return new ConformanceRules.RestrictedMethodCall(compiler, requirement); case RESTRICTED_PROPERTY_WRITE: return new ConformanceRules.RestrictedPropertyWrite(compiler, requirement); default: reportInvalidRequirement(compiler, requirement, "unknown requirement type"); return null; } } catch (InvalidRequirementSpec e) { reportInvalidRequirement(compiler, requirement, e.getMessage()); return null; } } public static class InvalidRequirementSpec extends Exception { InvalidRequirementSpec(String message) { super(message); } InvalidRequirementSpec(String message, Throwable cause) { super(message, cause); } } /** @param requirement */ private static void reportInvalidRequirement( AbstractCompiler compiler, Requirement requirement, String reason) { compiler.report( JSError.make(INVALID_REQUIREMENT_SPEC, reason, TextFormat.printToString(requirement))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy