All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.CollapseVariableDeclarations Maven / Gradle / Ivy

/*
 * Copyright 2006 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Collapses multiple variable declarations into a single one. i.e the
 * following:
 *
 * 
 * var a;
 * var b = 1;
 * var c = 2;
 * 
* * becomes: * *
var a, b = 1, c = 2;
* * This reduces the generated code size. More optimizations are possible: *
  • Group all variable declarations inside a function into one such variable. * declaration block.
  • *
  • Re-use variables instead of declaring a new one if they are used for * only part of a function.
  • * * Similarly, also collapses assigns like: * *
     * a = true;
     * b = true;
     * var c = true;
     * 
    * * becomes: * *
    var c = b = a = true;
    * * @author [email protected] (Nick Santos) */ class CollapseVariableDeclarations implements CompilerPass { /** Reference to JS Compiler */ private final AbstractCompiler compiler; /** Encapsulation of information about a variable declaration collapse */ private static class Collapse { /** * Variable declaration that any following var nodes should be * collapsed into */ final Node startNode; /** * Last node (non-inclusive) of the chain of nodes to collapse. */ final Node endNode; /** Parent of the nodes to the collapse */ final Node parent; Collapse(Node startNode, Node endNode, Node parent) { this.startNode = startNode; this.endNode = endNode; this.parent = parent; } } /** * Collapses to do in this pass. */ private final List collapses = new ArrayList<>(); /** * Nodes we've already looked at for collapsing, so that we don't look at them * again (we look ahead when examining what nodes can be collapsed, and the * node traversal may give them to us again) */ private final Set nodesToCollapse = new HashSet<>(); CollapseVariableDeclarations(AbstractCompiler compiler) { Preconditions.checkState(!compiler.getLifeCycleStage().isNormalized()); this.compiler = compiler; } @Override public void process(Node externs, Node root) { collapses.clear(); nodesToCollapse.clear(); NodeTraversal.traverseEs6(compiler, root, new GatherCollapses()); if (!collapses.isEmpty()) { applyCollapses(); compiler.reportCodeChange(); } } /** * Gathers all of the variable declarations / assignments that should be * collapsed into one. * * We do not do the collapsing as we go since node traversal would be affected * by the changes we are making to the parse tree. */ private class GatherCollapses extends AbstractPostOrderCallback { // If a VAR is declared like // var x; // then we should not create new VAR nodes for it later in the tree. // This is a workaround for a bug in Firefox. private final Set blacklistedVars = new HashSet<>(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isVar()) { blacklistStubVars(t, n); } // Only care about var nodes if (!n.isVar() && !canBeRedeclared(n, t.getScope())) { return; } // If we've already looked at this node, skip it if (nodesToCollapse.contains(n)) { return; } // Adjacent VAR children of an IF node are the if and else parts and can't // be collapsed if (parent.isIf()) { return; } Node varNode = n; boolean hasVar = n.isVar(); // Find variable declarations that follow this one (if any) n = n.getNext(); boolean hasNodesToCollapse = false; while (n != null && (n.isVar() || canBeRedeclared(n, t.getScope()))) { if (n.isVar()) { blacklistStubVars(t, n); hasVar = true; } nodesToCollapse.add(n); hasNodesToCollapse = true; n = n.getNext(); } if (hasNodesToCollapse && hasVar) { nodesToCollapse.add(varNode); collapses.add(new Collapse(varNode, n, parent)); } } private void blacklistStubVars(NodeTraversal t, Node varNode) { for (Node child = varNode.getFirstChild(); child != null; child = child.getNext()) { if (child.getFirstChild() == null) { blacklistedVars.add(t.getScope().getVar(child.getString())); } } } private boolean canBeRedeclared(Node n, Scope s) { if (!NodeUtil.isExprAssign(n)) { return false; } Node assign = n.getFirstChild(); Node lhs = assign.getFirstChild(); if (!lhs.isName()) { return false; } Var var = s.getVar(lhs.getString()); return var != null && (NodeUtil.getEnclosingFunction(var.getScope().getRootNode()) == NodeUtil.getEnclosingFunction(s.getRootNode())) && !isNamedParameter(var) && !blacklistedVars.contains(var); } } private static boolean isNamedParameter(Var v) { return v.isParam(); } private void applyCollapses() { for (Collapse collapse : collapses) { Node var = new Node(Token.VAR); var.useSourceInfoIfMissingFrom(collapse.startNode); collapse.parent.addChildBefore(var, collapse.startNode); boolean redeclaration = false; for (Node n = collapse.startNode; n != collapse.endNode;) { Node next = n.getNext(); Preconditions.checkState(var.getNext() == n); collapse.parent.removeChildAfter(var); if (n.isVar()) { while (n.hasChildren()) { var.addChildToBack(n.removeFirstChild()); } } else { Node assign = n.getFirstChild(); Node lhs = assign.getFirstChild(); Preconditions.checkState(lhs.isName()); Node rhs = assign.getLastChild(); lhs.addChildToBack(rhs.detachFromParent()); var.addChildToBack(lhs.detachFromParent()); redeclaration = true; } n = next; } if (redeclaration) { // TODO(johnlenz): share the JSDocInfo here rather than building // a new one each time. JSDocInfoBuilder builder = new JSDocInfoBuilder(false); builder.recordSuppressions(ImmutableSet.of("duplicate")); JSDocInfo info = builder.build(); var.setJSDocInfo(info); } } } }




    © 2015 - 2025 Weber Informatics LLC | Privacy Policy