private void addStatement(GenerateNodeContext context, Node stmt) { CodingConvention convention = compiler.getCodingConvention(); Node n = context.getNode(); Node exprRoot = n; while (!NodeUtil.isStatementBlock(exprRoot.getParent())) { exprRoot = exprRoot.getParent(); } // It's important that any class-building calls (goog.inherits) // come right after the class definition, so move the export after that. while (true) { Node next = exprRoot.getNext(); if (next != null && NodeUtil.isExprCall(next) && convention.getClassesDefinedByCall(next.getFirstChild()) != null) { exprRoot = next; } else { break; } } Node block = exprRoot.getParent(); block.addChildAfter(stmt, exprRoot); }
/** * @return Whether the definitionSite represents a function whose call signature can be * modified. */ private boolean canChangeSignature(Node function) { Definition definition = getFunctionDefinition(function); CodingConvention convention = compiler.getCodingConvention(); Preconditions.checkState(!definition.isExtern()); Collection<UseSite> useSites = defFinder.getUseSites(definition); for (UseSite site : useSites) { Node parent = site.node.getParent(); // This was a use site removed by something else before we run. // 1. By another pass before us which means the definition graph is // no updated properly. // 2. By the continuations algorithm above. if (parent == null) { continue; // Ignore it. } // Ignore references within goog.inherits calls. if (NodeUtil.isCall(parent) && convention.getClassesDefinedByCall(parent) != null) { continue; } // Accessing the property directly prevents rewrite. if (!SimpleDefinitionFinder.isCallOrNewSite(site)) { if (!(NodeUtil.isGetProp(parent) && NodeUtil.isFunctionObjectCall(parent.getParent()))) { return false; } } if (NodeUtil.isFunctionObjectApply(parent)) { return false; } // TODO(johnlenz): support specialization // Multiple definitions prevent rewrite. // Attempt to validate the state of the simple definition finder. Node nameNode = site.node; Collection<Definition> singleSiteDefinitions = defFinder.getDefinitionsReferencedAt(nameNode); Preconditions.checkState(singleSiteDefinitions.size() == 1); Preconditions.checkState(singleSiteDefinitions.contains(definition)); } return true; }
@Override public void process(Node externs, Node root) { FindExportableNodes findExportableNodes = new FindExportableNodes(compiler); NodeTraversal.traverse(compiler, root, findExportableNodes); Map<String, GenerateNodeContext> exports = findExportableNodes.getExports(); for (Map.Entry<String, GenerateNodeContext> entry : exports.entrySet()) { String export = entry.getKey(); GenerateNodeContext context = entry.getValue(); // Emit the proper CALL expression. // This is an optimization to avoid exporting everything as a symbol // because exporting a property is significantly simpler/faster. // Only export the property if the parent is being exported or // if the parent is "prototype" and the grandparent is being exported. String parent = null; String grandparent = null; Node node = context.getNode().getFirstChild(); if (node.getType() == Token.GETPROP) { parent = node.getFirstChild().getQualifiedName(); if (node.getFirstChild().getType() == Token.GETPROP && getPropertyName(node.getFirstChild()).equals(PROTOTYPE_PROPERTY)) { grandparent = node.getFirstChild().getFirstChild().getQualifiedName(); } } boolean useExportSymbol = true; if (grandparent != null && exports.containsKey(grandparent)) { useExportSymbol = false; } else if (parent != null && exports.containsKey(parent)) { useExportSymbol = false; } Node call; if (useExportSymbol) { // exportSymbol(publicPath, object); call = new Node( Token.CALL, NodeUtil.newQualifiedNameNode(exportSymbolFunction, context.getNode(), export)); call.addChildToBack(Node.newString(export)); call.addChildToBack(NodeUtil.newQualifiedNameNode(export, context.getNode(), export)); } else { // exportProperty(object, publicName, symbol); String property = getPropertyName(node); call = new Node( Token.CALL, new Node[] { NodeUtil.newQualifiedNameNode( exportPropertyFunction, context.getNode(), exportPropertyFunction), NodeUtil.newQualifiedNameNode(parent, context.getNode(), exportPropertyFunction), Node.newString(property), NodeUtil.newQualifiedNameNode(export, context.getNode(), exportPropertyFunction) }); } Node expression = new Node(Token.EXPR_RESULT, call); annotate(expression); // It's important that any class-building calls (goog.inherits) // come right after the class definition, so move the export after that. Node insertionPoint = context.getContextNode().getNext(); CodingConvention convention = compiler.getCodingConvention(); while (insertionPoint != null && NodeUtil.isExprCall(insertionPoint) && convention.getClassesDefinedByCall(insertionPoint.getFirstChild()) != null) { insertionPoint = insertionPoint.getNext(); } if (insertionPoint == null) { context.getScriptNode().addChildToBack(expression); } else { context.getScriptNode().addChildBefore(expression, insertionPoint); } compiler.reportCodeChange(); } }
/** * Traverses everything in the current scope and marks variables that are referenced. * * <p>During traversal, we identify subtrees that will only be referenced if their enclosing * variables are referenced. Instead of traversing those subtrees, we create a continuation for * them, and traverse them lazily. */ private void traverseNode(Node n, Node parent, Scope scope) { int type = n.getType(); Var var = null; switch (type) { case Token.FUNCTION: // If this function is a removable var, then create a continuation // for it instead of traversing immediately. if (NodeUtil.isFunctionDeclaration(n)) { var = scope.getVar(n.getFirstChild().getString()); } if (var != null && isRemovableVar(var)) { continuations.put(var, new Continuation(n, scope)); } else { traverseFunction(n, scope); } return; case Token.ASSIGN: Assign maybeAssign = Assign.maybeCreateAssign(n); if (maybeAssign != null) { // Put this in the assign map. It might count as a reference, // but we won't know that until we have an index of all assigns. var = scope.getVar(maybeAssign.nameNode.getString()); if (var != null) { assignsByVar.put(var, maybeAssign); assignsByNode.put(maybeAssign.nameNode, maybeAssign); if (isRemovableVar(var) && !maybeAssign.mayHaveSecondarySideEffects) { // If the var is unreferenced and performing this assign has // no secondary side effects, then we can create a continuation // for it instead of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } } break; case Token.CALL: // Look for calls to inheritance-defining calls (such as goog.inherits). SubclassRelationship subclassRelationship = codingConvention.getClassesDefinedByCall(n); if (subclassRelationship != null) { Var subclassVar = scope.getVar(subclassRelationship.subclassName); // Don't try to track the inheritance calls for non-globals. It would // be more correct to only not track when the subclass does not // reference a constructor, but checking that it is a global is // easier and mostly the same. if (subclassVar != null && subclassVar.isGlobal() && !referenced.contains(subclassVar)) { // Save a reference to the EXPR node. inheritsCalls.put(subclassVar, parent); continuations.put(subclassVar, new Continuation(n, scope)); return; } } break; case Token.NAME: var = scope.getVar(n.getString()); if (parent.getType() == Token.VAR) { Node value = n.getFirstChild(); if (value != null && var != null && isRemovableVar(var) && !NodeUtil.mayHaveSideEffects(value)) { // If the var is unreferenced and creating its value has no side // effects, then we can create a continuation for it instead // of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } else { // If arguments is escaped, we just assume the worst and continue // on all the parameters. if ("arguments".equals(n.getString()) && scope.isLocal()) { Node lp = scope.getRootNode().getFirstChild().getNext(); for (Node a = lp.getFirstChild(); a != null; a = a.getNext()) { markReferencedVar(scope.getVar(a.getString())); } } // All name references that aren't declarations or assigns // are references to other vars. if (var != null) { // If that var hasn't already been marked referenced, then // start tracking it. If this is an assign, do nothing // for now. if (isRemovableVar(var)) { if (!assignsByNode.containsKey(n)) { markReferencedVar(var); } } else { markReferencedVar(var); } } } break; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { traverseNode(c, n, scope); } }