/** Checks for illegal declarations. */ private void checkDeclaration(NodeTraversal t, Node n) { if ("eval".equals(n.getString())) { t.report(n, EVAL_DECLARATION); } else if ("arguments".equals(n.getString())) { t.report(n, ARGUMENTS_DECLARATION); } }
/** * Expand a jQuery.expandedEach call * * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment * with GETPROP nodes to allow for renaming. */ private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) { Node objectToLoopOver = n.getChildAtIndex(1); if (objectToLoopOver == null) { return; } Node callbackFunction = objectToLoopOver.getNext(); if (callbackFunction == null || !callbackFunction.isFunction()) { return; } // Run the peephole optimizations on the first argument to handle // cases like ("a " + "b").split(" ") peepholePasses.process(null, n.getChildAtIndex(1)); // Create a reference tree Node nClone = n.cloneTree(); objectToLoopOver = nClone.getChildAtIndex(1); // Check to see if the first argument is something we recognize and can // expand. if (!objectToLoopOver.isObjectLit() && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) { t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null); return; } // Find all references to the callback function arguments List<Node> keyNodeReferences = new ArrayList<>(); List<Node> valueNodeReferences = new ArrayList<>(); new NodeTraversal( compiler, new FindCallbackArgumentReferences( callbackFunction, keyNodeReferences, valueNodeReferences, objectToLoopOver.isArrayLit())) .traverseInnerNode( NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope()); if (keyNodeReferences.isEmpty()) { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); return; } Node fncBlock = tryExpandJqueryEachCall( t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences); if (fncBlock != null && fncBlock.hasChildren()) { replaceOriginalJqueryEachCall(n, fncBlock); } else { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: Node callee = n.getFirstChild(); if (callee.matchesQualifiedName("goog.module")) { if (currentModule == null) { currentModule = n; } else { t.report(n, MULTIPLE_MODULES_IN_FILE); } } else if (callee.matchesQualifiedName("goog.provide")) { t.report(n, MODULE_AND_PROVIDES); } else if (callee.matchesQualifiedName("goog.require")) { checkRequireCall(t, n, parent); } break; case Token.THIS: if (t.inGlobalHoistScope()) { t.report(n, GOOG_MODULE_REFERENCES_THIS); } break; case Token.THROW: if (t.inGlobalHoistScope()) { t.report(n, GOOG_MODULE_USES_THROW); } break; case Token.SCRIPT: currentModule = null; break; } }
/** Checks that object literal keys or class method names are valid. */ private static void checkObjectLiteralOrClass(NodeTraversal t, Node n) { Set<String> getters = new HashSet<>(); Set<String> setters = new HashSet<>(); for (Node key = n.getFirstChild(); key != null; key = key.getNext()) { if (!key.isSetterDef()) { // normal property and getter cases if (!getters.add(key.getString())) { if (n.isClassMembers()) { t.report(key, DUPLICATE_CLASS_METHODS); } else { t.report(key, DUPLICATE_OBJECT_KEY); } } } if (!key.isGetterDef()) { // normal property and setter cases if (!setters.add(key.getString())) { if (n.isClassMembers()) { t.report(key, DUPLICATE_CLASS_METHODS); } else { t.report(key, DUPLICATE_OBJECT_KEY); } } } } }
/** Checks that an assignment is not to the "arguments" object. */ private static void checkAssignment(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { if ("arguments".equals(n.getFirstChild().getString())) { t.report(n, ARGUMENTS_ASSIGNMENT); } else if ("eval".equals(n.getFirstChild().getString())) { // Note that assignment to eval is already illegal because any use of // that name is illegal. t.report(n, EVAL_ASSIGNMENT); } } }
/** Reports a warning for with statements. */ private static void checkWith(NodeTraversal t, Node n) { JSDocInfo info = n.getJSDocInfo(); boolean allowWith = info != null && info.getSuppressions().contains("with"); if (!allowWith) { t.report(n, USE_OF_WITH); } }
public void testReport() { final List<JSError> errors = new ArrayList<JSError>(); Compiler compiler = new Compiler( new BasicErrorManager() { @Override public void report(CheckLevel level, JSError error) { errors.add(error); } @Override public void println(CheckLevel level, JSError error) {} @Override protected void printSummary() {} }); compiler.initCompilerOptionsIfTesting(); NodeTraversal t = new NodeTraversal(compiler, null); DiagnosticType dt = DiagnosticType.warning("FOO", "{0}, {1} - {2}"); t.report(null, dt, "Foo", "Bar", "Hello"); assertEquals(1, errors.size()); assertEquals("Foo, Bar - Hello", errors.get(0).description); }
private static boolean reportIfNonObject(NodeTraversal t, Node n, DiagnosticType diagnosticType) { if (n.isAdd() || !NodeUtil.mayBeObect(n)) { t.report(n.getParent(), diagnosticType); return true; } return false; }
/** Checks that the arguments.callee is not used. */ private void checkGetProp(NodeTraversal t, Node n) { Node target = n.getFirstChild(); Node prop = n.getLastChild(); if (prop.getString().equals("callee")) { if (target.isName() && target.getString().equals("arguments")) { t.report(n, ARGUMENTS_CALLEE_FORBIDDEN); } } else if (prop.getString().equals("caller")) { if (target.isName() && target.getString().equals("arguments")) { t.report(n, ARGUMENTS_CALLER_FORBIDDEN); } else if (isFunctionType(target)) { t.report(n, FUNCTION_CALLER_FORBIDDEN); } } else if (prop.getString().equals("arguments") && isFunctionType(target)) { t.report(n, FUNCTION_ARGUMENTS_PROP_FORBIDDEN); } }
/** Checks that variables, functions, and arguments are not deleted. */ private static void checkDelete(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { Var v = t.getScope().getVar(n.getFirstChild().getString()); if (v != null) { t.report(n, DELETE_VARIABLE); } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isSwitch()) { Set<String> cases = new HashSet<>(); for (Node curr = n.getSecondChild(); curr != null; curr = curr.getNext()) { String source = compiler.toSource(curr.getFirstChild()); if (!cases.add(source)) { t.report(curr, DUPLICATE_CASE); } } } }
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); }
/** * Eliminates an assignment if the lvalue is: - A field name that's a strip name - A qualified * name that begins with a strip type * * @param t The traversal * @param n An ASSIGN node * @param parent {@code n}'s parent */ void maybeEliminateAssignmentByLvalueName(NodeTraversal t, Node n, Node parent) { // ASSIGN // lvalue // rvalue Node lvalue = n.getFirstChild(); if (nameEndsWithFieldNameToStrip(lvalue) || qualifiedNameBeginsWithStripType(lvalue)) { // Limit to EXPR_RESULT because it is not // safe to eliminate assignment in complex expressions, // e.g. in ((x = 7) + 8) if (NodeUtil.isExpressionNode(parent)) { Node gramps = parent.getParent(); replaceWithEmpty(parent, gramps); compiler.reportCodeChange(); } else { t.report(n, STRIP_ASSIGNMENT_ERROR, lvalue.getQualifiedName()); } } }
/** * Determines whether the given node helps to define a strip type. For example, * goog.inherits(stripType, Object) would be such a call. * * <p>Also reports an error if a non-strip type inherits from a strip type. * * @param t The current traversal * @param callNode The CALL node */ private boolean actsOnStripType(NodeTraversal t, Node callNode) { SubclassRelationship classes = compiler.getCodingConvention().getClassesDefinedByCall(callNode); if (classes != null) { // It's okay to strip a type that inherits from a non-stripped type // e.g. goog.inherits(goog.debug.Logger, Object) if (qualifiedNameBeginsWithStripType(classes.subclassName)) { return true; } // report an error if a non-strip type inherits from a // strip type. if (qualifiedNameBeginsWithStripType(classes.superclassName)) { t.report( callNode, STRIP_TYPE_INHERIT_ERROR, classes.subclassName, classes.superclassName); } } return false; }
/** Checks that the function is used legally. */ private static void checkFunctionUse(NodeTraversal t, Node n) { if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { t.report(n, BAD_FUNCTION_DECLARATION); } }
@Override public void visit(NodeTraversal traversal, Node node, Node parent) { // Fix types in JSDoc. JSDocInfo doc = node.getJSDocInfo(); if (doc != null) { fixJsdoc(traversal.getScope(), doc); } // Find qualified names that match static calls if (node.isQualifiedName()) { String name = node.getQualifiedName(); Polyfill polyfill = null; if (polyfills.statics.containsKey(name)) { polyfill = polyfills.statics.get(name); } if (polyfill != null) { // Check the scope to make sure it's a global name. if (isRootInScope(node, traversal) || NodeUtil.isVarOrSimpleAssignLhs(node, parent)) { return; } if (!languageOutIsAtLeast(polyfill.polyfillVersion)) { traversal.report( node, INSUFFICIENT_OUTPUT_VERSION_ERROR, name, compiler.getOptions().getLanguageOut().toString(), polyfill.polyfillVersion.toString()); } if (!languageOutIsAtLeast(polyfill.nativeVersion)) { if (!polyfill.installer.isEmpty()) { // Note: add the installer *before* replacing the node! addInstaller(node, polyfill.installer); } if (!polyfill.rewrite.isEmpty()) { changed = true; Node replacement = NodeUtil.newQName(compiler, polyfill.rewrite); replacement.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, replacement); } } // TODO(sdh): consider warning if language_in is too low? it's not really any // harm, and we can't do it consistently for the prototype methods, so maybe // it's not worth doing here, either. return; // isGetProp (below) overlaps, so just bail out now } } // Add any requires that *might* match method calls (but don't rewrite anything) if (node.isGetProp() && node.getLastChild().isString()) { for (Polyfill polyfill : polyfills.methods.get(node.getLastChild().getString())) { if (!languageOutIsAtLeast(polyfill.nativeVersion) && !polyfill.installer.isEmpty()) { // Check if this is a global function. if (!isStaticFunction(node, traversal)) { addInstaller(node, polyfill.installer); } } } } }
private Node tryExpandJqueryEachCall( NodeTraversal t, Node n, Node callbackFunction, List<Node> keyNodes, List<Node> valueNodes) { Node callTarget = n.getFirstChild(); Node objectToLoopOver = callTarget.getNext(); // New block to contain the expanded statements Node fncBlock = IR.block().srcref(callTarget); boolean isValidExpansion = true; // Expand the jQuery.expandedEach call Node key = objectToLoopOver.getFirstChild(), val = null; for (int i = 0; key != null; key = key.getNext(), i++) { if (key != null) { if (objectToLoopOver.isArrayLit()) { // Arrays have a value of their index number val = IR.number(i).srcref(key); } else { val = key.getFirstChild(); } } // Keep track of the replaced nodes so we can reset the tree List<Node> newKeys = new ArrayList<>(); List<Node> newValues = new ArrayList<>(); List<Node> origGetElems = new ArrayList<>(); List<Node> newGetProps = new ArrayList<>(); // Replace all of the key nodes with the prop name for (int j = 0; j < keyNodes.size(); j++) { Node origNode = keyNodes.get(j); Node ancestor = origNode.getParent(); Node newNode = IR.string(key.getString()).srcref(key); newKeys.add(newNode); ancestor.replaceChild(origNode, newNode); // Walk up the tree to see if the key is used in a GETELEM // assignment while (ancestor != null && !NodeUtil.isStatement(ancestor) && !ancestor.isGetElem()) { ancestor = ancestor.getParent(); } // Convert GETELEM nodes to GETPROP nodes so that they can be // renamed or removed. if (ancestor != null && ancestor.isGetElem()) { Node propObject = ancestor; while (propObject.isGetProp() || propObject.isGetElem()) { propObject = propObject.getFirstChild(); } Node ancestorClone = ancestor.cloneTree(); // Run the peephole passes to handle cases such as // obj['lit' + key] = val; peepholePasses.process(null, ancestorClone.getChildAtIndex(1)); Node prop = ancestorClone.getChildAtIndex(1); if (prop.isString() && NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) { Node target = ancestorClone.getFirstChild(); Node newGetProp = IR.getprop(target.detachFromParent(), prop.detachFromParent()); newGetProps.add(newGetProp); origGetElems.add(ancestor); ancestor.getParent().replaceChild(ancestor, newGetProp); } else { if (prop.isString() && !NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) { t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_NAME_ERROR, prop.getString()); } isValidExpansion = false; } } } if (isValidExpansion) { // Replace all of the value nodes with the prop value for (int j = 0; val != null && j < valueNodes.size(); j++) { Node origNode = valueNodes.get(j); Node newNode = val.cloneTree(); newValues.add(newNode); origNode.getParent().replaceChild(origNode, newNode); } // Wrap the new tree in an anonymous function call Node fnc = IR.function( IR.name("").srcref(key), IR.paramList().srcref(key), callbackFunction.getChildAtIndex(2).cloneTree()) .srcref(key); Node call = IR.call(fnc).srcref(key); call.putBooleanProp(Node.FREE_CALL, true); fncBlock.addChildToBack(IR.exprResult(call).srcref(call)); } // Reset the source tree for (int j = 0; j < newGetProps.size(); j++) { newGetProps.get(j).getParent().replaceChild(newGetProps.get(j), origGetElems.get(j)); } for (int j = 0; j < newKeys.size(); j++) { newKeys.get(j).getParent().replaceChild(newKeys.get(j), keyNodes.get(j)); } for (int j = 0; j < newValues.size(); j++) { newValues.get(j).getParent().replaceChild(newValues.get(j), valueNodes.get(j)); } if (!isValidExpansion) { return null; } } return fncBlock; }