Skip to content

Commit

Permalink
feat: add new omitLastInOneLineClassBody option to the semi rule (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
snitin315 committed Apr 27, 2023
1 parent e92a6fc commit 559ff4e
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 28 deletions.
73 changes: 48 additions & 25 deletions docs/src/rules/semi.md
Expand Up @@ -80,7 +80,8 @@ String option:

Object option (when `"always"`):

* `"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
* `"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
* `"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

Object option (when `"never"`):

Expand Down Expand Up @@ -132,6 +133,52 @@ class Foo {

:::

#### omitLastInOneLineBlock

Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options:

::: correct

```js
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */

if (foo) { bar() }

if (foo) { bar(); baz() }

function f() { bar(); baz() }

class C {
foo() { bar(); baz() }

static { bar(); baz() }
}
```

:::

#### omitLastInOneLineClassBody

Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineClassBody": true }` options:

::: correct

```js
/*eslint semi: ["error", "always", { "omitLastInOneLineClassBody": true}] */

export class SomeClass{
logType(){
console.log(this.type);
console.log(this.anotherType);
}
}

export class Variant1 extends SomeClass{type=1}
export class Variant2 extends SomeClass{type=2; anotherType=3}
```

:::

### never

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

:::

#### omitLastInOneLineBlock

Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options:

::: correct

```js
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */

if (foo) { bar() }

if (foo) { bar(); baz() }

function f() { bar(); baz() }

class C {
foo() { bar(); baz() }

static { bar(); baz() }
}
```

:::

#### beforeStatementContinuationChars

Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options:
Expand Down
31 changes: 28 additions & 3 deletions lib/rules/semi.js
Expand Up @@ -58,7 +58,8 @@ module.exports = {
{
type: "object",
properties: {
omitLastInOneLineBlock: { type: "boolean" }
omitLastInOneLineBlock: { type: "boolean" },
omitLastInOneLineClassBody: { type: "boolean" }
},
additionalProperties: false
}
Expand All @@ -83,6 +84,7 @@ module.exports = {
const options = context.options[1];
const never = context.options[0] === "never";
const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody);
const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
const sourceCode = context.getSourceCode();

Expand Down Expand Up @@ -334,6 +336,27 @@ module.exports = {
return false;
}

/**
* Checks a node to see if it's the last item in a one-liner `ClassBody` node.
* ClassBody 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 the last item in a one-liner ClassBody.
*/
function isLastInOneLinerClassBody(node) {
const parent = node.parent;
const nextToken = sourceCode.getTokenAfter(node);

if (!nextToken || nextToken.value !== "}") {
return false;
}

if (parent.type === "ClassBody") {
return parent.loc.start.line === parent.loc.end.line;
}

return false;
}

/**
* Checks a node to see if it's followed by a semicolon.
* @param {ASTNode} node The node to check.
Expand All @@ -354,10 +377,12 @@ module.exports = {
}
} else {
const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node));
const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node));
const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody;

if (isSemi && oneLinerBlock) {
if (isSemi && oneLinerBlockOrClassBody) {
report(node, true);
} else if (!isSemi && !oneLinerBlock) {
} else if (!isSemi && !oneLinerBlockOrClassBody) {
report(node);
}
}
Expand Down
203 changes: 203 additions & 0 deletions tests/lib/rules/semi.js
Expand Up @@ -111,6 +111,81 @@ ruleTester.run("semi", rule, {
{ 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 } },

// omitLastInOneLineClassBody: true
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1}
export class Variant2 extends SomeClass{type=2}
export class Variant3 extends SomeClass{type=3}
export class Variant4 extends SomeClass{type=4}
export class Variant5 extends SomeClass{type=5}
`,
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
},
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
console.log(this.anotherType);
}
}
export class Variant1 extends SomeClass{type=1; anotherType=2}
`,
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
},
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1;}
export class Variant2 extends SomeClass{type=2;}
export class Variant3 extends SomeClass{type=3;}
export class Variant4 extends SomeClass{type=4;}
export class Variant5 extends SomeClass{type=5;}
`,
options: ["always", { omitLastInOneLineClassBody: false }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" }
},
{
code: "class C {\nfoo;}",
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022 }
},
{
code: "class C {foo;\n}",
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022 }
},
{
code: "class C {foo;\nbar;}",
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022 }
},
{
code: "{ foo; }",
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022 }
},
{
code: "class C\n{ foo }",
options: ["always", { omitLastInOneLineClassBody: true }],
parserOptions: { ecmaVersion: 2022 }
},

// 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 } },
Expand Down Expand Up @@ -2309,6 +2384,134 @@ ruleTester.run("semi", rule, {
endLine: 1,
endColumn: 18
}]
},

// omitLastInOneLineClassBody
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1}
`,
output: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1;}
`,
options: ["always", { omitLastInOneLineClassBody: false }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
errors: [
{
messageId: "missingSemi",
line: 8,
column: 63,
endLine: 8,
endColumn: 64
}
]
},
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1}
`,
output: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1;}
`,
options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
errors: [
{
messageId: "missingSemi",
line: 8,
column: 63,
endLine: 8,
endColumn: 64
}
]
},
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1;}
`,
output: `
export class SomeClass{
logType(){
console.log(this.type);
}
}
export class Variant1 extends SomeClass{type=1}
`,
options: ["always", { omitLastInOneLineClassBody: true, omitLastInOneLineBlock: false }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
errors: [
{
messageId: "extraSemi",
line: 8,
column: 63,
endLine: 8,
endColumn: 64
}
]
},
{
code: `
export class SomeClass{
logType(){
console.log(this.type);
console.log(this.anotherType);
}
}
export class Variant1 extends SomeClass{type=1; anotherType=2}
`,
output: `
export class SomeClass{
logType(){
console.log(this.type);
console.log(this.anotherType);
}
}
export class Variant1 extends SomeClass{type=1; anotherType=2;}
`,
options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }],
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
errors: [
{
messageId: "missingSemi",
line: 9,
column: 78,
endLine: 9,
endColumn: 79
}
]
}
]
});

0 comments on commit 559ff4e

Please sign in to comment.