Skip to content

Commit 7620b89

Browse files
authored
fix: Remove no-unused-labels autofix before potential directives (#17314)
1 parent ef6e24e commit 7620b89

File tree

10 files changed

+158
-49
lines changed

10 files changed

+158
-49
lines changed

docs/src/rules/no-unused-labels.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Such labels take up space in the code and can lead to confusion by readers.
3030

3131
This rule is aimed at eliminating unused labels.
3232

33+
Problems reported by this rule can be fixed automatically, except when there are any comments between the label and the following statement, or when removing a label would cause the following statement to become a directive such as `"use strict"`.
34+
3335
Examples of **incorrect** code for this rule:
3436

3537
::: incorrect

lib/rules/dot-notation.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ module.exports = {
133133
}
134134
if (
135135
node.computed &&
136-
node.property.type === "TemplateLiteral" &&
137-
node.property.expressions.length === 0
136+
astUtils.isStaticTemplateLiteral(node.property)
138137
) {
139138
checkComputedProperty(node, node.property.quasis[0].value.cooked);
140139
}

lib/rules/no-restricted-modules.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
*/
66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const astUtils = require("./utils/ast-utils");
13+
814
//------------------------------------------------------------------------------
915
// Rule Definition
1016
//------------------------------------------------------------------------------
@@ -116,15 +122,6 @@ module.exports = {
116122
return node && node.type === "Literal" && typeof node.value === "string";
117123
}
118124

119-
/**
120-
* Function to check if a node is a static string template literal.
121-
* @param {ASTNode} node The node to check.
122-
* @returns {boolean} If the node is a string template literal.
123-
*/
124-
function isStaticTemplateLiteral(node) {
125-
return node && node.type === "TemplateLiteral" && node.expressions.length === 0;
126-
}
127-
128125
/**
129126
* Function to check if a node is a require call.
130127
* @param {ASTNode} node The node to check.
@@ -144,7 +141,7 @@ module.exports = {
144141
return node.value.trim();
145142
}
146143

147-
if (isStaticTemplateLiteral(node)) {
144+
if (astUtils.isStaticTemplateLiteral(node)) {
148145
return node.quasis[0].value.cooked.trim();
149146
}
150147

lib/rules/no-unused-labels.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55

66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const astUtils = require("./utils/ast-utils");
13+
814
//------------------------------------------------------------------------------
915
// Rule Definition
1016
//------------------------------------------------------------------------------
@@ -46,6 +52,45 @@ module.exports = {
4652
};
4753
}
4854

55+
/**
56+
* Checks if a `LabeledStatement` node is fixable.
57+
* For a node to be fixable, there must be no comments between the label and the body.
58+
* Furthermore, is must be possible to remove the label without turning the body statement into a
59+
* directive after other fixes are applied.
60+
* @param {ASTNode} node The node to evaluate.
61+
* @returns {boolean} Whether or not the node is fixable.
62+
*/
63+
function isFixable(node) {
64+
65+
/*
66+
* Only perform a fix if there are no comments between the label and the body. This will be the case
67+
* when there is exactly one token/comment (the ":") between the label and the body.
68+
*/
69+
if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !==
70+
sourceCode.getTokenBefore(node.body, { includeComments: true })) {
71+
return false;
72+
}
73+
74+
// Looking for the node's deepest ancestor which is not a `LabeledStatement`.
75+
let ancestor = node.parent;
76+
77+
while (ancestor.type === "LabeledStatement") {
78+
ancestor = ancestor.parent;
79+
}
80+
81+
if (ancestor.type === "Program" ||
82+
(ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) {
83+
const { body } = node;
84+
85+
if (body.type === "ExpressionStatement" &&
86+
((body.expression.type === "Literal" && typeof body.expression.value === "string") ||
87+
astUtils.isStaticTemplateLiteral(body.expression))) {
88+
return false; // potential directive
89+
}
90+
}
91+
return true;
92+
}
93+
4994
/**
5095
* Removes the top of the stack.
5196
* At the same time, this reports the label if it's never used.
@@ -58,19 +103,7 @@ module.exports = {
58103
node: node.label,
59104
messageId: "unused",
60105
data: node.label,
61-
fix(fixer) {
62-
63-
/*
64-
* Only perform a fix if there are no comments between the label and the body. This will be the case
65-
* when there is exactly one token/comment (the ":") between the label and the body.
66-
*/
67-
if (sourceCode.getTokenAfter(node.label, { includeComments: true }) ===
68-
sourceCode.getTokenBefore(node.body, { includeComments: true })) {
69-
return fixer.removeRange([node.range[0], node.body.range[0]]);
70-
}
71-
72-
return null;
73-
}
106+
fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null
74107
});
75108
}
76109

lib/rules/prefer-regex-literals.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ function isRegexLiteral(node) {
3737
return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
3838
}
3939

40-
/**
41-
* Determines whether the given node is a template literal without expressions.
42-
* @param {ASTNode} node Node to check.
43-
* @returns {boolean} True if the node is a template literal without expressions.
44-
*/
45-
function isStaticTemplateLiteral(node) {
46-
return node.type === "TemplateLiteral" && node.expressions.length === 0;
47-
}
48-
4940
const validPrecedingTokens = new Set([
5041
"(",
5142
";",
@@ -178,7 +169,7 @@ module.exports = {
178169
return node.type === "TaggedTemplateExpression" &&
179170
astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
180171
isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
181-
isStaticTemplateLiteral(node.quasi);
172+
astUtils.isStaticTemplateLiteral(node.quasi);
182173
}
183174

184175
/**
@@ -191,7 +182,7 @@ module.exports = {
191182
return node.value;
192183
}
193184

194-
if (isStaticTemplateLiteral(node)) {
185+
if (astUtils.isStaticTemplateLiteral(node)) {
195186
return node.quasis[0].value.cooked;
196187
}
197188

@@ -209,7 +200,7 @@ module.exports = {
209200
*/
210201
function isStaticString(node) {
211202
return isStringLiteral(node) ||
212-
isStaticTemplateLiteral(node) ||
203+
astUtils.isStaticTemplateLiteral(node) ||
213204
isStringRawTaggedStaticTemplateLiteral(node);
214205
}
215206

lib/rules/utils/ast-utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,15 @@ module.exports = {
21332133
return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
21342134
},
21352135

2136+
/**
2137+
* Determines whether the given node is a template literal without expressions.
2138+
* @param {ASTNode} node Node to check.
2139+
* @returns {boolean} True if the node is a template literal without expressions.
2140+
*/
2141+
isStaticTemplateLiteral(node) {
2142+
return node.type === "TemplateLiteral" && node.expressions.length === 0;
2143+
},
2144+
21362145
isReferenceToGlobalVariable,
21372146
isLogicalExpression,
21382147
isCoalesceExpression,

lib/rules/valid-typeof.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
*/
55
"use strict";
66

7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const astUtils = require("./utils/ast-utils");
12+
713
//------------------------------------------------------------------------------
814
// Rule Definition
915
//------------------------------------------------------------------------------
@@ -88,7 +94,7 @@ module.exports = {
8894
if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) {
8995
const sibling = parent.left === node ? parent.right : parent.left;
9096

91-
if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) {
97+
if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) {
9298
const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked;
9399

94100
if (!VALID_TYPES.has(value)) {

lib/rules/yoda.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,13 @@ function isNegativeNumericLiteral(node) {
5858
);
5959
}
6060

61-
/**
62-
* Determines whether a node is a Template Literal which can be determined statically.
63-
* @param {ASTNode} node Node to test
64-
* @returns {boolean} True if the node is a Template Literal without expression.
65-
*/
66-
function isStaticTemplateLiteral(node) {
67-
return node.type === "TemplateLiteral" && node.expressions.length === 0;
68-
}
69-
7061
/**
7162
* Determines whether a non-Literal node should be treated as a single Literal node.
7263
* @param {ASTNode} node Node to test
7364
* @returns {boolean} True if the node should be treated as a single Literal node.
7465
*/
7566
function looksLikeLiteral(node) {
76-
return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node);
67+
return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node);
7768
}
7869

7970
/**
@@ -100,7 +91,7 @@ function getNormalizedLiteral(node) {
10091
};
10192
}
10293

103-
if (isStaticTemplateLiteral(node)) {
94+
if (astUtils.isStaticTemplateLiteral(node)) {
10495
return {
10596
type: "Literal",
10697
value: node.quasis[0].value.cooked,

tests/lib/rules/no-unused-labels.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,69 @@ ruleTester.run("no-unused-labels", rule, {
7373
code: "A /* comment */: foo",
7474
output: null,
7575
errors: [{ messageId: "unused" }]
76+
},
77+
78+
// https://github.com/eslint/eslint/issues/16988
79+
{
80+
code: 'A: "use strict"',
81+
output: null,
82+
errors: [{ messageId: "unused" }]
83+
},
84+
{
85+
code: '"use strict"; foo: "bar"',
86+
output: null,
87+
errors: [{ messageId: "unused" }]
88+
},
89+
{
90+
code: 'A: ("use strict")', // Parentheses may be removed by another rule.
91+
output: null,
92+
errors: [{ messageId: "unused" }]
93+
},
94+
{
95+
code: "A: `use strict`", // `use strict` may be changed to "use strict" by another rule.
96+
output: null,
97+
parserOptions: { ecmaVersion: 6 },
98+
errors: [{ messageId: "unused" }]
99+
},
100+
{
101+
code: "if (foo) { bar: 'baz' }",
102+
output: "if (foo) { 'baz' }",
103+
errors: [{ messageId: "unused" }]
104+
},
105+
{
106+
code: "A: B: 'foo'",
107+
output: "B: 'foo'",
108+
errors: [{ messageId: "unused" }, { messageId: "unused" }]
109+
},
110+
{
111+
code: "A: B: C: 'foo'",
112+
output: "B: C: 'foo'", // Becomes "C: 'foo'" on the second pass.
113+
errors: [{ messageId: "unused" }, { messageId: "unused" }, { messageId: "unused" }]
114+
},
115+
{
116+
code: "A: B: C: D: 'foo'",
117+
output: "B: D: 'foo'", // Becomes "D: 'foo'" on the second pass.
118+
errors: [
119+
{ messageId: "unused" },
120+
{ messageId: "unused" },
121+
{ messageId: "unused" },
122+
{ messageId: "unused" }]
123+
},
124+
{
125+
code: "A: B: C: D: E: 'foo'",
126+
output: "B: D: E: 'foo'", // Becomes "E: 'foo'" on the third pass.
127+
errors: [
128+
{ messageId: "unused" },
129+
{ messageId: "unused" },
130+
{ messageId: "unused" },
131+
{ messageId: "unused" },
132+
{ messageId: "unused" }
133+
]
134+
},
135+
{
136+
code: "A: 42",
137+
output: "42",
138+
errors: [{ messageId: "unused" }]
76139
}
77140

78141
/*

tests/lib/rules/utils/ast-utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,4 +1857,22 @@ describe("ast-utils", () => {
18571857
});
18581858
});
18591859
});
1860+
1861+
describe("isStaticTemplateLiteral", () => {
1862+
const expectedResults = {
1863+
"``": true,
1864+
"`foo`": true,
1865+
"`foo${bar}`": false,
1866+
"\"foo\"": false,
1867+
"foo`bar`": false
1868+
};
1869+
1870+
Object.entries(expectedResults).forEach(([code, expectedResult]) => {
1871+
it(`returns ${expectedResult} for ${code}`, () => {
1872+
const ast = espree.parse(code, { ecmaVersion: 6 });
1873+
1874+
assert.strictEqual(astUtils.isStaticTemplateLiteral(ast.body[0].expression), expectedResult);
1875+
});
1876+
});
1877+
});
18601878
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy