Skip to content

Commit c84f000

Browse files
brad4dcopybara-github
authored andcommitted
RemoveUnusedCode: support optional chaining
PiperOrigin-RevId: 326108121
1 parent 114fff4 commit c84f000

File tree

7 files changed

+244
-19
lines changed

7 files changed

+244
-19
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,6 +1930,11 @@ public static boolean isNormalOrOptChainGet(Node n) {
19301930
return isNormalGet(n) || isOptChainGet(n);
19311931
}
19321932

1933+
/** Is this a GETPROP or OPTCHAIN_GETPROP? */
1934+
public static boolean isNormalOrOptChainGetProp(Node n) {
1935+
return n.isGetProp() || n.isOptChainGetProp();
1936+
}
1937+
19331938
/** Is this a GETPROP or GETELEM node? */
19341939
public static boolean isNormalGet(Node n) {
19351940
return n.isGetProp() || n.isGetElem();

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

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ private void traverseNode(Node n, Scope scope) {
469469
break;
470470

471471
case CALL:
472+
case OPTCHAIN_CALL:
472473
traverseCall(n, scope);
473474
break;
474475

@@ -546,7 +547,8 @@ private void traverseNode(Node n, Scope scope) {
546547
break;
547548

548549
case GETPROP:
549-
traverseGetProp(n, scope);
550+
case OPTCHAIN_GETPROP:
551+
traverseNormalOrOptChainGetProp(n, scope);
550552
break;
551553

552554
default:
@@ -569,7 +571,17 @@ private void traverseInstanceof(Node instanceofNode, Scope scope) {
569571
}
570572
}
571573

572-
private void traverseGetProp(Node getProp, Scope scope) {
574+
/**
575+
* Traverse `expr.prop` or `expr?.prop`.
576+
*
577+
* <p>Note that this method is called only for RHS nodes. Property references that are being
578+
* assigned to are handled by the logic traversing their parent (e.g. ASSIGN) node.
579+
*
580+
* <p>The primary purpose of this method is to make sure the property reference is correctly
581+
* recorded.
582+
*/
583+
private void traverseNormalOrOptChainGetProp(Node getProp, Scope scope) {
584+
checkState(NodeUtil.isNormalOrOptChainGetProp(getProp), getProp);
573585
Node objectNode = getProp.getFirstChild();
574586
Node propertyNameNode = objectNode.getNext();
575587
String propertyName = propertyNameNode.getString();
@@ -586,7 +598,10 @@ private void traverseGetProp(Node getProp, Scope scope) {
586598
markPropertyNameAsPinned(propertyName);
587599
traverseNode(objectNode, scope);
588600
} else if (objectNode.isThis()) {
601+
// This is probably the declaration of a class field in a constructor.
602+
// /** @private {number} */
589603
// this.propName;
604+
// We don't want to consider this a real usage that should prevent removal.
590605
RemovableBuilder builder = new RemovableBuilder().setIsThisDotPropertyReference(true);
591606
considerForIndependentRemoval(builder.buildUnusedReadReference(getProp, propertyNameNode));
592607
} else if (isDotPrototype(objectNode)) {
@@ -909,7 +924,8 @@ private boolean isAssignmentToPrototype(Node n) {
909924

910925
/** True for `someExpression.prototype`. */
911926
private static boolean isDotPrototype(Node n) {
912-
return n.isGetProp() && n.getLastChild().getString().equals("prototype");
927+
return NodeUtil.isNormalOrOptChainGetProp(n)
928+
&& n.getLastChild().getString().equals("prototype");
913929
}
914930

915931
private void traverseCatch(Node catchNode, Scope scope) {
@@ -1463,8 +1479,8 @@ private void considerForIndependentRemoval(Removable removable) {
14631479

14641480
/** @return Whether or not accessor side-effect are a possibility. */
14651481
private boolean considerForAccessorSideEffects(Node getprop, PropertyAccessKind usage) {
1466-
checkState(getprop.isGetProp(), getprop); // Other node types may make sense in the future.
1467-
1482+
// Other node types may make sense in the future.
1483+
checkState(NodeUtil.isNormalOrOptChainGetProp(getprop), getprop);
14681484
String propName = getprop.getSecondChild().getString();
14691485
PropertyAccessKind recorded = compiler.getAccessorSummary().getKind(propName);
14701486
if ((recorded.hasGetter() && usage.hasGetter() && !assumeGettersArePure)
@@ -2072,12 +2088,12 @@ public String toString() {
20722088

20732089
/** True for `this.propertyName` */
20742090
private static boolean isThisDotProperty(Node n) {
2075-
return n.isGetProp() && n.getFirstChild().isThis();
2091+
return NodeUtil.isNormalOrOptChainGetProp(n) && n.getFirstChild().isThis();
20762092
}
20772093

20782094
/** True for `(something).prototype.propertyName` */
20792095
private static boolean isDotPrototypeDotProperty(Node n) {
2080-
return n.isGetProp() && isDotPrototype(n.getFirstChild());
2096+
return NodeUtil.isNormalOrOptChainGetProp(n) && isDotPrototype(n.getFirstChild());
20812097
}
20822098

20832099
private class IndirectAssign extends Removable {
@@ -2854,7 +2870,7 @@ void considerPossibleReferenceInternal(Node possiblyReferencingNode) {
28542870
// A matching NAME node must be a reference (there's no need to check that the referenced
28552871
// Var is global, since local variables have all been renamed by normalization).
28562872
isRemovable = false;
2857-
} else if (possiblyReferencingNode.isGetProp()) {
2873+
} else if (NodeUtil.isNormalOrOptChainGetProp(possiblyReferencingNode)) {
28582874
// Assume that the owner is possibly the global `this` and skip removal.
28592875
isRemovable = false;
28602876
}
@@ -2877,10 +2893,9 @@ String getName() {
28772893

28782894
@Override
28792895
void considerPossibleReferenceInternal(Node possiblyReferencingNode) {
2880-
if (!possiblyReferencingNode.isGetProp()) {
2881-
return;
2896+
if (NodeUtil.isNormalOrOptChainGetProp(possiblyReferencingNode)) {
2897+
isRemovable = false;
28822898
}
2883-
isRemovable = false;
28842899
}
28852900
}
28862901

@@ -2900,11 +2915,10 @@ String getName() {
29002915

29012916
@Override
29022917
void considerPossibleReferenceInternal(Node possiblyReferencingNode) {
2903-
if (!possiblyReferencingNode.isGetProp()) {
2904-
return;
2918+
if (NodeUtil.isNormalOrOptChainGetProp(possiblyReferencingNode)) {
2919+
// Prototype properties are simply not removable.
2920+
isRemovable = false;
29052921
}
2906-
// Prototype properties are simply not removable.
2907-
isRemovable = false;
29082922
}
29092923
}
29102924

test/com/google/javascript/jscomp/AstChainOptionalizer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,17 @@ public void visit(NodeTraversal t, Node n, Node parent) {
138138
/** Special function qualified names that really should never be called with ?. */
139139
private static final ImmutableList<String> SPECIAL_CALLEE_QNAMES =
140140
ImmutableList.of(
141+
"Array.from",
141142
"Object.defineProperty",
142143
"Object.defineProperties",
144+
"goog.addSingletonGetter",
143145
"goog.inherits",
144-
"goog.reflect.cache");
146+
"goog.mixin",
147+
"goog$mixin",
148+
"goog.reflect.cache",
149+
"goog.reflect.objectProperty",
150+
"Math.random",
151+
"$jscomp.polyfill");
145152

146153
/**
147154
* Is the callee a special function name that we would never expect to be called using optional

test/com/google/javascript/jscomp/RemoveUnusedCodeClassPropertiesTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.javascript.jscomp;
1818

19+
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
1920
import org.junit.Before;
2021
import org.junit.Test;
2122
import org.junit.runner.RunWith;
@@ -96,6 +97,8 @@ protected CompilerPass getProcessor(Compiler compiler) {
9697
@Before
9798
public void setUp() throws Exception {
9899
super.setUp();
100+
// Allow testing of features that aren't fully supported for output yet.
101+
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT_IN);
99102
enableNormalize();
100103
enableGatherExternProperties();
101104
onlyValidateNoNewGettersAndSetters();
@@ -213,6 +216,10 @@ public void testDestructuringRest() {
213216
public void testExprResult() {
214217
test("this.x", "");
215218
test("externFunction().prototype.x", "externFunction()");
219+
// It doesn't make much sense to use optional chaining in these cases, but if you do,
220+
// it shouldn't prevent unused property removal
221+
test("this?.x", "");
222+
test("externFunction()?.prototype.x", "externFunction()");
216223
}
217224

218225
@Test

test/com/google/javascript/jscomp/RemoveUnusedCodeNameAnalyzerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ public void process(Node externs, Node root) {
107107
@Before
108108
public void setUp() throws Exception {
109109
super.setUp();
110-
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2017);
110+
// Allow testing of features that aren't fully supported for output yet.
111+
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT_IN);
111112
enableNormalize();
112113
enableGatherExternProperties();
113114
onlyValidateNoNewGettersAndSetters();

test/com/google/javascript/jscomp/RemoveUnusedCodePrototypePropertiesTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.javascript.jscomp;
1818

19+
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
1920
import com.google.javascript.rhino.Node;
2021
import org.junit.Before;
2122
import org.junit.Test;
@@ -85,6 +86,8 @@ public CompilerOptions getOptions() {
8586
@Before
8687
public void setUp() throws Exception {
8788
super.setUp();
89+
// Allow testing of features that aren't fully supported for output yet.
90+
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT_IN);
8891
enableNormalize();
8992
enableGatherExternProperties();
9093
onlyValidateNoNewGettersAndSetters();
@@ -818,6 +821,39 @@ public void testDestructuringRest() {
818821
"({ ...new Foo().a.b } = 0);"));
819822
}
820823

824+
@Test
825+
public void testOptionalGetPropPreventsRemoval() {
826+
test(
827+
lines(
828+
"class C {",
829+
" constructor() {",
830+
" this.x = 1;",
831+
" }",
832+
" optGetPropRef() {}",
833+
" optCallRef() {}",
834+
" unreferenced() {}",
835+
"}",
836+
"var c = new C;",
837+
"c?.optGetPropRef()",
838+
"c.optCallRef?.()",
839+
// no call to unreferenced()
840+
""),
841+
lines(
842+
"class C {",
843+
" constructor() {",
844+
" this.x = 1;",
845+
" }",
846+
" optGetPropRef() {}", // kept
847+
" optCallRef() {}", // kept
848+
// unreferenced() removed
849+
"}",
850+
"var c = new C;",
851+
"c?.optGetPropRef()",
852+
"c.optCallRef?.()",
853+
// no call to unreferenced()
854+
""));
855+
}
856+
821857
@Test
822858
public void testEs6Class() {
823859
testSame(

0 commit comments

Comments
 (0)