Repository: eslint/eslint
Pull request #15318 (2021-11-15 20:29:51)

feat: update padding-line-between-statements for class static blocks
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-proposal.md))
[x] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

Refs #15016, fixes `padding-line-between-statements`.

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

Updated the `padding-line-between-statements` rule to apply to the top level of class static blocks.

#### Is there anything you'd like reviewers to focus on?



Patch:
diff --git a/README.md b/README.md
index d5f156a74..bbc74b7ce 100644
--- a/README.md
+++ b/README.md
@@ -207,6 +207,8 @@ These folks keep the project moving and are resources for help.
 
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 
+
+
 <!--teamstart-->
 
 ### Technical Steering Committee (TSC)
@@ -290,6 +292,8 @@ Nitin Kumar
 <!--teamend-->
 
 
+
+
 ## <a name="sponsors"></a>Sponsors
 
 The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website.
diff --git a/docs/rules/padding-line-between-statements.md b/docs/rules/padding-line-between-statements.md
index 2f2f67ee0..065c7d937 100644
--- a/docs/rules/padding-line-between-statements.md
+++ b/docs/rules/padding-line-between-statements.md
@@ -147,6 +147,13 @@ function foo() {
     const a = 0;
     bar();
 }
+
+class C {
+    static {
+        let a = 0;
+        bar();
+    }
+}
 ```
 
 Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration:
@@ -178,6 +185,15 @@ function foo() {
 
     bar();
 }
+
+class C {
+    static {
+        let a = 0;
+        let b = 0;
+
+        bar();
+    }
+}
 ```
 
 ----
diff --git a/docs/rules/strict.md b/docs/rules/strict.md
index a6d87c20a..97285afc0 100644
--- a/docs/rules/strict.md
+++ b/docs/rules/strict.md
@@ -49,6 +49,8 @@ This rule disallows strict mode directives, no matter which option is specified,
 
 This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option.
 
+This rule does not apply to class static blocks, no matter which option is specified, because class static blocks do not have directives. Therefore, a `"use strict"` statement in a class static block is not a directive, and will be reported by the [no-unused-expressions](no-unused-expressions.md) rule.
+
 The `--fix` option on the command line does not insert new `"use strict"` statements, but only removes unneeded statements.
 
 ## Options
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 9ee7df474..ad84c37b6 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -473,7 +473,8 @@ This option causes ESLint to exit with exit code 2 if one or more fatal parsing
 
 #### `--debug`
 
-This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
+This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.  
+Add this flag to an ESLint command line invocation in order to get extra debug information as the command is run (e.g. `eslint --debug test.js` and `eslint test.js --debug` are equivalent)
 
 #### `-h`, `--help`
 
diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js
index 073940a40..42859dd38 100644
--- a/lib/rules/padding-line-between-statements.js
+++ b/lib/rules/padding-line-between-statements.js
@@ -618,9 +618,11 @@ module.exports = {
             Program: enterScope,
             BlockStatement: enterScope,
             SwitchStatement: enterScope,
+            StaticBlock: enterScope,
             "Program:exit": exitScope,
             "BlockStatement:exit": exitScope,
             "SwitchStatement:exit": exitScope,
+            "StaticBlock:exit": exitScope,
 
             ":statement": verify,
 
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index 1d02d8a75..17ab533b3 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -35,7 +35,7 @@ const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|g
 const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
 
 // A set of node types that can contain a list of statements
-const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
+const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]);
 
 const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
 


Test patch:
diff --git a/tests/lib/rules/padding-line-between-statements.js b/tests/lib/rules/padding-line-between-statements.js
index 931cdd24c..51ddf0eb3 100644
--- a/tests/lib/rules/padding-line-between-statements.js
+++ b/tests/lib/rules/padding-line-between-statements.js
@@ -2626,6 +2626,114 @@ ruleTester.run("padding-line-between-statements", rule, {
             options: [
                 { blankLine: "always", prev: "block-like", next: "block-like" }
             ]
+        },
+
+        // class static blocks
+        {
+            code: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+            options: [
+                { blankLine: "always", prev: "let", next: "expression" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n }\n }",
+            options: [
+                { blankLine: "never", prev: "let", next: "expression" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+            options: [
+                { blankLine: "never", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+            options: [
+                { blankLine: "never", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C {\n static {\n let a = 0;\n let b =0;\n\n bar();\n }\n }",
+            options: [
+                { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
+                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { let x; { let y; } let z; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { method() { let x; } static { let y; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { let y; } method() { let x; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { let x; } static { let y; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "let x; class C { static { let y; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { let x; } } let y;",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { let x; } }",
+            options: [
+                { blankLine: "always", prev: "class", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives
+            options: [
+                { blankLine: "always", prev: "directive", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 }
         }
     ],
     invalid: [
@@ -4976,6 +5084,117 @@ ruleTester.run("padding-line-between-statements", rule, {
                 { messageId: "expectedBlankLine" },
                 { messageId: "expectedBlankLine" }
             ]
+        },
+
+        // class static blocks
+        {
+            code: "class C {\n static {\n let x;\n foo();\n }\n }",
+            output: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+            options: [
+                { blankLine: "always", prev: "let", next: "expression" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+            output: "class C {\n static {\n let x;\n foo();\n }\n }",
+            options: [
+                { blankLine: "never", prev: "let", next: "expression" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unexpectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+            output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+            output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+            options: [
+                { blankLine: "never", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unexpectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+            output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+            output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+            options: [
+                { blankLine: "never", prev: "expression", next: "const" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unexpectedBlankLine" }]
+        },
+        {
+            code: "class C {\n static {\n let a = 0;\n bar();\n }\n }",
+            output: "class C {\n static {\n let a = 0;\n\n bar();\n }\n }",
+            options: [
+                { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
+                { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C { static { let x; { let y; let z; } let q; } }",
+            output: "class C { static { let x; { let y;\n\n let z; } let q; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C { static { { let x; } let y; let z; } }",
+            output: "class C { static { { let x; } let y;\n\n let z; } }",
+            options: [
+                { blankLine: "always", prev: "let", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C { static { foo(); if (bar) {} } }",
+            output: "class C { static { foo();\n\n if (bar) {} } }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "block-like" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C { static { let x; } } let y;",
+            output: "class C { static { let x; } }\n\n let y;",
+            options: [
+                { blankLine: "always", prev: "class", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives
+            output: "class C { static { 'use strict';\n\n let x; } }",
+            options: [
+                { blankLine: "always", prev: "expression", next: "let" }
+            ],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "expectedBlankLine" }]
         }
     ]
 });
diff --git a/tests/lib/rules/strict.js b/tests/lib/rules/strict.js
index def56ace4..e3ea705fe 100644
--- a/tests/lib/rules/strict.js
+++ b/tests/lib/rules/strict.js
@@ -88,7 +88,20 @@ ruleTester.run("strict", rule, {
         "function foo() { 'use strict'; return; }",
         { code: "'use strict'; function foo() { return; }", parserOptions: { ecmaFeatures: { globalReturn: true } } },
         { code: "function foo() { return; }", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
-        { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } }
+        { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } },
+
+        // class static blocks do not have directive prologues, therefore this rule should never require od disallow "use strict" statement in them.
+        { code: "'use strict'; class C { static { foo; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "'use strict'; class C { static { 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "'use strict'; class C { static { 'use strict'; 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { foo; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { foo; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+        { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, sourceType: "module" } },
+        { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } } }
 
     ],
     invalid: [
@@ -589,7 +602,60 @@ ruleTester.run("strict", rule, {
             options: ["function"],
             parserOptions: { ecmaVersion: 6 },
             errors: ["Use the function form of 'use strict'."]
-        }
+        },
 
+        // functions inside class static blocks should be checked
+        {
+            code: "'use strict'; class C { static { function foo() { \n'use strict'; } } }",
+            output: null,
+            options: ["global"],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "global", line: 2 }]
+        },
+        {
+            code: "class C { static { function foo() { \n'use strict'; } } }",
+            output: null,
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "never", line: 2 }]
+        },
+        {
+            code: "class C { static { function foo() { \n'use strict'; } } }",
+            output: "class C { static { function foo() { \n } } }",
+            options: ["safe"],
+            parserOptions: { ecmaVersion: 2022, sourceType: "module" },
+            errors: [{ messageId: "module", line: 2 }]
+        },
+        {
+            code: "class C { static { function foo() { \n'use strict'; } } }",
+            output: "class C { static { function foo() { \n } } }",
+            options: ["safe"],
+            parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } },
+            errors: [{ messageId: "implied", line: 2 }]
+        },
+        {
+            code: "function foo() {'use strict'; class C { static { function foo() { \n'use strict'; } } } }",
+            output: "function foo() {'use strict'; class C { static { function foo() { \n } } } }",
+            options: ["function"],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessary", line: 2 }]
+        },
+        {
+            code: "class C { static { function foo() { \n'use strict'; } } }",
+            output: "class C { static { function foo() { \n } } }",
+            options: ["function"],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessaryInClasses", line: 2 }]
+        },
+        {
+            code: "class C { static { function foo() { \n'use strict';\n'use strict'; } } }",
+            output: "class C { static { function foo() { \n\n } } }",
+            options: ["function"],
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [
+                { messageId: "unnecessaryInClasses", line: 2 },
+                { messageId: "multiple", line: 3 }
+            ]
+        }
     ]
 });