From 0f0971ffc2ca6f4513eeffdf5cfa36826c8f4543 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 15 Nov 2021 11:52:57 +0100 Subject: [PATCH] feat: update semi rule for class static blocks (#15286) Updates `omitLastInOneLineBlock` option to apply to class static blocks. Refs #15016 --- docs/rules/semi.md | 8 + lib/rules/semi.js | 27 ++- tests/lib/rules/semi.js | 371 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 395 insertions(+), 11 deletions(-) diff --git a/docs/rules/semi.md b/docs/rules/semi.md index 158cd7f53e2..cb8e6dd0dab 100644 --- a/docs/rules/semi.md +++ b/docs/rules/semi.md @@ -172,6 +172,14 @@ Examples of additional **correct** code for this rule with the `"always", { "omi if (foo) { bar() } if (foo) { bar(); baz() } + +function f() { bar(); baz() } + +class C { + foo() { bar(); baz() } + + static { bar(); baz() } +} ``` #### beforeStatementContinuationChars diff --git a/lib/rules/semi.js b/lib/rules/semi.js index 4124a8c508c..c29029cedfe 100644 --- a/lib/rules/semi.js +++ b/lib/rules/semi.js @@ -306,22 +306,31 @@ module.exports = { } /** - * Checks a node to see if it's in a one-liner block statement. + * Checks a node to see if it's the last item in a one-liner block. + * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its + * braces (and consequently everything between them) are on the same line. * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is in a one-liner block statement. + * @returns {boolean} whether the node is the last item in a one-liner block. */ - function isOneLinerBlock(node) { + function isLastInOneLinerBlock(node) { const parent = node.parent; const nextToken = sourceCode.getTokenAfter(node); if (!nextToken || nextToken.value !== "}") { return false; } - return ( - !!parent && - parent.type === "BlockStatement" && - parent.loc.start.line === parent.loc.end.line - ); + + if (parent.type === "BlockStatement") { + return parent.loc.start.line === parent.loc.end.line; + } + + if (parent.type === "StaticBlock") { + const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 }); // skip the `static` token + + return openingBrace.loc.start.line === parent.loc.end.line; + } + + return false; } /** @@ -343,7 +352,7 @@ module.exports = { report(node); } } else { - const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node)); + const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node)); if (isSemi && oneLinerBlock) { report(node, true); diff --git a/tests/lib/rules/semi.js b/tests/lib/rules/semi.js index bc00079d8a0..d87382182ea 100644 --- a/tests/lib/rules/semi.js +++ b/tests/lib/rules/semi.js @@ -48,14 +48,73 @@ ruleTester.run("semi", rule, { { code: "for (let thing of {}) {\n console.log(thing);\n}", parserOptions: { ecmaVersion: 6 } }, { code: "do{}while(true)", options: ["never"] }, { code: "do{}while(true);", options: ["always"] }, + { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static {} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); } }", options: ["always"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar(); baz();} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\nbar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\nbar()\nbaz() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n (a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;(a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n [a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;[a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n +a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;+a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n -a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;-a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n /a/ } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;/a/} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { + code: "class C { static { foo();\n (a) } }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { do ; while (foo)\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { do ; while (foo)\n ;(a)} }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + // omitLastInOneLineBlock: true { code: "if (foo) { bar() }", options: ["always", { omitLastInOneLineBlock: true }] }, { code: "if (foo) { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo)\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo) {\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo) { bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo() { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo()\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo(){\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo(){ bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "() => { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() =>\n { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() => {\n bar(); baz(); };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() => { bar(); baz(); \n};", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method()\n { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() {\n bar(); baz(); } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() { bar(); baz(); \n} };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method()\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n static { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, - - // method definitions don't have a semicolon. + // method definitions and static blocks don't have a semicolon. { code: "class A { a() {} b() {} }", parserOptions: { ecmaVersion: 6 } }, { code: "var A = class { a() {} b() {} };", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { static {} }", parserOptions: { ecmaVersion: 2022 } }, { code: "import theDefault, { named1, named2 } from 'src/mylib';", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import theDefault, { named1, named2 } from 'src/mylib'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, @@ -311,6 +370,11 @@ ruleTester.run("semi", rule, { options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { + code: "class C { static {}; }", // no-extra-semi reports it + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, { code: "class C { a=b;\n*foo() {} }", options: ["never"], @@ -990,7 +1054,158 @@ ruleTester.run("semi", rule, { endColumn: 17 }] }, + { + code: "class C { static { foo() } }", + output: "class C { static { foo(); } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + }] + }, + { + code: "class C { static { foo() } }", + output: "class C { static { foo(); } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + }] + }, + { + code: "class C { static { foo(); bar() } }", + output: "class C { static { foo(); bar(); } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 32, + endLine: 1, + endColumn: 33 + }] + }, + { + code: "class C { static { foo()\nbar(); } }", + output: "class C { static { foo();\nbar(); } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 2, + endColumn: 1 + }] + }, + { + code: "class C { static { foo(); bar()\nbaz(); } }", + output: "class C { static { foo(); bar();\nbaz(); } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 32, + endLine: 2, + endColumn: 1 + }] + }, + { + code: "class C { static { foo(); } }", + output: "class C { static { foo() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + }] + }, + { + code: "class C { static { foo();\nbar() } }", + output: "class C { static { foo()\nbar() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 + }] + }, + { + code: "class C { static { foo()\nbar(); } }", + output: "class C { static { foo()\nbar() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "ExpressionStatement", + line: 2, + column: 6, + endLine: 2, + endColumn: 7 + }] + }, + { + code: "class C { static { foo()\nbar();\nbaz() } }", + output: "class C { static { foo()\nbar()\nbaz() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "ExpressionStatement", + line: 2, + column: 6, + endLine: 2, + endColumn: 7 + }] + }, + { + code: "class C { static { do ; while (foo)\n (a)} }", + output: "class C { static { do ; while (foo);\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + type: "DoWhileStatement", + line: 1, + column: 36, + endLine: 2, + endColumn: 1 + }] + }, + { + code: "class C { static { do ; while (foo)\n ;(a)} }", + output: "class C { static { do ; while (foo)\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 3 + }] + }, + // omitLastInOneLineBlock: true { code: "if (foo) { bar()\n }", output: "if (foo) { bar();\n }", @@ -1039,6 +1254,158 @@ ruleTester.run("semi", rule, { endColumn: 18 }] }, + { + code: "function foo() { bar(); baz(); }", + output: "function foo() { bar(); baz() }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "extraSemi", + line: 1, + column: 30, + endLine: 1, + endColumn: 31 + }] + }, + { + code: "function foo()\n{ bar(); baz(); }", + output: "function foo()\n{ bar(); baz() }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "extraSemi", + line: 2, + column: 15, + endLine: 2, + endColumn: 16 + }] + }, + { + code: "function foo() {\n bar(); baz() }", + output: "function foo() {\n bar(); baz(); }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 2, + column: 14, + endLine: 2, + endColumn: 15 + }] + }, + { + code: "function foo() { bar(); baz() \n}", + output: "function foo() { bar(); baz(); \n}", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 1, + column: 30, + endLine: 1, + endColumn: 31 + }] + }, + { + code: "class C {\nfoo() { bar(); baz(); }\n}", + output: "class C {\nfoo() { bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "extraSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, + { + code: "class C {\nfoo() \n{ bar(); baz(); }\n}", + output: "class C {\nfoo() \n{ bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "extraSemi", + line: 3, + column: 15, + endLine: 3, + endColumn: 16 + }] + }, + { + code: "class C {\nfoo() {\n bar(); baz() }\n}", + output: "class C {\nfoo() {\n bar(); baz(); }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "missingSemi", + line: 3, + column: 14, + endLine: 3, + endColumn: 15 + }] + }, + { + code: "class C {\nfoo() { bar(); baz() \n}\n}", + output: "class C {\nfoo() { bar(); baz(); \n}\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "missingSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, + { + code: "class C {\nstatic { bar(); baz(); }\n}", + output: "class C {\nstatic { bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 2, + column: 22, + endLine: 2, + endColumn: 23 + }] + }, + { + code: "class C {\nstatic \n{ bar(); baz(); }\n}", + output: "class C {\nstatic \n{ bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 3, + column: 15, + endLine: 3, + endColumn: 16 + }] + }, + { + code: "class C {\nstatic {\n bar(); baz() }\n}", + output: "class C {\nstatic {\n bar(); baz(); }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 3, + column: 14, + endLine: 3, + endColumn: 15 + }] + }, + { + code: "class C {\nfoo() { bar(); baz() \n}\n}", + output: "class C {\nfoo() { bar(); baz(); \n}\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, // exports, "always"