Skip to content

Commit

Permalink
feat: code path analysis for class static blocks (#15282)
Browse files Browse the repository at this point in the history
Class static blocks are implicit functions. Therefore, they should be treated as separate code paths. This adds `class-static-block` code paths. Each `StaticBlock` node will start a new `class-static-block` code path.

Refs #15016
  • Loading branch information
mdjermanovic committed Nov 17, 2021
1 parent 15c1397 commit b3669fd
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/developer-guide/code-path-analysis.md
Expand Up @@ -27,7 +27,7 @@ This has references of both the initial segment and the final segments of a code
`CodePath` has the following properties:

* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
* `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, or `"class-field-initializer"`.
* `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`.
* `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
Expand Down
7 changes: 6 additions & 1 deletion lib/linter/code-path-analysis/code-path-analyzer.js
Expand Up @@ -461,6 +461,10 @@ function processCodePathToEnter(analyzer, node) {
startCodePath("function");
break;

case "StaticBlock":
startCodePath("class-static-block");
break;

case "ChainExpression":
state.pushChainContext();
break;
Expand Down Expand Up @@ -706,7 +710,8 @@ function postprocess(analyzer, node) {
case "Program":
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression": {
case "ArrowFunctionExpression":
case "StaticBlock": {
endCodePath();
break;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/linter/code-path-analysis/code-path.js
Expand Up @@ -40,7 +40,7 @@ class CodePath {

/**
* The reason that this code path was started. May be "program",
* "function", or "class-field-initializer".
* "function", "class-field-initializer", or "class-static-block".
* @type {string}
*/
this.origin = origin;
Expand Down
@@ -0,0 +1,65 @@
/*expected
initial->s2_1->s2_2->s2_3;
s2_1->s2_3->final;
*/
/*expected
initial->s3_1->s3_2->s3_3->s3_4;
s3_1->s3_4;
s3_2->s3_4->final;
*/
/*expected
initial->s4_1->s4_2->s4_3->s4_4->s4_5;
s4_1->s4_5;
s4_2->s4_5;
s4_3->s4_5->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { bar = a || b; static { x || y || z } baz = p || q || r || s; }

/*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];
s2_1[label="LogicalExpression:enter\nIdentifier (a)"];
s2_2[label="Identifier (b)"];
s2_3[label="LogicalExpression:exit"];
initial->s2_1->s2_2->s2_3;
s2_1->s2_3->final;
}
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];
s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
s3_2[label="Identifier (y)\nLogicalExpression:exit"];
s3_3[label="Identifier (z)"];
s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s3_1->s3_2->s3_3->s3_4;
s3_1->s3_4;
s3_2->s3_4->final;
}
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];
s4_1[label="LogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"];
s4_2[label="Identifier (q)\nLogicalExpression:exit"];
s4_3[label="Identifier (r)\nLogicalExpression:exit"];
s4_4[label="Identifier (s)"];
s4_5[label="LogicalExpression:exit"];
initial->s4_1->s4_2->s4_3->s4_4->s4_5;
s4_1->s4_5;
s4_2->s4_5;
s4_3->s4_5->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (bar)\nLogicalExpression\nPropertyDefinition:exit\nStaticBlock\nPropertyDefinition:enter\nIdentifier (baz)\nLogicalExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
@@ -0,0 +1,65 @@
/*expected
initial->s2_1->s2_2->s2_3;
s2_1->s2_3->final;
*/
/*expected
initial->s3_1->s3_2->s3_3->s3_4;
s3_1->s3_4;
s3_2->s3_4->final;
*/
/*expected
initial->s4_1->s4_2->s4_3->s4_4->s4_5;
s4_1->s4_5;
s4_2->s4_5;
s4_3->s4_5->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { bar () { a || b } static { x || y || z } baz() { p || q || r || s } }

/*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];
s2_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (a)"];
s2_2[label="Identifier (b)"];
s2_3[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"];
initial->s2_1->s2_2->s2_3;
s2_1->s2_3->final;
}
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];
s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
s3_2[label="Identifier (y)\nLogicalExpression:exit"];
s3_3[label="Identifier (z)"];
s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s3_1->s3_2->s3_3->s3_4;
s3_1->s3_4;
s3_2->s3_4->final;
}
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];
s4_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"];
s4_2[label="Identifier (q)\nLogicalExpression:exit"];
s4_3[label="Identifier (r)\nLogicalExpression:exit"];
s4_4[label="Identifier (s)"];
s4_5[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"];
initial->s4_1->s4_2->s4_3->s4_4->s4_5;
s4_1->s4_5;
s4_2->s4_5;
s4_3->s4_5->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nMethodDefinition:enter\nIdentifier (bar)\nFunctionExpression\nMethodDefinition:exit\nStaticBlock\nMethodDefinition:enter\nIdentifier (baz)\nFunctionExpression\nMethodDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
@@ -0,0 +1,29 @@
/*expected
initial->s2_1->s2_2->s2_4;
s2_1->s2_3->s2_4->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { this.bar = a ? b : c; } }

/*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];
s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nConditionalExpression:enter\nIdentifier (a)"];
s2_2[label="Identifier (b)"];
s2_4[label="ConditionalExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
s2_3[label="Identifier (c)"];
initial->s2_1->s2_2->s2_4;
s2_1->s2_3->s2_4->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
24 changes: 24 additions & 0 deletions tests/fixtures/code-path-analysis/class-static-blocks--empty.js
@@ -0,0 +1,24 @@
/*expected
initial->s2_1->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static {} }

/*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];
s2_1[label="StaticBlock"];
initial->s2_1->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
@@ -0,0 +1,34 @@
/*expected
initial->s3_1->final;
*/
/*expected
initial->s2_1->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { (p) => {} } }

/*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];
s3_1[label="ArrowFunctionExpression:enter\nIdentifier (p)\nBlockStatement\nArrowFunctionExpression:exit"];
initial->s3_1->final;
}
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];
s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nArrowFunctionExpression\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s2_1->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
29 changes: 29 additions & 0 deletions tests/fixtures/code-path-analysis/class-static-blocks--if-else.js
@@ -0,0 +1,29 @@
/*expected
initial->s2_1->s2_2->s2_4;
s2_1->s2_3->s2_4->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { if (bar) { this.baz = 1; } else { this.qux = 2; } } }

/*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];
s2_1[label="StaticBlock:enter\nIfStatement:enter\nIdentifier (bar)"];
s2_2[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
s2_4[label="IfStatement:exit\nStaticBlock:exit"];
s2_3[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (qux)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
initial->s2_1->s2_2->s2_4;
s2_1->s2_3->s2_4->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
@@ -0,0 +1,34 @@
/*expected
initial->s2_1->final;
*/
/*expected
initial->s3_1->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { this.bar = 1; } static { this.baz = 2; } }

/*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];
s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s2_1->final;
}
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];
s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s3_1->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
@@ -0,0 +1,45 @@
/*expected
initial->s2_1->s2_2->s2_3->s2_4;
s2_1->s2_4;
s2_2->s2_4->final;
*/
/*expected
initial->s3_1->s3_2->s3_3;
s3_1->s3_3->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { x || y || z } static { p || q } }

/*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];
s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
s2_2[label="Identifier (y)\nLogicalExpression:exit"];
s2_3[label="Identifier (z)"];
s2_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s2_1->s2_2->s2_3->s2_4;
s2_1->s2_4;
s2_2->s2_4->final;
}
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];
s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (p)"];
s3_2[label="Identifier (q)"];
s3_3[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s3_1->s3_2->s3_3;
s3_1->s3_3->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/
24 changes: 24 additions & 0 deletions tests/fixtures/code-path-analysis/class-static-blocks--simple.js
@@ -0,0 +1,24 @@
/*expected
initial->s2_1->final;
*/
/*expected
initial->s1_1->final;
*/
class Foo { static { this.bar = 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];
s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nIdentifier (baz)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
initial->s2_1->final;
}
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\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
initial->s1_1->final;
}
*/

0 comments on commit b3669fd

Please sign in to comment.