/*
* Copyright 2010 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.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
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;
/**
* Unit tests for PeepholeOptimizationsPass.
*
*/
public final class PeepholeOptimizationsPassTest extends CompilerTestCase {
private ImmutableList<AbstractPeepholeOptimization> currentPeepholePasses;
@Override
protected CompilerPass getProcessor(final Compiler compiler) {
return new PeepholeOptimizationsPass(
compiler, currentPeepholePasses.toArray(new AbstractPeepholeOptimization[0]));
}
@Override
protected int getNumRepetitions() {
// Our tests do not require multiple passes to reach a fixed-point.
return 1;
}
/**
* PeepholeOptimizationsPass should handle the case when no peephole
* optimizations are turned on.
*/
public void testEmptyPass() {
currentPeepholePasses = ImmutableList.of();
testSame("var x; var y;");
}
public void testOptimizationOrder() {
/*
* We need to make sure that: 1) We are only traversing the AST once 2) For
* each node, we visit the optimizations in the client-supplied order
*
* To test this, we create two fake optimizations that each make an entry in
* the visitationLog when they are passed a name node to optimize.
*
* Each entry is of the form nameX where 'name' is the name of the name node
* visited and X is the identity of the optimization (1 or 2 in this case).
* After the pass is run, we verify the correct ordering by querying the
* log.
*
* Using a log, rather than, say, transforming nodes, allows us to ensure
* not only that we are visiting each node but that our visits occur in the
* right order (i.e. we need to make sure we're not traversing the entire
* AST for the first optimization and then a second time for the second).
*/
final List<String> visitationLog = new ArrayList<>();
AbstractPeepholeOptimization note1Applied =
new AbstractPeepholeOptimization() {
@Override
public Node optimizeSubtree(Node node) {
if (node.isName()) {
visitationLog.add(node.getString() + "1");
}
return node;
}
};
AbstractPeepholeOptimization note2Applied =
new AbstractPeepholeOptimization() {
@Override
public Node optimizeSubtree(Node node) {
if (node.isName()) {
visitationLog.add(node.getString() + "2");
}
return node;
}
};
currentPeepholePasses =
ImmutableList.of(note1Applied, note2Applied);
testSame("var x; var y");
/*
* We expect the optimization order to be: "x" visited by optimization1 "x"
* visited by optimization2 "y" visited by optimization1 "y" visited by
* optimization2
*/
assertThat(visitationLog)
.containsExactly("x1", "x2", "y1", "y2").inOrder();
}
/**
* A peephole optimization that, given a subtree consisting of a VAR node,
* removes children of that node named "x".
*/
private static class RemoveNodesNamedXUnderVarOptimization
extends AbstractPeepholeOptimization {
@Override
public Node optimizeSubtree(Node node) {
if (node.isVar()) {
Set<Node> nodesToRemove = new HashSet<>();
for (Node child : node.children()) {
if ("x".equals(child.getString())) {
nodesToRemove.add(child);
}
}
for (Node childToRemove : nodesToRemove) {
node.removeChild(childToRemove);
reportCodeChange();
}
}
return node;
}
}
/**
* A peephole optimization that, given a subtree consisting of a name node
* named "x" removes that node.
*/
private static class RemoveNodesNamedXOptimization
extends AbstractPeepholeOptimization {
@Override
public Node optimizeSubtree(Node node) {
if (node.isName() && "x".equals(node.getString())) {
node.getParent().removeChild(node);
reportCodeChange();
return null;
}
return node;
}
}
/**
* A peephole optimization that, given a subtree consisting of a name node
* named "x" whose parent is a VAR node, removes the parent VAR node.
*/
private static class RemoveParentVarsForNodesNamedX
extends AbstractPeepholeOptimization {
@Override
public Node optimizeSubtree(Node node) {
if (node.isName() && "x".equals(node.getString())) {
Node parent = node.getParent();
if (parent.isVar()) {
parent.getParent().removeChild(parent);
reportCodeChange();
return null;
}
}
return node;
}
}
/**
* A peephole optimization that, given a subtree consisting of a name node
* named "y", replaces it with a name node named "x";
*/
private static class RenameYToX extends AbstractPeepholeOptimization {
@Override
public Node optimizeSubtree(Node node) {
if (node.isName() && "y".equals(node.getString())) {
Node replacement = Node.newString(Token.NAME, "x");
node.replaceWith(replacement);
reportCodeChange();
return replacement;
}
return node;
}
}
public void testOptimizationRemovingSubtreeChild() {
currentPeepholePasses = ImmutableList.<AbstractPeepholeOptimization>of(new
RemoveNodesNamedXUnderVarOptimization());
test("var x,y;", "var y;");
test("var y,x;", "var y;");
test("var x,y,x;", "var y;");
}
public void testOptimizationRemovingSubtree() {
currentPeepholePasses = ImmutableList.<AbstractPeepholeOptimization>of(new
RemoveNodesNamedXOptimization());
test("var x,y;", "var y;");
test("var y,x;", "var y;");
test("var x,y,x;", "var y;");
}
public void testOptimizationRemovingSubtreeParent() {
currentPeepholePasses = ImmutableList.<AbstractPeepholeOptimization>of(new
RemoveParentVarsForNodesNamedX());
test("var x; var y", "var y");
}
/**
* Test the case where the first peephole optimization removes a node and the
* second wants to remove (the now nonexistent) parent of that node.
*/
public void testOptimizationsRemoveParentAfterRemoveChild() {
currentPeepholePasses = ImmutableList.of(
new RemoveNodesNamedXOptimization(),
new RemoveParentVarsForNodesNamedX());
test("var x,y; var z;", "var y; var z;");
}
public void testOptimizationReplacingNode() {
currentPeepholePasses = ImmutableList.of(
new RenameYToX(),
new RemoveParentVarsForNodesNamedX());
test("var y; var z;", "var z;");
}
}