/** Inline a function into the call site. */ private void inlineFunction(NodeTraversal t, Reference ref, FunctionState fs) { Function fn = fs.getFn(); String fnName = fn.getName(); Node fnNode = fs.getSafeFnNode(); Node newExpr = injector.inline(ref, fnName, fnNode); if (!newExpr.isEquivalentTo(ref.callNode)) { t.getCompiler().reportChangeToEnclosingScope(newExpr); } t.getCompiler().addToDebugLog("Inlined function: " + fn.getName()); }
@Override public void exitScope(NodeTraversal traversal) { Preconditions.checkNotNull(traversal); // This is the case when we are exiting the global scope where we had never // collected argument access list. Since we do not perform this optimization // for the global scope, we will skip this exit point. if (currentArgumentsAccess == null) { return; } Node function = traversal.getScopeRoot(); if (!function.isFunction()) { return; } // Attempt to replace the argument access and if the AST has been change, // report back to the compiler. if (tryReplaceArguments(traversal.getScope())) { traversal.getCompiler().reportCodeChange(); } // After the attempt to replace the arguments. The currentArgumentsAccess // is stale and as we exit the Scope, no longer holds all the access to the // current scope anymore. We'll pop the access list from the outer scope // and set it as currentArgumentsAccess if the outer scope is not the global // scope. if (!argumentsAccessStack.isEmpty()) { currentArgumentsAccess = argumentsAccessStack.pop(); } else { currentArgumentsAccess = null; } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isReferenceName(n)) { return; } Scope referencedIn = t.getScope(); String oldName = n.getString(); Scope current = referencedIn; boolean doRename = false; String newName = null; while (current != null) { Map<String, String> renamesAtCurrentLevel = renameMap.get(current.getRootNode()); if (current.isDeclared(oldName, false)) { return; } else if (renamesAtCurrentLevel != null && renamesAtCurrentLevel.containsKey(oldName)) { doRename = true; newName = renamesAtCurrentLevel.get(oldName); break; } else { current = current.getParent(); } } if (doRename) { n.setString(newName); t.getCompiler().reportCodeChange(); } }
private static void reportIfWasEmpty(NodeTraversal t, Node block) { Preconditions.checkState(block.isBlock()); // A semicolon is distinguished from a block without children by // annotating it with EMPTY_BLOCK. Blocks without children are // usually intentional, especially with loops. if (!block.hasChildren() && block.isAddedBlock()) { t.getCompiler().report(t.makeError(block, SUSPICIOUS_SEMICOLON)); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(moz): Add support for renaming classes. if (!n.isLet() && !n.isConst() && !NodeUtil.isBlockScopedFunctionDeclaration(n)) { return; } Scope scope = t.getScope(); Node nameNode = n.getFirstChild(); if (!n.isFunction() && !nameNode.hasChildren() && (parent == null || !NodeUtil.isEnhancedFor(parent))) { nameNode.addChildToFront(IR.name("undefined").useSourceInfoIfMissingFrom(nameNode)); } String oldName = nameNode.getString(); if (n.isLet() || n.isConst()) { blockScopedDeclarations.add(n); } Scope hoistScope = scope.getClosestHoistScope(); boolean doRename = false; if (scope != hoistScope) { doRename = hoistScope.isDeclared(oldName, true) || undeclaredNames.contains(oldName); String newName = doRename ? oldName + "$" + compiler.getUniqueNameIdSupplier().get() : oldName; Var oldVar = scope.getVar(oldName); scope.undeclare(oldVar); hoistScope.declare(newName, nameNode, oldVar.input); if (doRename) { nameNode.setString(newName); Node scopeRoot = scope.getRootNode(); if (!renameMap.containsKey(scopeRoot)) { renameMap.put(scopeRoot, new HashMap<String, String>()); } renameMap.get(scopeRoot).put(oldName, newName); } } if (doRename) { t.getCompiler().reportCodeChange(); } }
private static void reportIfNaN(NodeTraversal t, Node n) { if (NodeUtil.isNaN(n)) { t.getCompiler().report(t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN)); } }
@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); } } }