/*
* Copyright 2011 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.Lists;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Deque;
import java.util.List;
/**
* Temp fix to decompose nested assignment bug in Opera.
*
* See open source issue: 390
*
*
* The conditions for which this Opera-specific bug will hit is
*
* v = rhs
*
* where 'rhs' contains a compound assignment of the form a[i] or obj.p _and_
* 'v' is also used in 'rhs'. i.e., for the above example,
*
* z = bar[z] = bar[z] || [];
*
* or
*
* x = foo.bar += x.baz;
*
* Opera 11.10 final will have the fix included, but if emitting constructs like
* the above is common and can be readily avoided, that'd be very helpful. More
* than happy to supply extra information & work through the details to make
* this happen, if needed.
* --sof / sof@opera.com
*
*/
class OperaCompoundAssignFix extends AbstractPostOrderCallback
implements CompilerPass, ScopedCallback {
private AbstractCompiler compiler;
private final Deque<VariableNameGenerator> names;
@Override
public void enterScope(NodeTraversal t) {
names.push(new VariableNameGenerator(t.getScope()));
}
@Override
public void exitScope(NodeTraversal t) {
names.pop();
}
OperaCompoundAssignFix(AbstractCompiler compiler) {
this.compiler = compiler;
names = Lists.newLinkedList();
}
@Override
public void process(Node externs, Node root) {
List<Node> code = Lists.newArrayList(externs, root);
NodeTraversal.traverseRoots(compiler, code, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!NodeUtil.isName(n)) {
return;
}
if (!NodeUtil.isGet(parent)) {
return;
}
boolean nested = false;
boolean reassign = false;
Node lastAssign = null;
Node prevParent = n;
while (!(NodeUtil.isExpressionNode(parent) ||
NodeUtil.isStatementBlock(parent))) {
if (NodeUtil.isAssign(parent) &&
NodeUtil.isName(parent.getFirstChild()) &&
parent.getFirstChild().getString().equals(n.getString()) &&
nested) {
reassign = true;
break;
} else if (NodeUtil.isAssignmentOp(parent) &&
parent.getLastChild() == prevParent) {
if (lastAssign == null) {
nested = true;
}
lastAssign = parent;
}
prevParent = parent;
parent = parent.getParent();
}
if (!(reassign && nested)) {
return;
}
applyWorkAround(parent, t);
}
private void applyWorkAround(Node assign, NodeTraversal t) {
//System.out.println("applyWorkAround: " + assign.toStringTree());
Preconditions.checkArgument(NodeUtil.isAssign(assign));
Node parent = assign.getParent();
Node comma = new Node(Token.COMMA);
comma.copyInformationFrom(assign);
parent.replaceChild(assign, comma);
String newName = names.peek().getNextNewName();
Node newAssign = new Node(Token.ASSIGN,
Node.newString(Token.NAME, newName));
newAssign.copyInformationFromForTree(assign);
newAssign.addChildToBack(assign.getLastChild().detachFromParent());
comma.addChildrenToBack(newAssign);
assign.addChildrenToBack(
Node.newString(Token.NAME, newName).copyInformationFrom(assign));
comma.addChildrenToBack(assign);
Node root = t.getScopeRoot();
Node var = new Node(Token.VAR, Node.newString(Token.NAME, newName));
var.copyInformationFromForTree(assign);
if (NodeUtil.isStatementBlock(root)) {
root = compiler.getNodeForCodeInsertion(t.getModule());
root.addChildrenToFront(var);
} else {
root.getLastChild().addChildrenToFront(var);
}
compiler.reportCodeChange();
}
}