Skip to content

Commit

Permalink
feat: code path analysis for class static blocks
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 10, 2021
1 parent edd8d24 commit d8bb8d0
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 d8bb8d0

Please sign in to comment.