diff --git a/src/com/google/javascript/jscomp/CollapseProperties.java b/src/com/google/javascript/jscomp/CollapseProperties.java index 21de78fab19..85dd6d2e9af 100644 --- a/src/com/google/javascript/jscomp/CollapseProperties.java +++ b/src/com/google/javascript/jscomp/CollapseProperties.java @@ -400,62 +400,103 @@ private void collapseDeclarationOfNameAndDescendants(Name n, String alias) { // Handle this name first so that nested object literals get unrolled. if (n.canCollapse()) { - updateGlobalNameDeclaration(n, alias, canCollapseChildNames); + updateObjLitOrFunctionDeclaration(n, alias, canCollapseChildNames); } if (n.props == null) { return; } for (Name p : n.props) { - collapseDeclarationOfNameAndDescendants(p, appendPropForAlias(alias, p.getBaseName())); + // Recur first so that saved node ancestries are intact when needed. + collapseDeclarationOfNameAndDescendants( + p, appendPropForAlias(alias, p.getBaseName())); + if (!p.inExterns + && canCollapseChildNames + && p.getDeclaration() != null + && p.canCollapse() + && p.getDeclaration().node != null + && p.getDeclaration().node.getParent() != null + && p.getDeclaration().node.getParent().isAssign()) { + updateSimpleDeclaration( + appendPropForAlias(alias, p.getBaseName()), p, p.getDeclaration()); + } } } /** * Updates the initial assignment to a collapsible property at global scope - * by adding a VAR stub and collapsing the property. e.g. c = a.b = 1; => var a$b; c = a$b = 1; - * This specifically handles "twinned" assignments, which are those where the assignment is also - * used as a reference and which need special handling. + * by changing it to a variable declaration (e.g. a.b = 1 -> var a$b = 1). + * The property's value may either be a primitive or an object literal or + * function whose properties aren't collapsible. * * @param alias The flattened property name (e.g. "a$b") * @param refName The name for the reference being updated. - * @param ref An object containing information about the assignment getting updated + * @param ref An object containing information about the assignment getting + * updated */ - private void updateTwinnedDeclaration(String alias, Name refName, Ref ref) { - checkNotNull(ref.getTwin()); + private void updateSimpleDeclaration(String alias, Name refName, Ref ref) { Node rvalue = ref.node.getNext(); Node parent = ref.node.getParent(); Node grandparent = parent.getParent(); + Node greatGrandparent = grandparent.getParent(); if (rvalue != null && rvalue.isFunction()) { checkForHosedThisReferences(rvalue, refName.docInfo, refName); } // Create the new alias node. - Node nameNode = - NodeUtil.newName(compiler, alias, grandparent.getFirstChild(), refName.getFullName()); + Node nameNode = NodeUtil.newName(compiler, alias, grandparent.getFirstChild(), + refName.getFullName()); NodeUtil.copyNameAnnotations(ref.node.getLastChild(), nameNode); - // BEFORE: - // ... (x.y = 3); - // - // AFTER: - // var x$y; - // ... (x$y = 3); - - Node current = grandparent; - Node currentParent = grandparent.getParent(); - for (; - !currentParent.isScript() && !currentParent.isNormalBlock(); - current = currentParent, currentParent = currentParent.getParent()) {} - - // Create a stub variable declaration right - // before the current statement. - Node stubVar = IR.var(nameNode.cloneTree()).useSourceInfoIfMissingFrom(nameNode); - currentParent.addChildBefore(stubVar, current); - - parent.replaceChild(ref.node, nameNode); - compiler.reportChangeToEnclosingScope(nameNode); + if (grandparent.isExprResult()) { + // BEFORE: a.b.c = ...; + // exprstmt + // assign + // getprop + // getprop + // name a + // string b + // string c + // NODE + // AFTER: var a$b$c = ...; + // var + // name a$b$c + // NODE + + // Remove the r-value (NODE). + parent.removeChild(rvalue); + nameNode.addChildToFront(rvalue); + + Node varNode = IR.var(nameNode); + greatGrandparent.replaceChild(grandparent, varNode); + compiler.reportChangeToEnclosingScope(varNode); + } else { + // This must be a complex assignment. + checkNotNull(ref.getTwin()); + + // BEFORE: + // ... (x.y = 3); + // + // AFTER: + // var x$y; + // ... (x$y = 3); + + Node current = grandparent; + Node currentParent = grandparent.getParent(); + for (; + !currentParent.isScript() && !currentParent.isNormalBlock(); + current = currentParent, currentParent = currentParent.getParent()) {} + + // Create a stub variable declaration right + // before the current statement. + Node stubVar = IR.var(nameNode.cloneTree()) + .useSourceInfoIfMissingFrom(nameNode); + currentParent.addChildBefore(stubVar, current); + + parent.replaceChild(ref.node, nameNode); + compiler.reportChangeToEnclosingScope(nameNode); + } } /** @@ -477,7 +518,7 @@ private void updateTwinnedDeclaration(String alias, Name refName, Ref ref) { * this name. (This is mostly passed for convenience; it's equivalent to * n.canCollapseChildNames()). */ - private void updateGlobalNameDeclaration( + private void updateObjLitOrFunctionDeclaration( Name n, String alias, boolean canCollapseChildNames) { Ref decl = n.getDeclaration(); if (decl == null) { @@ -486,18 +527,24 @@ private void updateGlobalNameDeclaration( return; } + if (decl.getTwin() != null) { + // Twin declarations will get handled when normal references + // are handled. + return; + } + switch (decl.node.getParent().getToken()) { case ASSIGN: - updateGlobalNameDeclarationAtAssignNode( + updateObjLitOrFunctionDeclarationAtAssignNode( n, alias, canCollapseChildNames); break; case VAR: case LET: case CONST: - updateGlobalNameDeclarationAtVariableNode(n, canCollapseChildNames); + updateObjLitOrFunctionDeclarationAtVariableNode(n, canCollapseChildNames); break; case FUNCTION: - updateGlobalNameDeclarationAtFunctionNode(n, canCollapseChildNames); + updateFunctionDeclarationAtFunctionNode(n, canCollapseChildNames); break; default: break; @@ -507,12 +554,12 @@ private void updateGlobalNameDeclaration( /** * Updates the first initialization (a.k.a "declaration") of a global name * that occurs at an ASSIGN node. See comment for - * {@link #updateGlobalNameDeclaration}. + * {@link #updateObjLitOrFunctionDeclaration}. * * @param n An object representing a global name (e.g. "a", "a.b.c") * @param alias The flattened name for {@code n} (e.g. "a", "a$b$c") */ - private void updateGlobalNameDeclarationAtAssignNode( + private void updateObjLitOrFunctionDeclarationAtAssignNode( Name n, String alias, boolean canCollapseChildNames) { // NOTE: It's important that we don't add additional nodes // (e.g. a var node before the exprstmt) because the exprstmt might be @@ -523,10 +570,6 @@ private void updateGlobalNameDeclarationAtAssignNode( // we are only collapsing for global names. Ref ref = n.getDeclaration(); Node rvalue = ref.node.getNext(); - if (ref.getTwin() != null) { - updateTwinnedDeclaration(alias, ref.name, ref); - return; - } Node varNode = new Node(Token.VAR); Node varParent = ref.node.getAncestor(3); Node grandparent = ref.node.getAncestor(2); @@ -613,11 +656,11 @@ public void visit(NodeTraversal t, Node n, Node parent) { /** * Updates the first initialization (a.k.a "declaration") of a global name that occurs at a VAR - * node. See comment for {@link #updateGlobalNameDeclaration}. + * node. See comment for {@link #updateObjLitOrFunctionDeclaration}. * * @param n An object representing a global name (e.g. "a") */ - private void updateGlobalNameDeclarationAtVariableNode( + private void updateObjLitOrFunctionDeclarationAtVariableNode( Name n, boolean canCollapseChildNames) { if (!canCollapseChildNames) { return; @@ -654,11 +697,11 @@ private void updateGlobalNameDeclarationAtVariableNode( /** * Updates the first initialization (a.k.a "declaration") of a global name * that occurs at a FUNCTION node. See comment for - * {@link #updateGlobalNameDeclaration}. + * {@link #updateObjLitOrFunctionDeclaration}. * * @param n An object representing a global name (e.g. "a") */ - private void updateGlobalNameDeclarationAtFunctionNode( + private void updateFunctionDeclarationAtFunctionNode( Name n, boolean canCollapseChildNames) { if (!canCollapseChildNames || !n.canCollapse()) { return;