Skip to content

Commit 391ed38

Browse files
authoredJun 20, 2023
fix: Remove no-extra-semi autofix before potential directives (#17297)
1 parent e1314bf commit 391ed38

File tree

3 files changed

+68
-11
lines changed

3 files changed

+68
-11
lines changed
 

‎docs/src/rules/no-extra-semi.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Typing mistakes and misunderstandings about where semicolons are required can le
1616

1717
This rule disallows unnecessary semicolons.
1818

19+
Problems reported by this rule can be fixed automatically, except when removing a semicolon would cause a following statement to become a directive such as `"use strict"`.
20+
1921
Examples of **incorrect** code for this rule:
2022

2123
::: incorrect

‎lib/rules/no-extra-semi.js

+29-11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ module.exports = {
3838
create(context) {
3939
const sourceCode = context.sourceCode;
4040

41+
/**
42+
* Checks if a node or token is fixable.
43+
* A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes.
44+
* @param {Token} nodeOrToken The node or token to check.
45+
* @returns {boolean} Whether or not the node is fixable.
46+
*/
47+
function isFixable(nodeOrToken) {
48+
const nextToken = sourceCode.getTokenAfter(nodeOrToken);
49+
50+
if (!nextToken || nextToken.type !== "String") {
51+
return true;
52+
}
53+
const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
54+
55+
return !astUtils.isTopLevelExpressionStatement(stringNode.parent);
56+
}
57+
4158
/**
4259
* Reports an unnecessary semicolon error.
4360
* @param {Node|Token} nodeOrToken A node or a token to be reported.
@@ -47,17 +64,18 @@ module.exports = {
4764
context.report({
4865
node: nodeOrToken,
4966
messageId: "unexpected",
50-
fix(fixer) {
51-
52-
/*
53-
* Expand the replacement range to include the surrounding
54-
* tokens to avoid conflicting with semi.
55-
* https://github.com/eslint/eslint/issues/7928
56-
*/
57-
return new FixTracker(fixer, context.sourceCode)
58-
.retainSurroundingTokens(nodeOrToken)
59-
.remove(nodeOrToken);
60-
}
67+
fix: isFixable(nodeOrToken)
68+
? fixer =>
69+
70+
/*
71+
* Expand the replacement range to include the surrounding
72+
* tokens to avoid conflicting with semi.
73+
* https://github.com/eslint/eslint/issues/7928
74+
*/
75+
new FixTracker(fixer, context.sourceCode)
76+
.retainSurroundingTokens(nodeOrToken)
77+
.remove(nodeOrToken)
78+
: null
6179
});
6280
}
6381

‎tests/lib/rules/no-extra-semi.js

+37
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,43 @@ ruleTester.run("no-extra-semi", rule, {
190190
output: "class A { static { a; } foo(){} }",
191191
parserOptions: { ecmaVersion: 2022 },
192192
errors: [{ messageId: "unexpected", type: "Punctuator", column: 24 }]
193+
},
194+
195+
// https://github.com/eslint/eslint/issues/16988
196+
{
197+
code: "; 'use strict'",
198+
output: null,
199+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
200+
},
201+
{
202+
code: "; ; 'use strict'",
203+
output: " ; 'use strict'",
204+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }, { messageId: "unexpected", type: "EmptyStatement" }]
205+
},
206+
{
207+
code: "debugger;\n;\n'use strict'",
208+
output: null,
209+
errors: [{ messageId: "unexpected", type: "EmptyStatement", line: 2 }]
210+
},
211+
{
212+
code: "function foo() { ; 'bar'; }",
213+
output: null,
214+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
215+
},
216+
{
217+
code: "{ ; 'foo'; }",
218+
output: "{ 'foo'; }",
219+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
220+
},
221+
{
222+
code: "; ('use strict');",
223+
output: " ('use strict');",
224+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
225+
},
226+
{
227+
code: "; 1;",
228+
output: " 1;",
229+
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
193230
}
194231
]
195232
});

0 commit comments

Comments
 (0)
Please sign in to comment.