/** @return Whether the name is used in a way that might be a candidate for inlining. */ static boolean isCandidateUsage(Node name) { Node parent = name.getParent(); Preconditions.checkState(name.isName()); if (parent.isVar() || parent.isFunction()) { // This is a declaration. Duplicate declarations are handle during // function candidate gathering. return true; } if (parent.isCall() && parent.getFirstChild() == name) { // This is a normal reference to the function. return true; } // Check for a ".call" to the named function: // CALL // GETPROP/GETELEM // NAME // STRING == "call" // This-Value // Function-parameter-1 // ... if (NodeUtil.isGet(parent) && name == parent.getFirstChild() && name.getNext().isString() && name.getNext().getString().equals("call")) { Node gramps = name.getAncestor(2); if (gramps.isCall() && gramps.getFirstChild() == parent) { // Yep, a ".call". return true; } } return false; }
private boolean matchesNodeShape(Node template, Node ast) { if (isTemplateParameterNode(template)) { // Match the entire expression but only if it is an expression. return !NodeUtil.isStatement(ast); } else if (isTemplateLocalNameNode(template)) { // Match any name. Maybe match locals here. if (!ast.isName()) { return false; } } else if (template.isCall()) { // Loosely match CALL nodes. isEquivalentToShallow checks free calls against non-free calls, // but the template should ignore that distinction. if (ast == null || !ast.isCall() || ast.getChildCount() != template.getChildCount()) { return false; } // But check any children. } else if (!template.isEquivalentToShallow(ast)) { return false; } // isEquivalentToShallow guarantees the child counts match Node templateChild = template.getFirstChild(); Node astChild = ast.getFirstChild(); while (templateChild != null) { if (!matchesNodeShape(templateChild, astChild)) { return false; } templateChild = templateChild.getNext(); astChild = astChild.getNext(); } return true; }
/** * Processes array literals or calls containing spreads. Eg.: [1, 2, ...x, 4, 5] => [1, * 2].concat(x, [4, 5]); Eg.: f(...arr) => f.apply(null, arr) Eg.: new F(...args) => new * Function.prototype.bind.apply(F, [].concat(args)) */ private void visitArrayLitOrCallWithSpread(Node node, Node parent) { Preconditions.checkArgument(node.isCall() || node.isArrayLit() || node.isNew()); List<Node> groups = new ArrayList<>(); Node currGroup = null; Node callee = node.isArrayLit() ? null : node.removeFirstChild(); Node currElement = node.removeFirstChild(); while (currElement != null) { if (currElement.isSpread()) { if (currGroup != null) { groups.add(currGroup); currGroup = null; } groups.add(currElement.removeFirstChild()); } else { if (currGroup == null) { currGroup = IR.arraylit(); } currGroup.addChildToBack(currElement); } currElement = node.removeFirstChild(); } if (currGroup != null) { groups.add(currGroup); } Node result = null; Node joinedGroups = IR.call( IR.getprop(IR.arraylit(), IR.string("concat")), groups.toArray(new Node[groups.size()])); if (node.isArrayLit()) { result = joinedGroups; } else if (node.isCall()) { if (NodeUtil.mayHaveSideEffects(callee) && callee.isGetProp()) { Node statement = node; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } Node freshVar = IR.name(FRESH_SPREAD_VAR + freshSpreadVarCounter++); Node n = IR.var(freshVar.cloneTree()); n.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(n, statement); callee.addChildToFront(IR.assign(freshVar.cloneTree(), callee.removeFirstChild())); result = IR.call(IR.getprop(callee, IR.string("apply")), freshVar, joinedGroups); } else { Node context = callee.isGetProp() ? callee.getFirstChild().cloneTree() : IR.nullNode(); result = IR.call(IR.getprop(callee, IR.string("apply")), context, joinedGroups); } } else { Node bindApply = NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), "Function.prototype.bind.apply"); result = IR.newNode(bindApply, callee, joinedGroups); } result.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, result); compiler.reportCodeChange(); }
/** * Replaces a GETPROP a.b.c with a NAME a$b$c. * * @param alias A flattened prefix name (e.g. "a$b") * @param n The GETPROP node corresponding to the original name (e.g. "a.b") * @param parent {@code n}'s parent * @param originalName String version of the property name. */ private void flattenNameRef(String alias, Node n, Node parent, String originalName) { Preconditions.checkArgument( n.isGetProp(), "Expected GETPROP, found %s. Node: %s", Token.name(n.getType()), n); // BEFORE: // getprop // getprop // name a // string b // string c // AFTER: // name a$b$c Node ref = NodeUtil.newName(compiler, alias, n, originalName); NodeUtil.copyNameAnnotations(n.getLastChild(), ref); if (parent.isCall() && n == parent.getFirstChild()) { // The node was a call target, we are deliberately flatten these as // we node the "this" isn't provided by the namespace. Mark it as such: parent.putBooleanProp(Node.FREE_CALL, true); } TypeI type = n.getTypeI(); if (type != null) { ref.setTypeI(type); } parent.replaceChild(n, ref); compiler.reportCodeChange(); }
void visitSubtree(Node n, Node parent) { if (n.isCall()) { boolean isModuleDetected = codingConvention.extractIsModuleFile(n, parent); if (isModuleDetected) { this.isModuleFile = true; } String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && !parent.isExprResult() && !parent.isScript()) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isCall() && isGoogDefineClass(n)) { if (!validateUsage(n)) { compiler.report(JSError.make(n, GOOG_CLASS_TARGET_INVALID)); } } maybeRewriteClassDefinition(n); }
private boolean isContainedInGoogDefineClass(Node n) { while (n != null) { n = n.getParent(); if (n.isCall()) { if (isGoogDefineClass(n)) { return true; } } else if (!n.isObjectLit() && !n.isStringKey()) { break; } } return false; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isCall()) { Node target = n.getFirstChild(); // TODO(johnlenz): add this to the coding convention // so we can remove goog.reflect.sinkValue as well. if (target.isName() && target.getString().equals(PROTECTOR_FN)) { Node expr = n.getLastChild(); n.detachChildren(); parent.replaceChild(n, expr); } } }
/** Remove useless calls: Object.defineProperties(o, {}) -> o */ private Node tryFoldCall(Node n) { Preconditions.checkArgument(n.isCall()); if (NodeUtil.isObjectDefinePropertiesDefinition(n)) { Node srcObj = n.getLastChild(); if (srcObj.isObjectLit() && !srcObj.hasChildren()) { Node parent = n.getParent(); Node destObj = n.getSecondChild().detachFromParent(); parent.replaceChild(n, destObj); reportCodeChange(); } } return n; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp() && convention.isPrototypeAlias(n)) { maybeReplaceJqueryPrototypeAlias(n); } else if (n.isCall()) { Node callTarget = n.getFirstChild(); String qName = callTarget.getQualifiedName(); if (isJqueryExtendCall(callTarget, qName, this.compiler)) { maybeExpandJqueryExtendCall(n); } else if (isJqueryExpandedEachCall(n, qName)) { maybeExpandJqueryEachCall(t, n); } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall() || !n.getFirstChild().isName()) { return; } Node callTarget = n.getFirstChild(); String callName = callTarget.getString(); if (startMarkerName.equals(callName)) { if (!parent.isExprResult()) { compiler.report(t.makeError(n, INVALID_MARKER_USAGE, startMarkerName)); return; } markerStack.push(parent); return; } if (!endMarkerName.equals(callName)) { return; } Node endMarkerNode = parent; if (!endMarkerNode.isExprResult()) { compiler.report(t.makeError(n, INVALID_MARKER_USAGE, endMarkerName)); return; } if (markerStack.isEmpty()) { compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } Node startMarkerNode = markerStack.pop(); if (endMarkerNode.getParent() != startMarkerNode.getParent()) { // The end marker isn't in the same block as the start marker. compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } // This is a valid marker set add it to the list of markers to process. validMarkers.add(new Marker(startMarkerNode, endMarkerNode)); }
@Override public FlowScope getPreciserScopeKnowingConditionOutcome( Node condition, FlowScope blindScope, boolean outcome) { if (condition.isCall() && condition.getChildCount() == 2) { Node callee = condition.getFirstChild(); Node param = condition.getLastChild(); if (callee.isGetProp() && param.isQualifiedName()) { JSType paramType = getTypeIfRefinable(param, blindScope); Node left = callee.getFirstChild(); Node right = callee.getLastChild(); if (left.isName() && "goog".equals(left.getString()) && right.isString()) { Function<TypeRestriction, JSType> restricter = restricters.get(right.getString()); if (restricter != null) { return restrictParameter(param, paramType, blindScope, restricter, outcome); } } } } return nextPreciserScopeKnowingConditionOutcome(condition, blindScope, outcome); }
/** * There are two types of calls we are interested in calls without explicit "this" values (what * we are call "free" calls) and direct call to eval. */ private static void annotateCalls(Node n) { Preconditions.checkState(n.isCall()); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); // ignore cast nodes. while (first.isCast()) { first = first.getFirstChild(); } if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.isName() && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } }
private void checkRequireCall(NodeTraversal t, Node callNode, Node parent) { Preconditions.checkState(callNode.isCall()); switch (parent.getType()) { case Token.EXPR_RESULT: return; case Token.GETPROP: if (parent.getParent().isName()) { checkRequireCall(t, callNode, parent.getParent()); return; } break; case Token.NAME: case Token.OBJECT_PATTERN: { Node declaration = parent.getParent(); if (declaration.getChildCount() != 1) { t.report(declaration, ONE_REQUIRE_PER_DECLARATION); } return; } } t.report(callNode, REQUIRE_NOT_AT_TOP_LEVEL); }
/** @return Whether the call represents a call to Polymer. */ private static boolean isPolymerCall(Node value) { return value != null && value.isCall() && value.getFirstChild().matchesQualifiedName("Polymer"); }
/** * Returns whether the node is an argument of a function that returns a unique id (the last part * of the qualified name matches GET_UNIQUE_ID_FUNCTION). */ private static boolean insideGetUniqueIdCall(Node n) { Node parent = n.getParent(); String name = parent.isCall() ? parent.getFirstChild().getQualifiedName() : null; return name != null && name.endsWith(GET_UNIQUE_ID_FUNCTION); }
private static Node fixupFreeCall(Node call) { Preconditions.checkState(call.isCall()); call.putBooleanProp(Node.FREE_CALL, true); return call; }
/** @return Whether the call represents a class definition. */ private static boolean isGoogDefineClass(Node value) { if (value != null && value.isCall()) { return value.getFirstChild().matchesQualifiedName("goog.defineClass"); } return false; }
public boolean isJqueryExpandedEachCall(Node call, String qName) { Preconditions.checkArgument(call.isCall()); return call.getFirstChild() != null && JQUERY_EXPANDED_EACH_NAME.equals(qName); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } int pt = parent.getType(); if (pt == Token.COMMA) { Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first // expression to a comma to be a no-op if it's used to indirect // an eval. if (n == parent.getFirstChild() && parent.getChildCount() == 2 && n.getNext().isName() && "eval".equals(n.getNext().getString())) { return; } } if (n == parent.getLastChild()) { for (Node an : parent.getAncestors()) { int ancestorType = an.getType(); if (ancestorType == Token.COMMA) continue; if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return; else break; } } } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) { if (pt == Token.FOR && parent.getChildCount() == 4 && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) { // Fall through and look for warnings for the 1st and 3rd child // of a for. } else { return; // it might be ok to not have a side-effect } } boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType()); if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) { if (n.isQualifiedName() && n.getJSDocInfo() != null) { // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. return; } else if (n.isExprResult()) { // we already reported the problem when we visited the child. return; } String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } }
/** * Tries to optimize all the arguments array access in this scope by assigning a name to each * element. * * @param scope scope of the function * @return true if any modification has been done to the AST */ private boolean tryReplaceArguments(Scope scope) { Node parametersList = scope.getRootNode().getSecondChild(); Preconditions.checkState(parametersList.isParamList()); // Keep track of rather this function modified the AST and needs to be // reported back to the compiler later. boolean changed = false; // Number of parameter that can be accessed without using the arguments // array. int numNamedParameter = parametersList.getChildCount(); // We want to guess what the highest index that has been access from the // arguments array. We will guess that it does not use anything index higher // than the named parameter list first until we see other wise. int highestIndex = numNamedParameter - 1; // Iterate through all the references to arguments array in the function to // determine the real highestIndex. for (Node ref : currentArgumentsAccess) { Node getElem = ref.getParent(); // Bail on anything but argument[c] access where c is a constant. // TODO(user): We might not need to bail out all the time, there might // be more cases that we can cover. if (!getElem.isGetElem() || ref != getElem.getFirstChild()) { return false; } Node index = ref.getNext(); // We have something like arguments[x] where x is not a constant. That // means at least one of the access is not known. if (!index.isNumber() || index.getDouble() < 0) { // TODO(user): Its possible not to give up just yet. The type // inference did a 'semi value propagation'. If we know that string // is never a subclass of the type of the index. We'd know that // it is never 'callee'. return false; // Give up. } Node getElemParent = getElem.getParent(); // When we have argument[0](), replacing it with a() is semantically // different if argument[0] is a function call that refers to 'this' if (getElemParent.isCall() && getElemParent.getFirstChild() == getElem) { // TODO(user): We can consider using .call() if aliasing that // argument allows shorter alias for other arguments. return false; } // Replace the highest index if we see an access that has a higher index // than all the one we saw before. int value = (int) index.getDouble(); if (value > highestIndex) { highestIndex = value; } } // Number of extra arguments we need. // For example: function() { arguments[3] } access index 3 so // it will need 4 extra named arguments to changed into: // function(a,b,c,d) { d }. int numExtraArgs = highestIndex - numNamedParameter + 1; // Temporary holds the new names as string for quick access later. String[] argNames = new String[numExtraArgs]; // Insert the formal parameter to the method's signature. // Example: function() --> function(r0, r1, r2) for (int i = 0; i < numExtraArgs; i++) { String name = getNewName(); argNames[i] = name; parametersList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(parametersList)); changed = true; } // This loop performs the replacement of arguments[x] -> a if x is known. for (Node ref : currentArgumentsAccess) { Node index = ref.getNext(); // Skip if it is unknown. if (!index.isNumber()) { continue; } int value = (int) index.getDouble(); // Unnamed parameter. if (value >= numNamedParameter) { ref.getGrandparent() .replaceChild(ref.getParent(), IR.name(argNames[value - numNamedParameter])); } else { // Here, for no apparent reason, the user is accessing a named parameter // with arguments[idx]. We can replace it with the actual name for them. Node name = parametersList.getFirstChild(); // This is a linear search for the actual name from the signature. // It is not necessary to make this fast because chances are the user // will not deliberately write code like this. for (int i = 0; i < value; i++) { name = name.getNext(); } ref.getGrandparent().replaceChild(ref.getParent(), IR.name(name.getString())); } changed = true; } return changed; }
private void visitScriptNode(NodeTraversal t) { if (mode == Mode.SINGLE_FILE && requires.isEmpty()) { // Likely a file that isn't using Closure at all. return; } Set<String> namespaces = new HashSet<>(); // For every usage, check that there is a goog.require, and warn if not. for (Map.Entry<String, Node> entry : usages.entrySet()) { String namespace = entry.getKey(); if (namespace.endsWith(".call") || namespace.endsWith(".apply")) { namespace = namespace.substring(0, namespace.lastIndexOf('.')); } if (namespace.startsWith("goog.global.")) { continue; } Node node = entry.getValue(); JSDocInfo info = NodeUtil.getBestJSDocInfo(NodeUtil.getEnclosingStatement(node)); if (info != null && info.getSuppressions().contains("missingRequire")) { continue; } String outermostClassName = getOutermostClassName(namespace); // The parent namespace is also checked as part of the requires so that classes // used by goog.module are still checked properly. This may cause missing requires // to be missed but in practice that should happen rarely. String nonNullClassName = outermostClassName != null ? outermostClassName : namespace; String parentNamespace = null; int separatorIndex = nonNullClassName.lastIndexOf('.'); if (separatorIndex > 0) { parentNamespace = nonNullClassName.substring(0, separatorIndex); } boolean notProvidedByConstructors = !providedNames.contains(namespace) && !providedNames.contains(outermostClassName) && !providedNames.contains(parentNamespace); boolean notProvidedByRequires = !requires.containsKey(namespace) && !requires.containsKey(outermostClassName) && !requires.containsKey(parentNamespace); if (notProvidedByConstructors && notProvidedByRequires && !namespaces.contains(namespace) && !"goog".equals(parentNamespace)) { // TODO(mknichel): If the symbol is not explicitly provided, find the next best // symbol from the provides in the same file. String rootName = Splitter.on('.').split(namespace).iterator().next(); if (mode != Mode.SINGLE_FILE || closurizedNamespaces.contains(rootName)) { if (node.isCall()) { compiler.report(t.makeError(node, MISSING_REQUIRE_CALL_WARNING, namespace)); } else { compiler.report(t.makeError(node, MISSING_REQUIRE_WARNING, namespace)); } namespaces.add(namespace); } } } // For every goog.require, check that there is a usage (in either usages or weakUsages) // and warn if there is not. for (Map.Entry<String, Node> entry : requires.entrySet()) { String require = entry.getKey(); Node call = entry.getValue(); Node parent = call.getParent(); if (parent.isAssign()) { // var baz = goog.require('foo.bar.baz'); // Assume that the var 'baz' is used somewhere, and don't warn. continue; } if (!usages.containsKey(require) && !weakUsages.containsKey(require)) { reportExtraRequireWarning(call, require); } } }
/** Returns whether the node is an argument of a goog.getCssName call. */ private static boolean insideGetCssNameCall(Node n) { Node parent = n.getParent(); return parent.isCall() && parent.getFirstChild().matchesQualifiedName(GET_CSS_NAME_FUNCTION); }
private void visitSuper(Node node, Node parent) { Node enclosing = parent; Node potentialCallee = node; if (!parent.isCall()) { enclosing = parent.getParent(); potentialCallee = parent; } if (!enclosing.isCall() || enclosing.getFirstChild() != potentialCallee) { cannotConvertYet(node, "Only calls to super or to a method of super are supported."); return; } Node clazz = NodeUtil.getEnclosingClass(node); if (clazz == null) { compiler.report(JSError.make(node, NO_SUPERTYPE)); return; } if (NodeUtil.getClassNameNode(clazz) == null) { // Unnamed classes of the form: // f(class extends D { ... }); // give the problem that there is no name to be used in the call to goog.base for the // translation of super calls. // This will throw an error when the class is processed. return; } Node enclosingMemberDef = NodeUtil.getEnclosingClassMember(node); if (enclosingMemberDef.isStaticMember()) { Node superName = clazz.getFirstChild().getNext(); if (!superName.isQualifiedName()) { // This has already been reported, just don't need to continue processing the class. return; } Node callTarget; potentialCallee.detachFromParent(); if (potentialCallee == node) { // of the form super() potentialCallee = IR.getprop(superName.cloneTree(), IR.string(enclosingMemberDef.getString())); enclosing.putBooleanProp(Node.FREE_CALL, false); } else { // of the form super.method() potentialCallee.replaceChild(node, superName.cloneTree()); } callTarget = IR.getprop(potentialCallee, IR.string("call")); enclosing.addChildToFront(callTarget); enclosing.addChildAfter(IR.thisNode(), callTarget); enclosing.useSourceInfoIfMissingFromForTree(enclosing); compiler.reportCodeChange(); return; } String methodName; Node callName = enclosing.removeFirstChild(); if (callName.isSuper()) { methodName = enclosingMemberDef.getString(); } else { methodName = callName.getLastChild().getString(); } Node baseCall = baseCall(clazz, methodName, enclosing.removeChildren()) .useSourceInfoIfMissingFromForTree(enclosing); enclosing.getParent().replaceChild(enclosing, baseCall); compiler.reportCodeChange(); }
@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.getFirstChild().getNext(); if (!tweakIdNode.isString()) { compiler.report(t.makeError(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(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR)); } // Ensure tweaks are registered in the global scope. if (!t.inGlobalScope()) { compiler.report(t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweaks are registered only once. if (tweakInfo.isRegistered()) { compiler.report(t.makeError(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(t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweak overrides occur before the tweak is registered. if (tweakInfo.isRegistered()) { compiler.report(t.makeError(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); } }