Skip to content

Commit

Permalink
Update: support logical assignments in code path analysis (refs #13569)…
Browse files Browse the repository at this point in the history
… (#13612)

* Update: support logical assignment in code path analysis (refs #13569)

* Fix minor formatting issues
  • Loading branch information
mdjermanovic committed Aug 31, 2020
1 parent db7488e commit 58abd93
Show file tree
Hide file tree
Showing 64 changed files with 1,391 additions and 3 deletions.
38 changes: 38 additions & 0 deletions lib/linter/code-path-analysis/code-path-analyzer.js
Expand Up @@ -39,6 +39,17 @@ function isHandledLogicalOperator(operator) {
return operator === "&&" || operator === "||" || operator === "??";
}

/**
* Checks whether the given assignment operator is a logical assignment operator.
* Logical assignments are taken into account for the code path analysis
* because of their short-circuiting semantics.
* @param {string} operator The operator found in the AssignmentExpression node
* @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
*/
function isLogicalAssignmentOperator(operator) {
return operator === "&&=" || operator === "||=" || operator === "??=";
}

/**
* Gets the label if the parent node of a given node is a LabeledStatement.
* @param {ASTNode} node A node to get.
Expand Down Expand Up @@ -71,6 +82,9 @@ function isForkingByTrueOrFalse(node) {
case "LogicalExpression":
return isHandledLogicalOperator(parent.operator);

case "AssignmentExpression":
return isLogicalAssignmentOperator(parent.operator);

default:
return false;
}
Expand Down Expand Up @@ -266,6 +280,15 @@ function preprocess(analyzer, node) {
}
break;

case "AssignmentExpression":
if (
parent.right === node &&
isLogicalAssignmentOperator(parent.operator)
) {
state.makeLogicalRight();
}
break;

case "ConditionalExpression":
case "IfStatement":

Expand Down Expand Up @@ -413,6 +436,15 @@ function processCodePathToEnter(analyzer, node) {
}
break;

case "AssignmentExpression":
if (isLogicalAssignmentOperator(node.operator)) {
state.pushChoiceContext(
node.operator.slice(0, -1), // removes `=` from the end
isForkingByTrueOrFalse(node)
);
}
break;

case "ConditionalExpression":
case "IfStatement":
state.pushChoiceContext("test", false);
Expand Down Expand Up @@ -491,6 +523,12 @@ function processCodePathToExit(analyzer, node) {
}
break;

case "AssignmentExpression":
if (isLogicalAssignmentOperator(node.operator)) {
state.popChoiceContext();
}
break;

case "SwitchStatement":
state.popSwitchContext();
break;
Expand Down
4 changes: 2 additions & 2 deletions lib/linter/code-path-analysis/code-path-state.js
Expand Up @@ -317,7 +317,7 @@ class CodePathState {
//--------------------------------------------------------------------------

/**
* Creates a context for ConditionalExpression, LogicalExpression,
* Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
* IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
*
* LogicalExpressions have cases that it goes different paths between the
Expand All @@ -339,7 +339,7 @@ class CodePathState {
* a -> b -> foo();
* a -> b -> bar();
* @param {string} kind A kind string.
* If the new context is LogicalExpression's, this is `"&&"` or `"||"`.
* If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
* If it's IfStatement's or ConditionalExpression's, this is `"test"`.
* Otherwise, this is `"loop"`.
* @param {boolean} isForkingAsResult A flag that shows that goes different
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--do-while-and.js
@@ -0,0 +1,21 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_2->s1_4;
s1_3->s1_4->final;
*/
do {
foo();
} while (a &&= b);

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nDoWhileStatement:enter"];
s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="DoWhileStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_2->s1_4;
s1_3->s1_4->final;
}
*/
21 changes: 21 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--do-while-or.js
@@ -0,0 +1,21 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_2->s1_2;
s1_3->s1_4->final;
*/
do {
foo();
} while (a ||= b);

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nDoWhileStatement:enter"];
s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="DoWhileStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_2->s1_2;
s1_3->s1_4->final;
}
*/
23 changes: 23 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--do-while-qq.js
@@ -0,0 +1,23 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_2->s1_2;
s1_3->s1_4;
s1_2->s1_4->final;
*/
do {
foo();
} while (a ??= b);

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nDoWhileStatement:enter"];
s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="DoWhileStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_2->s1_2;
s1_3->s1_4;
s1_2->s1_4->final;
}
*/
23 changes: 23 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-and-1.js
@@ -0,0 +1,23 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
s1_3->s1_6->final;
*/
for (init; a &&= b; update) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="Identifier (update)"];
s1_6[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
s1_3->s1_6->final;
}
*/
22 changes: 22 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-and-2.js
@@ -0,0 +1,22 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
s1_3->s1_5->final;
*/
for (init; a &&= b;) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
s1_3->s1_5->final;
}
*/
23 changes: 23 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-or-1.js
@@ -0,0 +1,23 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
s1_3->s1_6->final;
*/
for (init; a ||= b; update) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="Identifier (update)"];
s1_6[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
s1_3->s1_6->final;
}
*/
22 changes: 22 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-or-2.js
@@ -0,0 +1,22 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
s1_3->s1_5->final;
*/
for (init; a ||= b;) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
s1_3->s1_5->final;
}
*/
25 changes: 25 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-qq-1.js
@@ -0,0 +1,25 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
s1_3->s1_6;
s1_2->s1_6->final;
*/
for (init; a ??= b; update) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="Identifier (update)"];
s1_6[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
s1_3->s1_6;
s1_2->s1_6->final;
}
*/
24 changes: 24 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--for-qq-2.js
@@ -0,0 +1,24 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
s1_3->s1_5;
s1_2->s1_5->final;
*/
for (init; a ??= b;) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="ForStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
s1_3->s1_5;
s1_2->s1_5->final;
}
*/
23 changes: 23 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--if-and-1.js
@@ -0,0 +1,23 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4;
s1_1->s1_4;
s1_2->s1_4->final;
*/
if (a &&= b) {
foo();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_4[label="IfStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4;
s1_1->s1_4;
s1_2->s1_4->final;
}
*/
28 changes: 28 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--if-and-2.js
@@ -0,0 +1,28 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_5;
s1_1->s1_4->s1_5;
s1_2->s1_4;
s1_5->final;
*/
if (a &&= b) {
foo();
} else {
bar();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_5[label="IfStatement:exit\nProgram:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
initial->s1_1->s1_2->s1_3->s1_5;
s1_1->s1_4->s1_5;
s1_2->s1_4;
s1_5->final;
}
*/
36 changes: 36 additions & 0 deletions tests/fixtures/code-path-analysis/assignment--if-and-3.js
@@ -0,0 +1,36 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
s1_1->s1_8->s1_9;
s1_2->s1_8;
s1_3->s1_8;
s1_4->s1_6->s1_7;
s1_9->final;
*/
if ((a &&= b) && c) {
d ? foo() : bar();
} else {
baz();
}

/*DOT
digraph {
node[shape=box,style="rounded,filled",fillcolor=white];
initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
s1_1[label="Program:enter\nIfStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
s1_3[label="Identifier (c)\nLogicalExpression:exit"];
s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nConditionalExpression:enter\nIdentifier (d)"];
s1_5[label="CallExpression:enter\nIdentifier (foo)\nCallExpression:exit"];
s1_7[label="ConditionalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_9[label="IfStatement:exit\nProgram:exit"];
s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (baz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s1_6[label="CallExpression:enter\nIdentifier (bar)\nCallExpression:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
s1_1->s1_8->s1_9;
s1_2->s1_8;
s1_3->s1_8;
s1_4->s1_6->s1_7;
s1_9->final;
}
*/

0 comments on commit 58abd93

Please sign in to comment.