com.google.javascript.jscomp.Denormalize Maven / Gradle / Ivy
Show all versions of closure-compiler-unshaded Show documentation
/*
* Copyright 2009 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 static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.alwaysTrue;
import com.google.javascript.jscomp.ReferenceCollector.Behavior;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* The goal with this pass is to reverse the simplifications done in the normalization pass that are
* not handled by other passes (such as CollapseVariableDeclarations) to avoid making the resulting
* code larger.
*
* Currently this pass only does a few things:
*
*
1. Push statements into for-loop initializer. This: var a = 0; for(;a<0;a++) {} becomes:
* for(var a = 0;a<0;a++) {}
*
*
2. Fold assignments like x = x + 1 into x += 1
*
*
3. Inline 'var' keyword. For instance:
* var x;
* if (y) { x = 0; }
*
becomes if (y) { var x = 0; }
, effectively undoing what {@link
* HoistVarsOutOfBlocks} does.
*/
class Denormalize implements CompilerPass, NodeTraversal.Callback, Behavior {
private final AbstractCompiler compiler;
private final FeatureSet outputFeatureSet;
Denormalize(AbstractCompiler compiler, FeatureSet outputFeatureSet) {
this.compiler = compiler;
this.outputFeatureSet = outputFeatureSet;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
// Don't inline the VAR declaration if this compilation involves old-style ctemplates.
if (compiler.getOptions().syntheticBlockStartMarker == null) {
(new ReferenceCollector(compiler, this, new SyntacticScopeCreator(compiler))).process(root);
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
/**
* Implements step 3 (inlining the var keyword).
*/
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {
Node scopeRoot = t.getScopeRoot();
if (scopeRoot.isBlock() && scopeRoot.getParent().isFunction()) {
boolean changed = false;
for (Var v : t.getScope().getVarIterable()) {
ReferenceCollection references = referenceMap.getReferences(v);
Reference declaration = null;
Reference assign = null;
for (Reference r : references) {
if (r.isVarDeclaration()
&& NodeUtil.isStatement(r.getNode().getParent())
&& !r.isInitializingDeclaration()) {
declaration = r;
} else if (assign == null
&& r.isSimpleAssignmentToName()
&& r.getScope().getClosestHoistScope().equals(t.getScope())) {
assign = r;
}
}
if (declaration != null && assign != null) {
Node lhs = assign.getNode();
Node assignNode = lhs.getParent();
if (assignNode.getParent().isExprResult()) {
Node rhs = lhs.getNext();
assignNode.getParent().replaceWith(IR.var(lhs.detach(), rhs.detach()));
Node var = declaration.getNode().getParent();
checkState(var.isVar(), var);
NodeUtil.removeChild(var, declaration.getNode());
changed = true;
}
}
}
if (changed) {
t.reportCodeChange();
}
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
maybeCollapseIntoForStatements(n, parent);
maybeCollapseLogicalAssignShorthand(t, n, parent);
maybeCollapseAssignShorthand(n, parent);
}
/**
* Collapse VARs and EXPR_RESULT node into FOR loop initializers where
* possible.
*/
private void maybeCollapseIntoForStatements(Node n, Node parent) {
// Only SCRIPT, BLOCK, and LABELs can have FORs that can be collapsed into.
// LABELs are not supported here.
if (parent == null || !NodeUtil.isStatementBlock(parent)) {
return;
}
// Is the current node something that can be in a for loop initializer?
if (!n.isExprResult() && !n.isVar()) {
return;
}
// Is the next statement a valid FOR?
Node nextSibling = n.getNext();
if (nextSibling == null) {
return;
} else if (nextSibling.isForIn() || nextSibling.isForOf()) {
Node forNode = nextSibling;
Node forVar = forNode.getFirstChild();
if (forVar.isName()
&& n.isVar() && n.hasOneChild()) {
Node name = n.getFirstChild();
if (!name.hasChildren()
&& forVar.getString().equals(name.getString())) {
// OK, the names match, and the var declaration does not have an
// initializer. Move it into the loop.
n.detach();
forVar.replaceWith(n);
compiler.reportChangeToEnclosingScope(parent);
}
}
} else if (nextSibling.isVanillaFor() && nextSibling.getFirstChild().isEmpty()) {
// Does the current node contain an in operator? If so, embedding
// the expression in a for loop can cause some JavaScript parsers (such
// as the PlayStation 3's browser based on Access's NetFront
// browser) to fail to parse the code.
// See bug 1778863 for details.
if (NodeUtil.has(n, Node::isIn, alwaysTrue())) {
return;
}
// Move the current node into the FOR loop initializer.
Node forNode = nextSibling;
Node oldInitializer = forNode.getFirstChild();
n.detach();
Node newInitializer;
if (n.isVar()) {
newInitializer = n;
} else {
// Extract the expression from EXPR_RESULT node.
checkState(n.hasOneChild(), n);
newInitializer = n.getFirstChild();
newInitializer.detach();
}
oldInitializer.replaceWith(newInitializer);
compiler.reportChangeToEnclosingScope(forNode);
}
}
private void maybeCollapseAssignShorthand(Node n, Node parent) {
if (!isCollapsableAssign(n)) {
return;
}
Node op = n.getLastChild();
Token assignOp = getAssignOpFromOp(op);
if (n.getFirstChild().getString().equals(op.getFirstChild().getString())) {
op.setToken(assignOp);
Node opDetached = op.detach();
opDetached.setJSDocInfo(n.getJSDocInfo());
n.replaceWith(opDetached);
compiler.reportChangeToEnclosingScope(parent);
}
}
private void maybeCollapseLogicalAssignShorthand(NodeTraversal t, Node n, Node parent) {
if (!isCollapsableLogicalAssign(n)) {
return;
}
Node op = n.getLastChild();
Token assignOp = getAssignOpFromOp(n);
if (n.getFirstChild().getString().equals(op.getFirstChild().getString())) {
op.setToken(assignOp);
Node opDetached = op.detach();
opDetached.setJSDocInfo(n.getJSDocInfo());
n.replaceWith(opDetached);
NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.LOGICAL_ASSIGNMENT, compiler);
compiler.reportChangeToEnclosingScope(parent);
}
}
private boolean isCollapsableAssign(Node n) {
return n.isAssign()
&& n.getFirstChild().isName()
&& hasCorrespondingAssignmentOp(n.getLastChild())
&& n.getLastChild().getFirstChild().isName();
}
private boolean isCollapsableLogicalAssign(Node n) {
return outputFeatureSet.has(Feature.LOGICAL_ASSIGNMENT)
&& (n.isOr() || n.isAnd() || n.isNullishCoalesce())
&& n.getFirstChild().isName()
&& n.getLastChild().isAssign()
&& n.getLastChild().getFirstChild().isName();
}
private Token getAssignOpFromOp(Node n) {
switch (n.getToken()) {
case BITOR:
return Token.ASSIGN_BITOR;
case BITXOR:
return Token.ASSIGN_BITXOR;
case BITAND:
return Token.ASSIGN_BITAND;
case LSH:
return Token.ASSIGN_LSH;
case RSH:
return Token.ASSIGN_RSH;
case URSH:
return Token.ASSIGN_URSH;
case ADD:
return Token.ASSIGN_ADD;
case SUB:
return Token.ASSIGN_SUB;
case MUL:
return Token.ASSIGN_MUL;
case EXPONENT:
return Token.ASSIGN_EXPONENT;
case DIV:
return Token.ASSIGN_DIV;
case MOD:
return Token.ASSIGN_MOD;
case OR:
return Token.ASSIGN_OR;
case AND:
return Token.ASSIGN_AND;
case COALESCE:
return Token.ASSIGN_COALESCE;
default:
throw new IllegalStateException("Unexpected operator: " + n);
}
}
private boolean hasCorrespondingAssignmentOp(Node n) {
switch (n.getToken()) {
case BITOR:
case BITXOR:
case BITAND:
case LSH:
case RSH:
case URSH:
case ADD:
case SUB:
case MUL:
case DIV:
case MOD:
return true;
default:
return false;
}
}
}