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

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

/*
 * Copyright 2011 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.CharMatcher;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Process goog.tweak primitives. Checks that:
 * 
    *
  • parameters to goog.tweak.register* are literals of the correct type. *
  • the parameter to goog.tweak.get* is a string literal. *
  • parameters to goog.tweak.overrideDefaultValue are literals of the correct * type. *
  • tweak IDs passed to goog.tweak.get* and goog.tweak.overrideDefaultValue * correspond to registered tweaks. *
  • all calls to goog.tweak.register* and goog.tweak.overrideDefaultValue are * within the top-level context. *
  • each tweak is registered only once. *
  • calls to goog.tweak.overrideDefaultValue occur before the call to the * corresponding goog.tweak.register* function. *
* @author [email protected] (Andrew Grieve) */ class ProcessTweaks implements CompilerPass { private final AbstractCompiler compiler; private final boolean stripTweaks; private final SortedMap compilerDefaultValueOverrides; private static final CharMatcher ID_MATCHER = CharMatcher.inRange('a', 'z'). or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.anyOf("0123456789_.")); // Warnings and Errors. static final DiagnosticType UNKNOWN_TWEAK_WARNING = DiagnosticType.warning( "JSC_UNKNOWN_TWEAK_WARNING", "no tweak registered with ID {0}"); static final DiagnosticType TWEAK_MULTIPLY_REGISTERED_ERROR = DiagnosticType.error( "JSC_TWEAK_MULTIPLY_REGISTERED_ERROR", "Tweak {0} has already been registered."); static final DiagnosticType NON_LITERAL_TWEAK_ID_ERROR = DiagnosticType.error( "JSC_NON_LITERAL_TWEAK_ID_ERROR", "tweak ID must be a string literal"); static final DiagnosticType INVALID_TWEAK_DEFAULT_VALUE_WARNING = DiagnosticType.warning( "JSC_INVALID_TWEAK_DEFAULT_VALUE_WARNING", "tweak {0} registered with {1} must have a default value that is a " + "literal of type {2}"); static final DiagnosticType NON_GLOBAL_TWEAK_INIT_ERROR = DiagnosticType.error( "JSC_NON_GLOBAL_TWEAK_INIT_ERROR", "tweak declaration {0} must occur in the global scope"); static final DiagnosticType TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR = DiagnosticType.error( "JSC_TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR", "Cannot override the default value of tweak {0} after it has been " + "registered"); static final DiagnosticType TWEAK_WRONG_GETTER_TYPE_WARNING = DiagnosticType.warning( "JSC_TWEAK_WRONG_GETTER_TYPE_WARNING", "tweak getter function {0} used for tweak registered using {1}"); static final DiagnosticType INVALID_TWEAK_ID_ERROR = DiagnosticType.error( "JSC_INVALID_TWEAK_ID_ERROR", "tweak ID contains illegal characters. Only letters, numbers, _ " + "and . are allowed"); /** * An enum of goog.tweak functions. */ private static enum TweakFunction { REGISTER_BOOLEAN("goog.tweak.registerBoolean", "boolean", Token.TRUE, Token.FALSE), REGISTER_NUMBER("goog.tweak.registerNumber", "number", Token.NUMBER), REGISTER_STRING("goog.tweak.registerString", "string", Token.STRING), OVERRIDE_DEFAULT_VALUE("goog.tweak.overrideDefaultValue"), GET_COMPILER_OVERRIDES("goog.tweak.getCompilerOverrides_"), GET_BOOLEAN("goog.tweak.getBoolean", REGISTER_BOOLEAN), GET_NUMBER("goog.tweak.getNumber", REGISTER_NUMBER), GET_STRING("goog.tweak.getString", REGISTER_STRING); final String name; final String expectedTypeName; final Token validNodeTypeA; final Token validNodeTypeB; final TweakFunction registerFunction; TweakFunction(String name) { this(name, null, Token.EMPTY, Token.EMPTY, null); } TweakFunction(String name, String expectedTypeName, Token validNodeTypeA) { this(name, expectedTypeName, validNodeTypeA, Token.EMPTY, null); } TweakFunction(String name, String expectedTypeName, Token validNodeTypeA, Token validNodeTypeB) { this(name, expectedTypeName, validNodeTypeA, validNodeTypeB, null); } TweakFunction(String name, TweakFunction registerFunction) { this(name, null, Token.EMPTY, Token.EMPTY, registerFunction); } TweakFunction(String name, String expectedTypeName, Token validNodeTypeA, Token validNodeTypeB, TweakFunction registerFunction) { this.name = name; this.expectedTypeName = expectedTypeName; this.validNodeTypeA = validNodeTypeA; this.validNodeTypeB = validNodeTypeB; this.registerFunction = registerFunction; } boolean isValidNodeType(Token type) { return type == validNodeTypeA || type == validNodeTypeB; } boolean isCorrectRegisterFunction(TweakFunction registerFunction) { checkNotNull(registerFunction); return this.registerFunction == registerFunction; } boolean isGetterFunction() { return registerFunction != null; } String getName() { return name; } String getExpectedTypeName() { return expectedTypeName; } Node createDefaultValueNode() { switch (this) { case REGISTER_BOOLEAN: return IR.falseNode(); case REGISTER_NUMBER: return IR.number(0); case REGISTER_STRING: return IR.string(""); default: throw new IllegalStateException(); } } } // A map of function name -> TweakFunction. private static final Map TWEAK_FUNCTIONS_MAP; static { TWEAK_FUNCTIONS_MAP = new HashMap<>(); for (TweakFunction func : TweakFunction.values()) { TWEAK_FUNCTIONS_MAP.put(func.getName(), func); } } ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks, Map compilerDefaultValueOverrides) { this.compiler = compiler; this.stripTweaks = stripTweaks; // Having the map sorted is required for the unit tests to be deterministic. this.compilerDefaultValueOverrides = new TreeMap<>(); this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides); } @Override public void process(Node externs, Node root) { CollectTweaksResult result = collectTweaks(root); applyCompilerDefaultValueOverrides(result.tweakInfos); if (stripTweaks) { stripAllCalls(result.tweakInfos); } else if (!compilerDefaultValueOverrides.isEmpty()) { replaceGetCompilerOverridesCalls(result.getOverridesCalls); } } /** * Passes the compiler default value overrides to the JS by replacing calls * to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value; */ private void replaceGetCompilerOverridesCalls( List calls) { for (TweakFunctionCall call : calls) { Node callNode = call.callNode; Node objNode = createCompilerDefaultValueOverridesVarNode(callNode); callNode.replaceWith(objNode); compiler.reportChangeToEnclosingScope(objNode); } } /** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter * functions with the tweak's default value. */ private void stripAllCalls(Map tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); compiler.reportChangeToEnclosingScope(parent); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)) .srcref(callNode); parent.replaceChild(callNode, voidZeroNode); compiler.reportChangeToEnclosingScope(parent); } } } } /** * Creates a JS object that holds a map of tweakId -> default value override. */ private Node createCompilerDefaultValueOverridesVarNode( Node sourceInformationNode) { Node objNode = IR.objectlit().srcref(sourceInformationNode); for (Entry entry : compilerDefaultValueOverrides.entrySet()) { Node objKeyNode = IR.stringKey(entry.getKey()) .useSourceInfoIfMissingFrom(sourceInformationNode); Node objValueNode = entry.getValue().cloneNode() .useSourceInfoIfMissingFrom(sourceInformationNode); objKeyNode.addChildToBack(objValueNode); objNode.addChildToBack(objKeyNode); } return objNode; } /** Sets the default values of tweaks based on compiler options. */ private void applyCompilerDefaultValueOverrides( Map tweakInfos) { for (Entry entry : compilerDefaultValueOverrides.entrySet()) { String tweakId = entry.getKey(); TweakInfo tweakInfo = tweakInfos.get(tweakId); if (tweakInfo == null) { compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId)); } else { TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc; Node value = entry.getValue(); if (!registerFunc.isValidNodeType(value.getToken())) { compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } else { tweakInfo.defaultValueNode = value; } } } } /** * Finds all calls to goog.tweak functions and emits warnings/errors if any * of the calls have issues. * @return A map of {@link TweakInfo} structures, keyed by tweak ID. */ private CollectTweaksResult collectTweaks(Node root) { CollectTweaks pass = new CollectTweaks(); NodeTraversal.traverse(compiler, root, pass); Map tweakInfos = pass.allTweaks; for (TweakInfo tweakInfo : tweakInfos.values()) { tweakInfo.emitAllWarnings(); } return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls); } private static final class CollectTweaksResult { final Map tweakInfos; final List getOverridesCalls; CollectTweaksResult(Map tweakInfos, List getOverridesCalls) { this.tweakInfos = tweakInfos; this.getOverridesCalls = getOverridesCalls; } } /** * Processes all calls to goog.tweak functions. */ private final class CollectTweaks extends AbstractPostOrderCallback { final Map allTweaks = new HashMap<>(); final List getOverridesCalls = new ArrayList<>(); @SuppressWarnings("incomplete-switch") @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName); if (tweakFunc == null) { return; } if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) { getOverridesCalls.add( new TweakFunctionCall(tweakFunc, n)); return; } // Ensure the first parameter (the tweak ID) is a string literal. Node tweakIdNode = n.getSecondChild(); if (!tweakIdNode.isString()) { compiler.report(JSError.make(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR)); return; } String tweakId = tweakIdNode.getString(); // Make sure there is a TweakInfo structure for it. TweakInfo tweakInfo = allTweaks.get(tweakId); if (tweakInfo == null) { tweakInfo = new TweakInfo(tweakId); allTweaks.put(tweakId, tweakInfo); } switch (tweakFunc) { case REGISTER_BOOLEAN: case REGISTER_NUMBER: case REGISTER_STRING: // Ensure the ID contains only valid characters. if (!ID_MATCHER.matchesAllOf(tweakId)) { compiler.report(JSError.make(tweakIdNode, INVALID_TWEAK_ID_ERROR)); } // Ensure tweaks are registered in the global scope. if (!t.inGlobalHoistScope()) { compiler.report(JSError.make(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweaks are registered only once. if (tweakInfo.isRegistered()) { compiler.report(JSError.make(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId)); break; } Node tweakDefaultValueNode = tweakIdNode.getNext().getNext(); tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n, tweakDefaultValueNode); break; case OVERRIDE_DEFAULT_VALUE: // Ensure tweaks overrides occur in the global scope. if (!t.inGlobalScope()) { compiler.report(JSError.make(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweak overrides occur before the tweak is registered. if (tweakInfo.isRegistered()) { compiler.report(JSError.make(n, TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR, tweakId)); break; } tweakDefaultValueNode = tweakIdNode.getNext(); tweakInfo.addOverrideDefaultValueCall(t.getSourceName(), tweakFunc, n, tweakDefaultValueNode); break; case GET_BOOLEAN: case GET_NUMBER: case GET_STRING: tweakInfo.addGetterCall(t.getSourceName(), tweakFunc, n); break; default: break; } } } /** * Holds information about a call to a goog.tweak function. */ private static final class TweakFunctionCall { final TweakFunction tweakFunc; final Node callNode; final Node valueNode; TweakFunctionCall(TweakFunction tweakFunc, Node callNode) { this(tweakFunc, callNode, null); } TweakFunctionCall(TweakFunction tweakFunc, Node callNode, Node valueNode) { this.callNode = callNode; this.tweakFunc = tweakFunc; this.valueNode = valueNode; } Node getIdNode() { return callNode.getSecondChild(); } } /** * Stores information about a single tweak. */ private final class TweakInfo { final String tweakId; final List functionCalls; TweakFunctionCall registerCall; Node defaultValueNode; TweakInfo(String tweakId) { this.tweakId = tweakId; functionCalls = new ArrayList<>(); } /** * If this tweak is registered, then looks for type warnings in default * value parameters and getter functions. If it is not registered, emits an * error for each function call. */ void emitAllWarnings() { if (isRegistered()) { emitAllTypeWarnings(); } else { emitUnknownTweakErrors(); } } /** * Emits a warning for each default value parameter that has the wrong type * and for each getter function that was used for the wrong type of tweak. */ void emitAllTypeWarnings() { for (TweakFunctionCall call : functionCalls) { Node valueNode = call.valueNode; TweakFunction tweakFunc = call.tweakFunc; TweakFunction registerFunc = registerCall.tweakFunc; if (valueNode != null) { // For register* and overrideDefaultValue calls, ensure the default // value is a literal of the correct type. if (!registerFunc.isValidNodeType(valueNode.getToken())) { compiler.report(JSError.make( valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } } else if (tweakFunc.isGetterFunction()) { // For getter calls, ensure the correct getter was used. if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) { compiler.report(JSError.make( call.callNode, TWEAK_WRONG_GETTER_TYPE_WARNING, tweakFunc.getName(), registerFunc.getName())); } } } } /** * Emits an error for each function call that was found. */ void emitUnknownTweakErrors() { for (TweakFunctionCall call : functionCalls) { compiler.report(JSError.make( call.getIdNode(), UNKNOWN_TWEAK_WARNING, tweakId)); } } void addRegisterCall(String sourceName, TweakFunction tweakFunc, Node callNode, Node defaultValueNode) { registerCall = new TweakFunctionCall(tweakFunc, callNode, defaultValueNode); functionCalls.add(registerCall); } void addOverrideDefaultValueCall(String sourceName, TweakFunction tweakFunc, Node callNode, Node defaultValueNode) { functionCalls.add(new TweakFunctionCall(tweakFunc, callNode, defaultValueNode)); this.defaultValueNode = defaultValueNode; } void addGetterCall(String sourceName, TweakFunction tweakFunc, Node callNode) { functionCalls.add(new TweakFunctionCall(tweakFunc, callNode)); } boolean isRegistered() { return registerCall != null; } Node getDefaultValueNode() { checkState(isRegistered()); // Use calls to goog.tweak.overrideDefaultValue() first. if (defaultValueNode != null) { return defaultValueNode; } // Use the value passed to the register function next. if (registerCall.valueNode != null) { return registerCall.valueNode; } // Otherwise, use the default value for the tweak's type. return registerCall.tweakFunc.createDefaultValueNode(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy