Skip to content

Commit 2b29a98

Browse files
lauraharkercopybara-github
authored andcommitted
Refactor some shared code into a new AstManipulations class
A followup will use this code in a fourth pass so the StatementFusion class seems like the wrong home for it. PiperOrigin-RevId: 325844462
1 parent c45b138 commit 2b29a98

File tree

3 files changed

+162
-34
lines changed

3 files changed

+162
-34
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2020 The Closure Compiler Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.javascript.jscomp;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.javascript.rhino.Node;
22+
import com.google.javascript.rhino.Token;
23+
24+
/** Utilities to abstract away certain common AST manipulations */
25+
final class AstManipulations {
26+
27+
// Non-instantiable
28+
private AstManipulations() {}
29+
30+
/**
31+
* Returns a single node equivalent to executing <expr1, expr2>.
32+
*
33+
* <p>Requires that expr1 and expr2 are detached (i.e. have no parent nodes). If exp2 is EMPTY
34+
* this just returns exp1.
35+
*/
36+
static Node fuseExpressions(Node exp1, Node exp2) {
37+
checkArgument(exp1.getParent() == null, "Expected detached node, got %s", exp1);
38+
checkArgument(exp2.getParent() == null, "Expected detached node, got %s", exp2);
39+
if (exp2.isEmpty()) {
40+
return exp1;
41+
}
42+
Node comma = new Node(Token.COMMA, exp1);
43+
comma.useSourceInfoIfMissingFrom(exp2);
44+
45+
// We can just join the new comma expression with another comma but
46+
// lets keep all the comma's in a straight line. That way we can use
47+
// tree comparison.
48+
if (exp2.isComma()) {
49+
Node leftMostChild = exp2;
50+
while (leftMostChild.isComma()) {
51+
leftMostChild = leftMostChild.getFirstChild();
52+
}
53+
Node parent = leftMostChild.getParent();
54+
comma.addChildToBack(leftMostChild.detach());
55+
parent.addChildToFront(comma);
56+
return exp2;
57+
} else {
58+
comma.addChildToBack(exp2);
59+
return comma;
60+
}
61+
}
62+
}

src/com/google/javascript/jscomp/StatementFusion.java

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static com.google.common.base.Preconditions.checkArgument;
2020

2121
import com.google.javascript.rhino.Node;
22-
import com.google.javascript.rhino.Token;
2322

2423
/**
2524
* Tries to fuse all the statements in a block into a one statement by using COMMAs or statements.
@@ -33,7 +32,7 @@
3332
* program, and so it makes sense to prefer fusing statements with semicolons rather than commas.
3433
* This assumption has never been validated on a real program.
3534
*/
36-
class StatementFusion extends AbstractPeepholeOptimization {
35+
final class StatementFusion extends AbstractPeepholeOptimization {
3736

3837
@Override
3938
Node optimizeSubtree(Node n) {
@@ -118,8 +117,7 @@ private static Node fuseIntoOneStatement(Node parent, Node first, Node last) {
118117

119118
Node next = null;
120119
for (Node cur = first.getNext(); cur != last; cur = next) {
121-
commaTree = fuseExpressionIntoExpression(
122-
commaTree, cur.removeFirstChild());
120+
commaTree = AstManipulations.fuseExpressions(commaTree, cur.removeFirstChild());
123121
next = cur.getNext();
124122
parent.removeChild(cur);
125123
}
@@ -162,41 +160,15 @@ private static void fuseExpressionIntoControlFlowStatement(
162160
}
163161
}
164162

165-
// exp1, exp1
166-
static Node fuseExpressionIntoExpression(Node exp1, Node exp2) {
167-
if (exp2.isEmpty()) {
168-
return exp1;
169-
}
170-
Node comma = new Node(Token.COMMA, exp1);
171-
comma.useSourceInfoIfMissingFrom(exp2);
172-
173-
// We can just join the new comma expression with another comma but
174-
// lets keep all the comma's in a straight line. That way we can use
175-
// tree comparison.
176-
if (exp2.isComma()) {
177-
Node leftMostChild = exp2;
178-
while (leftMostChild.isComma()) {
179-
leftMostChild = leftMostChild.getFirstChild();
180-
}
181-
Node parent = leftMostChild.getParent();
182-
comma.addChildToBack(leftMostChild.detach());
183-
parent.addChildToFront(comma);
184-
return exp2;
185-
} else {
186-
comma.addChildToBack(exp2);
187-
return comma;
188-
}
189-
}
190-
191-
protected static void fuseExpressionIntoFirstChild(Node exp, Node stmt) {
163+
private static void fuseExpressionIntoFirstChild(Node exp, Node stmt) {
192164
Node val = stmt.removeFirstChild();
193-
Node comma = fuseExpressionIntoExpression(exp, val);
165+
Node comma = AstManipulations.fuseExpressions(exp, val);
194166
stmt.addChildToFront(comma);
195167
}
196168

197-
protected static void fuseExpressionIntoSecondChild(Node exp, Node stmt) {
169+
private static void fuseExpressionIntoSecondChild(Node exp, Node stmt) {
198170
Node val = stmt.getSecondChild().detach();
199-
Node comma = fuseExpressionIntoExpression(exp, val);
171+
Node comma = AstManipulations.fuseExpressions(exp, val);
200172
stmt.addChildAfter(comma, stmt.getFirstChild());
201173
}
202174
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2020 The Closure Compiler Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.javascript.jscomp;
18+
19+
import static com.google.javascript.rhino.testing.NodeSubject.assertNode;
20+
21+
import com.google.javascript.rhino.IR;
22+
import com.google.javascript.rhino.Node;
23+
import com.google.javascript.rhino.Token;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.junit.runners.JUnit4;
27+
28+
/** Unit tests for {@link AstManipulations} */
29+
@RunWith(JUnit4.class)
30+
public final class AstManipulationsTest {
31+
32+
@Test
33+
public void fuseExpressions_fusesTwoNumbers() {
34+
Node one = IR.number(1);
35+
Node two = IR.number(2);
36+
37+
assertNode(AstManipulations.fuseExpressions(one, two))
38+
.isEqualTo(IR.comma(one.cloneNode(), two.cloneNode()));
39+
}
40+
41+
@Test
42+
public void fuseExpressions_dropsEmptySecondExpression() {
43+
Node one = IR.number(1);
44+
Node empty = IR.empty();
45+
46+
assertNode(AstManipulations.fuseExpressions(one, empty)).isEqualTo(one.cloneNode());
47+
}
48+
49+
@Test
50+
public void fuseExpressions_keepsEmptyFirstExpression() {
51+
Node empty = IR.empty();
52+
Node one = IR.number(1);
53+
54+
// Note: it should be fine to return one instead of this comma
55+
assertNode(AstManipulations.fuseExpressions(empty, one))
56+
.isEqualTo(new Node(Token.COMMA, empty.cloneNode(), one.cloneNode()));
57+
}
58+
59+
@Test
60+
public void fuseExpressions_fusesNumberIntoComma() {
61+
Node zero = IR.number(0);
62+
Node one = IR.number(1);
63+
Node zeroCommaOne = IR.comma(zero, one);
64+
Node two = IR.number(2);
65+
66+
assertNode(AstManipulations.fuseExpressions(zeroCommaOne, two))
67+
.isEqualTo(IR.comma(zeroCommaOne.cloneTree(), two.cloneNode()));
68+
}
69+
70+
@Test
71+
public void fuseExpressions_fusesCommaIntoNumber() {
72+
Node zero = IR.number(0);
73+
Node one = IR.number(1);
74+
Node two = IR.number(2);
75+
Node oneCommaTwo = IR.comma(one, two);
76+
77+
// Utility keeps commas in the left branch of the comma tree, i.e.
78+
// COMMA
79+
// COMMA
80+
// NUMBER 0
81+
// NUMBER 1
82+
// NUMBER 2
83+
// instead of this:
84+
// COMMA
85+
// NUMBER 0
86+
// COMMA
87+
// NUMBER 1
88+
// NUMBER 2
89+
// to make unit-testing passes that use this utility easier. Writing 'expected' JS code
90+
// for the latter tree requires more parentheses.
91+
assertNode(AstManipulations.fuseExpressions(zero, oneCommaTwo))
92+
.isEqualTo(IR.comma(IR.comma(zero.cloneNode(), one.cloneNode()), two.cloneNode()));
93+
}
94+
}

0 commit comments

Comments
 (0)