Skip to content

Commit 559ff4e

Browse files
authored
feat: add new omitLastInOneLineClassBody option to the semi rule (#17105)
* feat: add new `omitLastInOneLineClassBody` option to the `semi` rule * test: add cases for `omitLastInOneLineClassBody` option for `semi` * docs: add examples for `omitLastInOneLineClassBody` option in `semi` * docs: update options order in semi rule * fix: remove false positive for omitLastInOneLineClassBody * tests: add more cases
1 parent e92a6fc commit 559ff4e

3 files changed

Lines changed: 279 additions & 28 deletions

File tree

docs/src/rules/semi.md

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ String option:
8080

8181
Object option (when `"always"`):
8282

83-
* `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line
83+
* `"omitLastInOneLineBlock": true` disallows the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line
84+
* `"omitLastInOneLineClassBody": true` disallows the last semicolon in a class body in which its braces (and therefore the content of the class body) are in the same line
8485

8586
Object option (when `"never"`):
8687

@@ -132,6 +133,52 @@ class Foo {
132133

133134
:::
134135

136+
#### omitLastInOneLineBlock
137+
138+
Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options:
139+
140+
::: correct
141+
142+
```js
143+
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */
144+
145+
if (foo) { bar() }
146+
147+
if (foo) { bar(); baz() }
148+
149+
function f() { bar(); baz() }
150+
151+
class C {
152+
foo() { bar(); baz() }
153+
154+
static { bar(); baz() }
155+
}
156+
```
157+
158+
:::
159+
160+
#### omitLastInOneLineClassBody
161+
162+
Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineClassBody": true }` options:
163+
164+
::: correct
165+
166+
```js
167+
/*eslint semi: ["error", "always", { "omitLastInOneLineClassBody": true}] */
168+
169+
export class SomeClass{
170+
logType(){
171+
console.log(this.type);
172+
console.log(this.anotherType);
173+
}
174+
}
175+
176+
export class Variant1 extends SomeClass{type=1}
177+
export class Variant2 extends SomeClass{type=2; anotherType=3}
178+
```
179+
180+
:::
181+
135182
### never
136183

137184
Examples of **incorrect** code for this rule with the `"never"` option:
@@ -190,30 +237,6 @@ class Foo {
190237

191238
:::
192239

193-
#### omitLastInOneLineBlock
194-
195-
Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options:
196-
197-
::: correct
198-
199-
```js
200-
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */
201-
202-
if (foo) { bar() }
203-
204-
if (foo) { bar(); baz() }
205-
206-
function f() { bar(); baz() }
207-
208-
class C {
209-
foo() { bar(); baz() }
210-
211-
static { bar(); baz() }
212-
}
213-
```
214-
215-
:::
216-
217240
#### beforeStatementContinuationChars
218241

219242
Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options:

lib/rules/semi.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ module.exports = {
5858
{
5959
type: "object",
6060
properties: {
61-
omitLastInOneLineBlock: { type: "boolean" }
61+
omitLastInOneLineBlock: { type: "boolean" },
62+
omitLastInOneLineClassBody: { type: "boolean" }
6263
},
6364
additionalProperties: false
6465
}
@@ -83,6 +84,7 @@ module.exports = {
8384
const options = context.options[1];
8485
const never = context.options[0] === "never";
8586
const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
87+
const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody);
8688
const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
8789
const sourceCode = context.getSourceCode();
8890

@@ -334,6 +336,27 @@ module.exports = {
334336
return false;
335337
}
336338

339+
/**
340+
* Checks a node to see if it's the last item in a one-liner `ClassBody` node.
341+
* ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line.
342+
* @param {ASTNode} node The node to check.
343+
* @returns {boolean} whether the node is the last item in a one-liner ClassBody.
344+
*/
345+
function isLastInOneLinerClassBody(node) {
346+
const parent = node.parent;
347+
const nextToken = sourceCode.getTokenAfter(node);
348+
349+
if (!nextToken || nextToken.value !== "}") {
350+
return false;
351+
}
352+
353+
if (parent.type === "ClassBody") {
354+
return parent.loc.start.line === parent.loc.end.line;
355+
}
356+
357+
return false;
358+
}
359+
337360
/**
338361
* Checks a node to see if it's followed by a semicolon.
339362
* @param {ASTNode} node The node to check.
@@ -354,10 +377,12 @@ module.exports = {
354377
}
355378
} else {
356379
const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node));
380+
const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node));
381+
const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody;
357382

358-
if (isSemi && oneLinerBlock) {
383+
if (isSemi && oneLinerBlockOrClassBody) {
359384
report(node, true);
360-
} else if (!isSemi && !oneLinerBlock) {
385+
} else if (!isSemi && !oneLinerBlockOrClassBody) {
361386
report(node);
362387
}
363388
}

tests/lib/rules/semi.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,81 @@ ruleTester.run("semi", rule, {
111111
{ code: "class C {\n static {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
112112
{ code: "class C {\n static { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
113113

114+
// omitLastInOneLineClassBody: true
115+
{
116+
code: `
117+
export class SomeClass{
118+
logType(){
119+
console.log(this.type);
120+
}
121+
}
122+
123+
export class Variant1 extends SomeClass{type=1}
124+
export class Variant2 extends SomeClass{type=2}
125+
export class Variant3 extends SomeClass{type=3}
126+
export class Variant4 extends SomeClass{type=4}
127+
export class Variant5 extends SomeClass{type=5}
128+
`,
129+
options: ["always", { omitLastInOneLineClassBody: true }],
130+
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
131+
},
132+
{
133+
code: `
134+
export class SomeClass{
135+
logType(){
136+
console.log(this.type);
137+
console.log(this.anotherType);
138+
}
139+
}
140+
141+
export class Variant1 extends SomeClass{type=1; anotherType=2}
142+
`,
143+
options: ["always", { omitLastInOneLineClassBody: true }],
144+
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
145+
},
146+
{
147+
code: `
148+
export class SomeClass{
149+
logType(){
150+
console.log(this.type);
151+
}
152+
}
153+
154+
export class Variant1 extends SomeClass{type=1;}
155+
export class Variant2 extends SomeClass{type=2;}
156+
export class Variant3 extends SomeClass{type=3;}
157+
export class Variant4 extends SomeClass{type=4;}
158+
export class Variant5 extends SomeClass{type=5;}
159+
`,
160+
options: ["always", { omitLastInOneLineClassBody: false }],
161+
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
162+
},
163+
{
164+
code: "class C {\nfoo;}",
165+
options: ["always", { omitLastInOneLineClassBody: true }],
166+
parserOptions: { ecmaVersion: 2022 }
167+
},
168+
{
169+
code: "class C {foo;\n}",
170+
options: ["always", { omitLastInOneLineClassBody: true }],
171+
parserOptions: { ecmaVersion: 2022 }
172+
},
173+
{
174+
code: "class C {foo;\nbar;}",
175+
options: ["always", { omitLastInOneLineClassBody: true }],
176+
parserOptions: { ecmaVersion: 2022 }
177+
},
178+
{
179+
code: "{ foo; }",
180+
options: ["always", { omitLastInOneLineClassBody: true }],
181+
parserOptions: { ecmaVersion: 2022 }
182+
},
183+
{
184+
code: "class C\n{ foo }",
185+
options: ["always", { omitLastInOneLineClassBody: true }],
186+
parserOptions: { ecmaVersion: 2022 }
187+
},
188+
114189
// method definitions and static blocks don't have a semicolon.
115190
{ code: "class A { a() {} b() {} }", parserOptions: { ecmaVersion: 6 } },
116191
{ code: "var A = class { a() {} b() {} };", parserOptions: { ecmaVersion: 6 } },
@@ -2309,6 +2384,134 @@ ruleTester.run("semi", rule, {
23092384
endLine: 1,
23102385
endColumn: 18
23112386
}]
2387+
},
2388+
2389+
// omitLastInOneLineClassBody
2390+
{
2391+
code: `
2392+
export class SomeClass{
2393+
logType(){
2394+
console.log(this.type);
2395+
}
2396+
}
2397+
2398+
export class Variant1 extends SomeClass{type=1}
2399+
`,
2400+
output: `
2401+
export class SomeClass{
2402+
logType(){
2403+
console.log(this.type);
2404+
}
2405+
}
2406+
2407+
export class Variant1 extends SomeClass{type=1;}
2408+
`,
2409+
options: ["always", { omitLastInOneLineClassBody: false }],
2410+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
2411+
errors: [
2412+
{
2413+
messageId: "missingSemi",
2414+
line: 8,
2415+
column: 63,
2416+
endLine: 8,
2417+
endColumn: 64
2418+
}
2419+
]
2420+
},
2421+
{
2422+
code: `
2423+
export class SomeClass{
2424+
logType(){
2425+
console.log(this.type);
2426+
}
2427+
}
2428+
2429+
export class Variant1 extends SomeClass{type=1}
2430+
`,
2431+
output: `
2432+
export class SomeClass{
2433+
logType(){
2434+
console.log(this.type);
2435+
}
2436+
}
2437+
2438+
export class Variant1 extends SomeClass{type=1;}
2439+
`,
2440+
options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }],
2441+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
2442+
errors: [
2443+
{
2444+
messageId: "missingSemi",
2445+
line: 8,
2446+
column: 63,
2447+
endLine: 8,
2448+
endColumn: 64
2449+
}
2450+
]
2451+
},
2452+
{
2453+
code: `
2454+
export class SomeClass{
2455+
logType(){
2456+
console.log(this.type);
2457+
}
2458+
}
2459+
2460+
export class Variant1 extends SomeClass{type=1;}
2461+
`,
2462+
output: `
2463+
export class SomeClass{
2464+
logType(){
2465+
console.log(this.type);
2466+
}
2467+
}
2468+
2469+
export class Variant1 extends SomeClass{type=1}
2470+
`,
2471+
options: ["always", { omitLastInOneLineClassBody: true, omitLastInOneLineBlock: false }],
2472+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
2473+
errors: [
2474+
{
2475+
messageId: "extraSemi",
2476+
line: 8,
2477+
column: 63,
2478+
endLine: 8,
2479+
endColumn: 64
2480+
}
2481+
]
2482+
},
2483+
{
2484+
code: `
2485+
export class SomeClass{
2486+
logType(){
2487+
console.log(this.type);
2488+
console.log(this.anotherType);
2489+
}
2490+
}
2491+
2492+
export class Variant1 extends SomeClass{type=1; anotherType=2}
2493+
`,
2494+
output: `
2495+
export class SomeClass{
2496+
logType(){
2497+
console.log(this.type);
2498+
console.log(this.anotherType);
2499+
}
2500+
}
2501+
2502+
export class Variant1 extends SomeClass{type=1; anotherType=2;}
2503+
`,
2504+
options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }],
2505+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
2506+
errors: [
2507+
{
2508+
messageId: "missingSemi",
2509+
line: 9,
2510+
column: 78,
2511+
endLine: 9,
2512+
endColumn: 79
2513+
}
2514+
]
23122515
}
23132516
]
23142517
});

0 commit comments

Comments
 (0)