Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd2f6801

Browse files
fix: Ensure correct code path for && followed by ?? (#17618)
* fix: Ensure correct code path for && followed by ??fixes#13614* Remove .only* Update lib/linter/code-path-analysis/code-path-state.jsCo-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>* Update tests/lib/linter/code-path-analysis/code-path-analyzer.jsCo-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>* Update variable names in comments---------Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
1 parent2665552 commitd2f6801

File tree

4 files changed

+130
-59
lines changed

4 files changed

+130
-59
lines changed

‎lib/linter/code-path-analysis/code-path-state.js‎

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class ChainContext {
121121
* true go one way and if false go the other (tracked by `trueForkContext` and
122122
* `falseForkContext`). The `??` operator doesn't operate on true/false because
123123
* the left expression is evaluated to be nullish or not, so only if nullish do
124-
* we fork to the right expression (tracked by `qqForkcontext`).
124+
* we fork to the right expression (tracked by `nullishForkContext`).
125125
*/
126126
classChoiceContext{
127127

@@ -170,14 +170,14 @@ class ChoiceContext {
170170
this.falseForkContext=ForkContext.newEmpty(forkContext);
171171

172172
/**
173-
* The fork context for theright side of the `??` path of the choice.
173+
* The fork context forwhenthechoice result is `null` or `undefined`.
174174
*@type {ForkContext}
175175
*/
176-
this.qqForkContext=ForkContext.newEmpty(forkContext);
176+
this.nullishForkContext=ForkContext.newEmpty(forkContext);
177177

178178
/**
179179
* Indicates if any of `trueForkContext`, `falseForkContext`, or
180-
* `qqForkContext` have been updated with segments from a child context.
180+
* `nullishForkContext` have been updated with segments from a child context.
181181
*@type {boolean}
182182
*/
183183
this.processed=false;
@@ -849,7 +849,7 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
849849
if(!choiceContext.processed){
850850
choiceContext.trueForkContext.add(head);
851851
choiceContext.falseForkContext.add(head);
852-
choiceContext.qqForkContext.add(head);
852+
choiceContext.nullishForkContext.add(head);
853853
}
854854

855855
/*
@@ -1095,31 +1095,31 @@ class CodePathState {
10951095

10961096
/**
10971097
* Pops the last choice context and finalizes it.
1098+
* This is called upon leaving a node that represents a choice.
10981099
*@throws {Error} (Unreachable.)
10991100
*@returns {ChoiceContext} The popped context.
11001101
*/
11011102
popChoiceContext(){
1102-
constcontext=this.choiceContext;
1103-
1104-
this.choiceContext=context.upper;
1105-
1103+
constpoppedChoiceContext=this.choiceContext;
11061104
constforkContext=this.forkContext;
1107-
constheadSegments=forkContext.head;
1105+
consthead=forkContext.head;
11081106

1109-
switch(context.kind){
1107+
this.choiceContext=poppedChoiceContext.upper;
1108+
1109+
switch(poppedChoiceContext.kind){
11101110
case"&&":
11111111
case"||":
11121112
case"??":
11131113

11141114
/*
1115-
* The `headSegments` are the path of the right-hand operand.
1115+
* The `head` are the path of the right-hand operand.
11161116
* If we haven't previously added segments from child contexts,
11171117
* then we add these segments to all possible forks.
11181118
*/
1119-
if(!context.processed){
1120-
context.trueForkContext.add(headSegments);
1121-
context.falseForkContext.add(headSegments);
1122-
context.qqForkContext.add(headSegments);
1119+
if(!poppedChoiceContext.processed){
1120+
poppedChoiceContext.trueForkContext.add(head);
1121+
poppedChoiceContext.falseForkContext.add(head);
1122+
poppedChoiceContext.nullishForkContext.add(head);
11231123
}
11241124

11251125
/*
@@ -1128,38 +1128,38 @@ class CodePathState {
11281128
* then we take the segments for this context and move them up
11291129
* to the parent context.
11301130
*/
1131-
if(context.isForkingAsResult){
1131+
if(poppedChoiceContext.isForkingAsResult){
11321132
constparentContext=this.choiceContext;
11331133

1134-
parentContext.trueForkContext.addAll(context.trueForkContext);
1135-
parentContext.falseForkContext.addAll(context.falseForkContext);
1136-
parentContext.qqForkContext.addAll(context.qqForkContext);
1134+
parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext);
1135+
parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext);
1136+
parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext);
11371137
parentContext.processed=true;
11381138

11391139
// Exit early so we don't collapse all paths into one.
1140-
returncontext;
1140+
returnpoppedChoiceContext;
11411141
}
11421142

11431143
break;
11441144

11451145
case"test":
1146-
if(!context.processed){
1146+
if(!poppedChoiceContext.processed){
11471147

11481148
/*
11491149
* The head segments are the path of the `if` block here.
11501150
* Updates the `true` path with the end of the `if` block.
11511151
*/
1152-
context.trueForkContext.clear();
1153-
context.trueForkContext.add(headSegments);
1152+
poppedChoiceContext.trueForkContext.clear();
1153+
poppedChoiceContext.trueForkContext.add(head);
11541154
}else{
11551155

11561156
/*
11571157
* The head segments are the path of the `else` block here.
11581158
* Updates the `false` path with the end of the `else`
11591159
* block.
11601160
*/
1161-
context.falseForkContext.clear();
1162-
context.falseForkContext.add(headSegments);
1161+
poppedChoiceContext.falseForkContext.clear();
1162+
poppedChoiceContext.falseForkContext.add(head);
11631163
}
11641164

11651165
break;
@@ -1170,7 +1170,7 @@ class CodePathState {
11701170
* Loops are addressed in `popLoopContext()` so just return
11711171
* the context without modification.
11721172
*/
1173-
returncontext;
1173+
returnpoppedChoiceContext;
11741174

11751175
/* c8 ignore next */
11761176
default:
@@ -1180,71 +1180,116 @@ class CodePathState {
11801180
/*
11811181
* Merge the true path with the false path to create a single path.
11821182
*/
1183-
constcombinedForkContext=context.trueForkContext;
1183+
constcombinedForkContext=poppedChoiceContext.trueForkContext;
11841184

1185-
combinedForkContext.addAll(context.falseForkContext);
1185+
combinedForkContext.addAll(poppedChoiceContext.falseForkContext);
11861186
forkContext.replaceHead(combinedForkContext.makeNext(0,-1));
11871187

1188-
returncontext;
1188+
returnpoppedChoiceContext;
11891189
}
11901190

11911191
/**
1192-
*Makes a code path segmentof the right-hand operand of a logical
1192+
*Creates a code path segmentto represent right-hand operand of a logical
11931193
* expression.
1194+
* This is called in the preprocessing phase when entering a node.
11941195
*@throws {Error} (Unreachable.)
11951196
*@returns {void}
11961197
*/
11971198
makeLogicalRight(){
1198-
constcontext=this.choiceContext;
1199+
constcurrentChoiceContext=this.choiceContext;
11991200
constforkContext=this.forkContext;
12001201

1201-
if(context.processed){
1202+
if(currentChoiceContext.processed){
12021203

12031204
/*
1204-
* This got segments already from the child choice context.
1205-
* Creates the next path from own true/false fork context.
1205+
* This context was already assigned segments from a child
1206+
* choice context. In this case, we are concerned only about
1207+
* the path that does not short-circuit and so ends up on the
1208+
* right-hand operand of the logical expression.
12061209
*/
12071210
letprevForkContext;
12081211

1209-
switch(context.kind){
1212+
switch(currentChoiceContext.kind){
12101213
case"&&":// if true then go to the right-hand side.
1211-
prevForkContext=context.trueForkContext;
1214+
prevForkContext=currentChoiceContext.trueForkContext;
12121215
break;
12131216
case"||":// if false then go to the right-hand side.
1214-
prevForkContext=context.falseForkContext;
1217+
prevForkContext=currentChoiceContext.falseForkContext;
12151218
break;
1216-
case"??":// Both true/false can short-circuit, so needs the third path to go to the right-hand side. That'sqqForkContext.
1217-
prevForkContext=context.qqForkContext;
1219+
case"??":// Both true/false can short-circuit, so needs the third path to go to the right-hand side. That'snullishForkContext.
1220+
prevForkContext=currentChoiceContext.nullishForkContext;
12181221
break;
12191222
default:
12201223
thrownewError("unreachable");
12211224
}
12221225

1226+
/*
1227+
* Create the segment for the right-hand operand of the logical expression
1228+
* and adjust the fork context pointer to point there. The right-hand segment
1229+
* is added at the end of all segments in `prevForkContext`.
1230+
*/
12231231
forkContext.replaceHead(prevForkContext.makeNext(0,-1));
1232+
1233+
/*
1234+
* We no longer need this list of segments.
1235+
*
1236+
* Reset `processed` because we've removed the segments from the child
1237+
* choice context. This allows `popChoiceContext()` to continue adding
1238+
* segments later.
1239+
*/
12241240
prevForkContext.clear();
1225-
context.processed=false;
1241+
currentChoiceContext.processed=false;
1242+
12261243
}else{
12271244

12281245
/*
1229-
* This did not get segments from the child choice context.
1230-
* So addresses the head segments.
1231-
* The head segments are the path of the left-hand operand.
1246+
* This choice context was not assigned segments from a child
1247+
* choice context, which means that it's a terminal logical
1248+
* expression.
1249+
*
1250+
* `head` is the segments for the left-hand operand of the
1251+
* logical expression.
1252+
*
1253+
* Each of the fork contexts below are empty at this point. We choose
1254+
* the path(s) that will short-circuit and add the segment for the
1255+
* left-hand operand to it. Ultimately, this will be the only segment
1256+
* in that path due to the short-circuting, so we are just seeding
1257+
* these paths to start.
12321258
*/
1233-
switch(context.kind){
1234-
case"&&":// the false path can short-circuit.
1235-
context.falseForkContext.add(forkContext.head);
1259+
switch(currentChoiceContext.kind){
1260+
case"&&":
1261+
1262+
/*
1263+
* In most contexts, when a && expression evaluates to false,
1264+
* it short circuits, so we need to account for that by setting
1265+
* the `falseForkContext` to the left operand.
1266+
*
1267+
* When a && expression is the left-hand operand for a ??
1268+
* expression, such as `(a && b) ?? c`, a nullish value will
1269+
* also short-circuit in a different way than a false value,
1270+
* so we also set the `nullishForkContext` to the left operand.
1271+
* This path is only used with a ?? expression and is thrown
1272+
* away for any other type of logical expression, so it's safe
1273+
* to always add.
1274+
*/
1275+
currentChoiceContext.falseForkContext.add(forkContext.head);
1276+
currentChoiceContext.nullishForkContext.add(forkContext.head);
12361277
break;
12371278
case"||":// the true path can short-circuit.
1238-
context.trueForkContext.add(forkContext.head);
1279+
currentChoiceContext.trueForkContext.add(forkContext.head);
12391280
break;
12401281
case"??":// both can short-circuit.
1241-
context.trueForkContext.add(forkContext.head);
1242-
context.falseForkContext.add(forkContext.head);
1282+
currentChoiceContext.trueForkContext.add(forkContext.head);
1283+
currentChoiceContext.falseForkContext.add(forkContext.head);
12431284
break;
12441285
default:
12451286
thrownewError("unreachable");
12461287
}
12471288

1289+
/*
1290+
* Create the segment for the right-hand operand of the logical expression
1291+
* and adjust the fork context pointer to point there.
1292+
*/
12481293
forkContext.replaceHead(forkContext.makeNext(-1,-1));
12491294
}
12501295
}
@@ -1265,7 +1310,7 @@ class CodePathState {
12651310
if(!context.processed){
12661311
context.trueForkContext.add(forkContext.head);
12671312
context.falseForkContext.add(forkContext.head);
1268-
context.qqForkContext.add(forkContext.head);
1313+
context.nullishForkContext.add(forkContext.head);
12691314
}
12701315

12711316
context.processed=false;

‎tests/fixtures/code-path-analysis/assignment--nested-and-3.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/*expected
22
initial->s1_1->s1_2->s1_3->s1_4;
3-
s1_1->s1_4;
4-
s1_2->s1_4->final;
3+
s1_1->s1_3;
4+
s1_2->s1_4;
5+
s1_1->s1_4->final;
56
*/
67
(a&&=b)??c;
78

@@ -15,7 +16,8 @@ digraph {
1516
s1_3[label="Identifier (c)"];
1617
s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
1718
initial->s1_1->s1_2->s1_3->s1_4;
18-
s1_1->s1_4;
19-
s1_2->s1_4->final;
19+
s1_1->s1_3;
20+
s1_2->s1_4;
21+
s1_1->s1_4->final;
2022
}
2123
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*expected
2+
initial->s1_1->s1_2->s1_3->s1_4;
3+
s1_1->s1_3;
4+
s1_2->s1_4;
5+
s1_1->s1_4->final;
6+
*/
7+
(a&&b)??c;
8+
9+
/*DOT
10+
digraph {
11+
node[shape=box,style="rounded,filled",fillcolor=white];
12+
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
13+
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
14+
s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (a)"];
15+
s1_2[label="Identifier (b)\nLogicalExpression:exit"];
16+
s1_3[label="Identifier (c)"];
17+
s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
18+
initial->s1_1->s1_2->s1_3->s1_4;
19+
s1_1->s1_3;
20+
s1_2->s1_4;
21+
s1_1->s1_4->final;
22+
}*/

‎tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/*expected
22
initial->s1_1->s1_2->s1_3->s1_4->s1_6;
3-
s1_1->s1_5->s1_6;
3+
s1_1->s1_3;
44
s1_2->s1_4;
5-
s1_3->s1_5;
5+
s1_3->s1_5->s1_6;
6+
s1_1->s1_5;
67
s1_2->s1_5;
78
s1_6->final;
89
*/
@@ -24,9 +25,10 @@ digraph {
2425
s1_6[label="IfStatement:exit\nProgram:exit"];
2526
s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
2627
initial->s1_1->s1_2->s1_3->s1_4->s1_6;
27-
s1_1->s1_5->s1_6;
28+
s1_1->s1_3;
2829
s1_2->s1_4;
29-
s1_3->s1_5;
30+
s1_3->s1_5->s1_6;
31+
s1_1->s1_5;
3032
s1_2->s1_5;
3133
s1_6->final;
3234
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp