Skip to content

Commit 392f9ff

Browse files
brad4dcopybara-github
authored andcommitted
Improve NodeUtil support for optional chains
* Rename some methods to clarify the difference between a chain segment and the entire chain. * Add a method to detect the end of an entire optional chain. PiperOrigin-RevId: 321683430
1 parent bef0345 commit 392f9ff

File tree

4 files changed

+41
-81
lines changed

4 files changed

+41
-81
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ private static Node findNonconditionalParent(Node subExpression, Node expression
337337
// result is currently `x?.y.z(subExpression)`, but we want it to be the full sub-chain
338338
// containing subExpression
339339
// `x?.y.z(subExpression).p.q`
340-
result = NodeUtil.getEndOfOptChainSegment(result);
340+
result = NodeUtil.getEndOfOptChain(result);
341341
}
342342

343343
return result;
@@ -456,7 +456,7 @@ private void extractOptionalChain(Node optChainNode, Node injectionPoint) {
456456
checkState(NodeUtil.isOptChainNode(optChainNode), optChainNode);
457457

458458
// find the start of the chain & convert it to non-optional
459-
final Node optChainStart = NodeUtil.getStartOfOptChainSegment(optChainNode);
459+
final Node optChainStart = NodeUtil.getStartOfOptChain(optChainNode);
460460
optionalToNonOptionalChain(optChainStart);
461461

462462
// Identify or create the statement that will need to go into the if-statement body

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

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,79 +1950,49 @@ public static boolean isOptChainNode(Node n) {
19501950
* @param n A node in an optional chain
19511951
* @return The start of the optional chain that `n` is part of.
19521952
*/
1953-
static Node getStartOfOptChainSegment(Node n) {
1953+
static Node getStartOfOptChain(Node n) {
19541954
checkState(NodeUtil.isOptChainNode(n), n);
19551955
if (n.isOptionalChainStart()) {
19561956
return n;
19571957
}
1958-
return getStartOfOptChainSegment(n.getFirstChild());
1958+
return getStartOfOptChain(n.getFirstChild());
19591959
}
19601960

19611961
/**
1962-
* Find the end of an optional chain segment.
1963-
*
1964-
* <p>Each `?.` ends one segment and starts another.
1962+
* Find the end of the optional chain.
19651963
*
19661964
* <p>Examples
19671965
*
19681966
* <pre>
1969-
* a?.b.c.d // end is the node with children `a?.b.c` and `d`
1970-
* a?.b.c?.d // given a?.b end is the node with children `a?.b` and `c`
1971-
* a?.b.c?.d // given a?.b.c end is the node with children `a?.b.c` and `d`
1972-
* (a?.b.c).d // given a?.b end is the node with children `a?.b` and `c`
1967+
* a?.b.c.d // end is b.c
1968+
* a?.b.c?.d // given a?.b end is b.c
1969+
* (a?.b.c).d // given a?.b end is b.c
19731970
* </pre>
19741971
*
19751972
* @param n A node in an optional chain
19761973
* @return The end of the optional chain that `n` is part of.
19771974
*/
1978-
static Node getEndOfOptChainSegment(Node n) {
1975+
static Node getEndOfOptChain(Node n) {
19791976
checkState(NodeUtil.isOptChainNode(n), n);
1980-
if (isEndOfOptChainSegment(n)) {
1977+
if (isEndOfOptChain(n)) {
19811978
return n;
19821979
} else {
1983-
return getEndOfOptChainSegment(n.getParent());
1980+
return getEndOfOptChain(n.getParent());
19841981
}
19851982
}
19861983

1987-
/**
1988-
* Is this node the final node of a full optional chain?
1989-
*
1990-
* <p>e.g. for `a?.b.c?.d` this method returns true only for the Node with children `a?.b.c` and
1991-
* `d`. That node is the end of the whole chain, and also represents the whole chain in the AST.
1992-
*/
1993-
static boolean isEndOfFullOptChain(Node n) {
1984+
static boolean isEndOfOptChain(Node n) {
19941985
if (NodeUtil.isOptChainNode(n)) {
19951986
Node parent = n.getParent();
1996-
// the chain continues if this is the first child of another optional chain node
1997-
return !(NodeUtil.isOptChainNode(parent) && n.isFirstChildOf(parent));
1987+
return parent == null
1988+
|| !NodeUtil.isOptChainNode(parent)
1989+
|| parent.isOptionalChainStart()
1990+
// if an optional chain node `n` is also a call's arg or an index into a GETELEM, it will
1991+
// be the end of its chain
1992+
// e.g. In `a?.(x?.y.z)`or `a?.[x?.y.z]`, the `x?.y.z` is the end node
1993+
|| !n.isFirstChildOf(parent);
19981994
} else {
1999-
// not even an optional chain node
2000-
return false;
2001-
}
2002-
}
2003-
2004-
/**
2005-
* Is this node the end of an optional chain segment?
2006-
*
2007-
* <p>Each `?.` begins a new segment and ends the previous one, if any. The end of the whole chain
2008-
* is also the end of its final segment.
2009-
*/
2010-
static boolean isEndOfOptChainSegment(Node n) {
2011-
if (!NodeUtil.isOptChainNode(n)) {
20121995
return false;
2013-
} else {
2014-
Node parent = n.getParent();
2015-
// Check for null so this method will work for a disconnected Node.
2016-
if (parent != null && n.isFirstChildOf(parent) && NodeUtil.isOptChainNode(parent)) {
2017-
// The parent is a continuation of this optional chain.
2018-
// If it starts a new segment, then this node is the end of a segment
2019-
return parent.isOptionalChainStart();
2020-
} else {
2021-
// The parent doesn't continue this node's optional chain, though it might be part of a
2022-
// different one.
2023-
// e.g. in `a?.(x?.y.z)` the parent of `x?.y.z` is part of a different optional chain.
2024-
return true;
2025-
}
20261996
}
20271997
}
20281998

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,9 +2336,9 @@ private static class OptChainInfo {
23362336
private FlowScope traverseOptChain(Node n, FlowScope scope) {
23372337
checkArgument(NodeUtil.isOptChainNode(n));
23382338

2339-
if (NodeUtil.isEndOfOptChainSegment(n)) {
2339+
if (NodeUtil.isEndOfOptChain(n)) {
23402340
// Create new optional chain tracking object and push it onto the stack.
2341-
final Node startOfChain = NodeUtil.getStartOfOptChainSegment(n);
2341+
final Node startOfChain = NodeUtil.getStartOfOptChain(n);
23422342
OptChainInfo optChainInfo = new OptChainInfo(n, startOfChain);
23432343
optChainArrayDeque.addFirst(optChainInfo);
23442344
}
@@ -2370,10 +2370,10 @@ private FlowScope traverseOptChain(Node n, FlowScope scope) {
23702370
n.setJSType(unknownType);
23712371
}
23722372

2373-
if (NodeUtil.isEndOfOptChainSegment(n)) {
2373+
if (NodeUtil.isEndOfOptChain(n)) {
23742374
// Use the startNode's type to selectively join the executed scope with the unexecuted scope,
23752375
// and update the type assigned to `n` in `setXAfterChildrenTraversed()`
2376-
final Node startOfChain = NodeUtil.getStartOfOptChainSegment(n);
2376+
final Node startOfChain = NodeUtil.getStartOfOptChain(n);
23772377

23782378
// Pop the stack to obtain the current chain.
23792379
OptChainInfo currentChain = optChainArrayDeque.removeFirst();

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

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3729,7 +3729,7 @@ public void isStartOfChain() {
37293729
// `expr?.prop`
37303730
Node optChainGet = IR.startOptChainGetprop(IR.name("expr"), IR.string("prop"));
37313731

3732-
assertThat(NodeUtil.getStartOfOptChainSegment(optChainGet)).isEqualTo(optChainGet);
3732+
assertThat(NodeUtil.getStartOfOptChain(optChainGet)).isEqualTo(optChainGet);
37333733
}
37343734

37353735
@Test
@@ -3738,7 +3738,7 @@ public void shortChain() {
37383738
Node innerGetProp = IR.startOptChainGetprop(IR.name("expr"), IR.string("pro1"));
37393739
Node outterGetProp = IR.continueOptChainGetprop(innerGetProp, IR.string("prop2"));
37403740

3741-
assertThat(NodeUtil.getStartOfOptChainSegment(outterGetProp)).isEqualTo(innerGetProp);
3741+
assertThat(NodeUtil.getStartOfOptChain(outterGetProp)).isEqualTo(innerGetProp);
37423742
}
37433743

37443744
@Test
@@ -3750,8 +3750,8 @@ public void mixedChain() {
37503750
Node optCall = IR.continueOptChainCall(optGetProp);
37513751
Node optGetElem = IR.continueOptChainGetelem(optCall, IR.name("prop3"));
37523752

3753-
assertThat(NodeUtil.getStartOfOptChainSegment(optGetElem)).isEqualTo(optGetProp);
3754-
assertThat(NodeUtil.getStartOfOptChainSegment(optCall)).isEqualTo(optGetProp);
3753+
assertThat(NodeUtil.getStartOfOptChain(optGetElem)).isEqualTo(optGetProp);
3754+
assertThat(NodeUtil.getStartOfOptChain(optCall)).isEqualTo(optGetProp);
37553755
}
37563756
}
37573757

@@ -3763,7 +3763,7 @@ public void isEndOfChain() {
37633763
// `expr?.prop`
37643764
Node optChainGet = IR.startOptChainGetprop(IR.name("expr"), IR.string("prop"));
37653765

3766-
assertThat(NodeUtil.getEndOfOptChainSegment(optChainGet)).isEqualTo(optChainGet);
3766+
assertThat(NodeUtil.getEndOfOptChain(optChainGet)).isEqualTo(optChainGet);
37673767
}
37683768

37693769
@Test
@@ -3775,32 +3775,27 @@ public void isEndOfChain_call_innerChain() {
37753775
Node innerOptChain = optChainCall.getLastChild(); // `x?.y`
37763776
assertThat(innerOptChain.isOptChainGetProp()).isTrue();
37773777
assertThat(innerOptChain.isOptionalChainStart()).isTrue();
3778-
assertThat(NodeUtil.isEndOfOptChainSegment(innerOptChain)).isTrue();
3779-
assertThat(NodeUtil.isEndOfFullOptChain(innerOptChain)).isTrue();
3778+
assertThat(NodeUtil.isEndOfOptChain(innerOptChain)).isTrue();
37803779
}
37813780

37823781
@Test
37833782
public void isEndOfChain_getProp_innerChain() {
37843783
Node optChainGetProp = parseExpr("a?.b.x?.y");
37853784
assertThat(optChainGetProp.isOptChainGetProp()).isTrue();
37863785
assertThat(optChainGetProp.isOptionalChainStart()).isTrue();
3787-
assertThat(NodeUtil.isEndOfOptChainSegment(optChainGetProp)).isTrue();
3788-
assertThat(NodeUtil.isEndOfFullOptChain(optChainGetProp)).isTrue();
3786+
assertThat(NodeUtil.isEndOfOptChain(optChainGetProp)).isTrue();
37893787

3790-
// Check that `a?.b.x` is not the start of the optional chain, but it ends the segment that
3791-
// starts with `a?.b`.
3788+
// Check that `a?.b.x` is not the start of optChain, but it ends the chain `a?.b`.
37923789
Node innerOptChain = optChainGetProp.getFirstChild(); // `a?.b.x`
37933790
assertThat(innerOptChain.isOptChainGetProp()).isTrue();
37943791
assertThat(innerOptChain.isOptionalChainStart()).isFalse();
3795-
assertThat(NodeUtil.isEndOfOptChainSegment(innerOptChain)).isTrue();
3796-
assertThat(NodeUtil.isEndOfFullOptChain(innerOptChain)).isFalse();
3792+
assertThat(NodeUtil.isEndOfOptChain(innerOptChain)).isTrue();
37973793

37983794
// Check that `a?.b` is the start of optChain but not the end
37993795
Node innerMostOptChain = innerOptChain.getFirstChild(); // `a?.b`
38003796
assertThat(innerMostOptChain.isOptChainGetProp()).isTrue();
38013797
assertThat(innerMostOptChain.isOptionalChainStart()).isTrue();
3802-
assertThat(NodeUtil.isEndOfOptChainSegment(innerMostOptChain)).isFalse();
3803-
assertThat(NodeUtil.isEndOfFullOptChain(innerMostOptChain)).isFalse();
3798+
assertThat(NodeUtil.isEndOfOptChain(innerMostOptChain)).isFalse();
38043799
}
38053800

38063801
@Test
@@ -3813,8 +3808,7 @@ public void isEndOfChain_call_innerChain2() {
38133808
Node innerOptChain = optChainCall.getLastChild();
38143809
assertThat(innerOptChain.isOptChainGetProp()).isTrue();
38153810
assertThat(innerOptChain.isOptionalChainStart()).isTrue();
3816-
assertThat(NodeUtil.isEndOfOptChainSegment(innerOptChain)).isTrue();
3817-
assertThat(NodeUtil.isEndOfFullOptChain(innerOptChain)).isTrue();
3811+
assertThat(NodeUtil.isEndOfOptChain(innerOptChain)).isTrue();
38183812
}
38193813

38203814
@Test
@@ -3827,15 +3821,13 @@ public void isEndOfChain_callAndGetProp_innerChain() {
38273821
Node optChainCall = optChainGetProp.getFirstChild();
38283822
assertThat(optChainCall.isOptChainCall()).isTrue();
38293823
assertThat(optChainCall.isOptionalChainStart()).isFalse();
3830-
assertThat(NodeUtil.isEndOfOptChainSegment(optChainCall)).isFalse();
3831-
assertThat(NodeUtil.isEndOfFullOptChain(optChainCall)).isFalse();
3824+
assertThat(NodeUtil.isEndOfOptChain(optChainCall)).isFalse();
38323825

38333826
// Check that `x?.y` is the start and end of the chain
38343827
Node innerOptChain = optChainCall.getLastChild();
38353828
assertThat(innerOptChain.isOptChainGetProp()).isTrue();
38363829
assertThat(innerOptChain.isOptionalChainStart()).isTrue();
3837-
assertThat(NodeUtil.isEndOfOptChainSegment(innerOptChain)).isTrue();
3838-
assertThat(NodeUtil.isEndOfFullOptChain(innerOptChain)).isTrue();
3830+
assertThat(NodeUtil.isEndOfOptChain(innerOptChain)).isTrue();
38393831
}
38403832

38413833
@Test
@@ -3848,15 +3840,13 @@ public void isEndOfChain_callAndGetProp_innerChain_multipleInnerArgs() {
38483840
Node optChainCall = optChainGetProp.getFirstChild();
38493841
assertThat(optChainCall.isOptChainCall()).isTrue();
38503842
assertThat(optChainCall.isOptionalChainStart()).isFalse();
3851-
assertThat(NodeUtil.isEndOfOptChainSegment(optChainCall)).isFalse();
3852-
assertThat(NodeUtil.isEndOfFullOptChain(optChainCall)).isFalse();
3843+
assertThat(NodeUtil.isEndOfOptChain(optChainCall)).isFalse();
38533844

38543845
// Check that `x?.y` is the start and end of its chain
38553846
Node innerOptChain = optChainCall.getSecondChild();
38563847
assertThat(innerOptChain.isOptChainGetProp()).isTrue();
38573848
assertThat(innerOptChain.isOptionalChainStart()).isTrue();
3858-
assertThat(NodeUtil.isEndOfOptChainSegment(innerOptChain)).isTrue();
3859-
assertThat(NodeUtil.isEndOfFullOptChain(innerOptChain)).isTrue();
3849+
assertThat(NodeUtil.isEndOfOptChain(innerOptChain)).isTrue();
38603850
}
38613851

38623852
@Test
@@ -3865,7 +3855,7 @@ public void shortChain() {
38653855
Node innerGetProp = IR.startOptChainGetprop(IR.name("expr"), IR.string("pro1"));
38663856
Node outerGetProp = IR.continueOptChainGetprop(innerGetProp, IR.string("prop2"));
38673857

3868-
assertThat(NodeUtil.getEndOfOptChainSegment(innerGetProp)).isEqualTo(outerGetProp);
3858+
assertThat(NodeUtil.getEndOfOptChain(innerGetProp)).isEqualTo(outerGetProp);
38693859
}
38703860

38713861
@Test
@@ -3877,7 +3867,7 @@ public void twoChains() {
38773867
Node optCall = IR.continueOptChainCall(optGetProp);
38783868
IR.startOptChainGetelem(optCall, IR.name("prop3"));
38793869

3880-
assertThat(NodeUtil.getEndOfOptChainSegment(startOptGetProp)).isEqualTo(optCall);
3870+
assertThat(NodeUtil.getEndOfOptChain(startOptGetProp)).isEqualTo(optCall);
38813871
}
38823872

38833873
@Test
@@ -3887,7 +3877,7 @@ public void breakingOutOfOptChain() {
38873877
Node optGetProp = IR.continueOptChainGetprop(startOptGetProp, IR.string("prop2"));
38883878
IR.getprop(optGetProp, IR.string("prop3"));
38893879

3890-
assertThat(NodeUtil.getEndOfOptChainSegment(startOptGetProp)).isEqualTo(optGetProp);
3880+
assertThat(NodeUtil.getEndOfOptChain(startOptGetProp)).isEqualTo(optGetProp);
38913881
}
38923882
}
38933883

0 commit comments

Comments
 (0)