eslint 10.2.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/config/config-loader.js +1 -1
- package/lib/config/flat-config-schema.js +9 -6
- package/lib/linter/code-path-analysis/code-path-analyzer.js +13 -10
- package/lib/linter/code-path-analysis/code-path-state.js +21 -5
- package/lib/linter/code-path-analysis/code-path.js +6 -4
- package/lib/linter/code-path-analysis/id-generator.js +1 -2
- package/lib/rules/no-async-promise-executor.js +7 -1
- package/lib/rules/no-shadow.js +1 -1
- package/lib/rules/no-unused-private-class-members.js +201 -1
- package/lib/rules/no-useless-concat.js +2 -2
- package/lib/rules/no-useless-constructor.js +3 -2
- package/lib/rules/require-await.js +6 -4
- package/lib/rules/utils/ast-utils.js +36 -4
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -360,7 +360,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
|
|
360
360
|
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
|
|
361
361
|
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a></p><h3>Silver Sponsors</h3>
|
|
362
362
|
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
|
|
363
|
-
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="
|
|
363
|
+
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://citadel.co.jp"><img src="https://avatars.githubusercontent.com/u/75781367" alt="Citadel AI" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="TestMu AI Open Source Office (Formerly LambdaTest)" height="32"></a></p>
|
|
364
364
|
<h3>Technology Sponsors</h3>
|
|
365
365
|
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
|
|
366
366
|
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
|
|
@@ -230,7 +230,7 @@ async function loadConfigFile(filePath, hasUnstableNativeNodeJsTSConfigFlag) {
|
|
|
230
230
|
* the require cache only if the file has been changed.
|
|
231
231
|
*/
|
|
232
232
|
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
|
|
233
|
-
delete require.cache[filePath];
|
|
233
|
+
delete require.cache?.[filePath];
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
const isTS = isFileTS(filePath);
|
|
@@ -224,15 +224,16 @@ function assertIsRuleSeverity(ruleId, value) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
|
-
* Validates that a given string
|
|
227
|
+
* Validates that a given string matches the "pluginName/memberPlaceholder" pattern.
|
|
228
228
|
* @param {string} value The string to check.
|
|
229
|
+
* @param {string} memberPlaceholder The placeholder for the member portion of the expected format in the error message.
|
|
229
230
|
* @returns {void}
|
|
230
|
-
* @throws {TypeError} If the string
|
|
231
|
+
* @throws {TypeError} If the string doesn't match the expected pattern.
|
|
231
232
|
*/
|
|
232
|
-
function assertIsPluginMemberName(value) {
|
|
233
|
+
function assertIsPluginMemberName(value, memberPlaceholder) {
|
|
233
234
|
if (!/[\w\-@$]+(?:\/[\w\-$]+)+$/iu.test(value)) {
|
|
234
235
|
throw new TypeError(
|
|
235
|
-
`Expected string in the form "pluginName
|
|
236
|
+
`Expected string in the form "pluginName/${memberPlaceholder}" but found "${value}".`,
|
|
236
237
|
);
|
|
237
238
|
}
|
|
238
239
|
}
|
|
@@ -375,7 +376,9 @@ const languageOptionsSchema = {
|
|
|
375
376
|
/** @type {ObjectPropertySchema} */
|
|
376
377
|
const languageSchema = {
|
|
377
378
|
merge: "replace",
|
|
378
|
-
validate
|
|
379
|
+
validate(value) {
|
|
380
|
+
assertIsPluginMemberName(value, "languageName");
|
|
381
|
+
},
|
|
379
382
|
};
|
|
380
383
|
|
|
381
384
|
/** @type {ObjectPropertySchema} */
|
|
@@ -430,7 +433,7 @@ const processorSchema = {
|
|
|
430
433
|
merge: "replace",
|
|
431
434
|
validate(value) {
|
|
432
435
|
if (typeof value === "string") {
|
|
433
|
-
assertIsPluginMemberName(value);
|
|
436
|
+
assertIsPluginMemberName(value, "processorName");
|
|
434
437
|
} else if (value && typeof value === "object") {
|
|
435
438
|
if (
|
|
436
439
|
typeof value.preprocess !== "function" ||
|
|
@@ -31,7 +31,7 @@ function isCaseNode(node) {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Checks if a given node appears as the value of a PropertyDefinition node.
|
|
34
|
-
* @param {ASTNode} node
|
|
34
|
+
* @param {ASTNode} node The node to check.
|
|
35
35
|
* @returns {boolean} `true` if the node is a PropertyDefinition value,
|
|
36
36
|
* false if not.
|
|
37
37
|
*/
|
|
@@ -77,8 +77,8 @@ function getLabel(node) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Checks whether
|
|
81
|
-
*
|
|
80
|
+
* Checks whether a given logical expression node takes different paths for the
|
|
81
|
+
* `true` and `false` cases.
|
|
82
82
|
* @param {ASTNode} node A node to check.
|
|
83
83
|
* @returns {boolean} `true` if the node is a test of a choice statement.
|
|
84
84
|
*/
|
|
@@ -107,8 +107,8 @@ function isForkingByTrueOrFalse(node) {
|
|
|
107
107
|
/**
|
|
108
108
|
* Gets the boolean value of a given literal node.
|
|
109
109
|
*
|
|
110
|
-
* This is used to detect
|
|
111
|
-
* Statements preceded by an
|
|
110
|
+
* This is used to detect infinite loops (e.g. `while (true) {}`).
|
|
111
|
+
* Statements preceded by an infinite loop are unreachable if the loop didn't
|
|
112
112
|
* have any `break` statement.
|
|
113
113
|
* @param {ASTNode} node A node to get.
|
|
114
114
|
* @returns {boolean|undefined} a boolean value if the node is a Literal node,
|
|
@@ -221,8 +221,8 @@ function forwardCurrentToHead(analyzer, node) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
* Updates the current segment with empty.
|
|
225
|
-
* This is called
|
|
224
|
+
* Updates the current segment with an empty array.
|
|
225
|
+
* This is called when a code path ends.
|
|
226
226
|
* @param {CodePathAnalyzer} analyzer The instance.
|
|
227
227
|
* @param {ASTNode} node The current AST node.
|
|
228
228
|
* @returns {void}
|
|
@@ -504,7 +504,7 @@ function processCodePathToEnter(analyzer, node) {
|
|
|
504
504
|
|
|
505
505
|
case "SwitchCase":
|
|
506
506
|
/*
|
|
507
|
-
* Fork if this node is after the
|
|
507
|
+
* Fork if this node is after the 1st node in `cases`.
|
|
508
508
|
* It's similar to `else` blocks.
|
|
509
509
|
* The next `test` node is processed in this path.
|
|
510
510
|
*/
|
|
@@ -626,10 +626,13 @@ function processCodePathToExit(analyzer, node) {
|
|
|
626
626
|
case "ImportExpression":
|
|
627
627
|
case "MemberExpression":
|
|
628
628
|
case "NewExpression":
|
|
629
|
-
case "YieldExpression":
|
|
630
629
|
state.makeFirstThrowablePathInTryBlock();
|
|
631
630
|
break;
|
|
632
631
|
|
|
632
|
+
case "YieldExpression":
|
|
633
|
+
state.makeYield();
|
|
634
|
+
break;
|
|
635
|
+
|
|
633
636
|
case "WhileStatement":
|
|
634
637
|
case "DoWhileStatement":
|
|
635
638
|
case "ForStatement":
|
|
@@ -724,7 +727,7 @@ function postprocess(analyzer, node) {
|
|
|
724
727
|
* a = () => {}
|
|
725
728
|
* }
|
|
726
729
|
*
|
|
727
|
-
* In this case,
|
|
730
|
+
* In this case, the ArrowFunctionExpression code path is closed first,
|
|
728
731
|
* and then we need to close the code path for the PropertyDefinition
|
|
729
732
|
* value.
|
|
730
733
|
*/
|
|
@@ -569,11 +569,10 @@ class TryContext {
|
|
|
569
569
|
this.position = "try";
|
|
570
570
|
|
|
571
571
|
/**
|
|
572
|
-
* If the `try` statement has a `finally` block, this affects how
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
* `
|
|
576
|
-
* to track it.
|
|
572
|
+
* If the `try` statement has a `finally` block, this affects how return-like
|
|
573
|
+
* leaving paths behave in the `try` block. Without `finally`, they behave as
|
|
574
|
+
* usual and don't require a fork; with `finally`, they fork into the
|
|
575
|
+
* `finally` block, so we need a fork context to track them.
|
|
577
576
|
* @type {ForkContext|null}
|
|
578
577
|
*/
|
|
579
578
|
this.returnedForkContext = hasFinalizer
|
|
@@ -1755,6 +1754,23 @@ class CodePathState {
|
|
|
1755
1754
|
this.forkContext.add(segments);
|
|
1756
1755
|
}
|
|
1757
1756
|
|
|
1757
|
+
/**
|
|
1758
|
+
* Records abrupt resumption paths from a suspended `yield` expression,
|
|
1759
|
+
* then splits normal post-`yield` continuation into a fresh segment.
|
|
1760
|
+
* @returns {void}
|
|
1761
|
+
*/
|
|
1762
|
+
makeYield() {
|
|
1763
|
+
const forkContext = this.forkContext;
|
|
1764
|
+
const leavingSegments = forkContext.head;
|
|
1765
|
+
|
|
1766
|
+
if (forkContext.reachable) {
|
|
1767
|
+
getReturnContext(this).returnedForkContext.add(leavingSegments);
|
|
1768
|
+
getThrowContext(this).thrownForkContext.add(leavingSegments);
|
|
1769
|
+
|
|
1770
|
+
forkContext.replaceHead(forkContext.makeNext(-1, -1));
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1758
1774
|
/**
|
|
1759
1775
|
* Makes a code path segment from the first throwable node to the `catch`
|
|
1760
1776
|
* block or the `finally` block.
|
|
@@ -100,9 +100,10 @@ class CodePath {
|
|
|
100
100
|
* Final code path segments that represent normal completion of the code path.
|
|
101
101
|
* For functions, this means both explicit `return` statements and implicit returns,
|
|
102
102
|
* such as the last reachable segment in a function that does not have an
|
|
103
|
-
* explicit `return` as this implicitly returns `undefined
|
|
104
|
-
*
|
|
105
|
-
*
|
|
103
|
+
* explicit `return` as this implicitly returns `undefined`, as well as
|
|
104
|
+
* return-like exits from suspended `yield` expressions. For scripts, modules,
|
|
105
|
+
* class field initializers, and class static blocks, this means all lines of
|
|
106
|
+
* code have been executed.
|
|
106
107
|
* These segments are also present in `finalSegments`.
|
|
107
108
|
* This is a passthrough to the underlying `CodePathState`.
|
|
108
109
|
* @type {CodePathSegment[]}
|
|
@@ -112,7 +113,8 @@ class CodePath {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
/**
|
|
115
|
-
* Final code path segments that represent `throw` statements
|
|
116
|
+
* Final code path segments that represent `throw` statements and throw-like
|
|
117
|
+
* exits from suspended `yield` expressions.
|
|
116
118
|
* This is a passthrough to the underlying `CodePathState`.
|
|
117
119
|
* These segments are also present in `finalSegments`.
|
|
118
120
|
* @type {CodePathSegment[]}
|
|
@@ -28,12 +28,18 @@ module.exports = {
|
|
|
28
28
|
},
|
|
29
29
|
|
|
30
30
|
create(context) {
|
|
31
|
+
const sourceCode = context.sourceCode;
|
|
32
|
+
|
|
31
33
|
return {
|
|
32
34
|
"NewExpression[callee.name='Promise'][arguments.0.async=true]"(
|
|
33
35
|
node,
|
|
34
36
|
) {
|
|
37
|
+
if (!sourceCode.isGlobalReference(node.callee)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
context.report({
|
|
36
|
-
node:
|
|
42
|
+
node: sourceCode.getFirstToken(
|
|
37
43
|
node.arguments[0],
|
|
38
44
|
token => token.value === "async",
|
|
39
45
|
),
|
package/lib/rules/no-shadow.js
CHANGED
|
@@ -612,7 +612,7 @@ module.exports = {
|
|
|
612
612
|
|
|
613
613
|
/**
|
|
614
614
|
* Checks the current context for shadowed variables.
|
|
615
|
-
* @param {Scope} scope
|
|
615
|
+
* @param {Scope} scope The scope to check for shadowed variables.
|
|
616
616
|
* @returns {void}
|
|
617
617
|
*/
|
|
618
618
|
function checkForShadows(scope) {
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
7
|
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
// Requirements
|
|
10
|
+
//------------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
|
13
|
+
|
|
8
14
|
//------------------------------------------------------------------------------
|
|
9
15
|
// Rule Definition
|
|
10
16
|
//------------------------------------------------------------------------------
|
|
@@ -13,6 +19,7 @@
|
|
|
13
19
|
module.exports = {
|
|
14
20
|
meta: {
|
|
15
21
|
type: "problem",
|
|
22
|
+
hasSuggestions: true,
|
|
16
23
|
|
|
17
24
|
docs: {
|
|
18
25
|
description: "Disallow unused private class members",
|
|
@@ -25,12 +32,168 @@ module.exports = {
|
|
|
25
32
|
messages: {
|
|
26
33
|
unusedPrivateClassMember:
|
|
27
34
|
"'{{classMemberName}}' is defined but never used.",
|
|
35
|
+
removeUnusedPrivateClassMember:
|
|
36
|
+
"Remove unused private class member '{{classMemberName}}'.",
|
|
28
37
|
},
|
|
29
38
|
},
|
|
30
39
|
|
|
31
40
|
create(context) {
|
|
41
|
+
const sourceCode = context.sourceCode;
|
|
32
42
|
const trackedClasses = [];
|
|
33
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Gets the start index of the line that contains a given token or node.
|
|
46
|
+
* @param {ASTNode|Token|Comment} nodeOrToken The token or node to check
|
|
47
|
+
* @returns {number} The line start index
|
|
48
|
+
*/
|
|
49
|
+
function getLineStartIndex(nodeOrToken) {
|
|
50
|
+
return nodeOrToken.range[0] - nodeOrToken.loc.start.column;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Checks whether a token or node starts on its own line, preceded only by whitespace.
|
|
55
|
+
* @param {ASTNode|Token|Comment} nodeOrToken The token or node to check
|
|
56
|
+
* @returns {boolean} Whether the token or node starts on its own line
|
|
57
|
+
*/
|
|
58
|
+
function startsOnOwnLine(nodeOrToken) {
|
|
59
|
+
return (
|
|
60
|
+
sourceCode.getTokenBefore(nodeOrToken, {
|
|
61
|
+
includeComments: true,
|
|
62
|
+
}).loc.end.line !== nodeOrToken.loc.start.line
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets leading comments that are directly attached to a class member.
|
|
68
|
+
* @param {ASTNode} classMemberNode The class member node
|
|
69
|
+
* @returns {Comment[]} Leading comments to remove with the member
|
|
70
|
+
*/
|
|
71
|
+
function getLeadingComments(classMemberNode) {
|
|
72
|
+
const commentsBefore =
|
|
73
|
+
sourceCode.getCommentsBefore(classMemberNode);
|
|
74
|
+
const lastNonLeadingCommentIndex = commentsBefore.findLastIndex(
|
|
75
|
+
(comment, index, self) => {
|
|
76
|
+
const next =
|
|
77
|
+
index < self.length - 1
|
|
78
|
+
? self[index + 1]
|
|
79
|
+
: classMemberNode;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
!startsOnOwnLine(comment) ||
|
|
83
|
+
next.loc.start.line - comment.loc.end.line > 1
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return commentsBefore.slice(lastNonLeadingCommentIndex + 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Checks whether a class member shares its line with another token.
|
|
93
|
+
* @param {ASTNode} classMemberNode The class member node
|
|
94
|
+
* @returns {boolean} Whether the member shares its line with another token
|
|
95
|
+
*/
|
|
96
|
+
function sharesLineWithAnotherToken(classMemberNode) {
|
|
97
|
+
const previousToken = sourceCode.getTokenBefore(classMemberNode);
|
|
98
|
+
const nextToken = sourceCode.getTokenAfter(classMemberNode);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
previousToken.loc.end.line === classMemberNode.loc.start.line ||
|
|
102
|
+
nextToken.loc.start.line === classMemberNode.loc.end.line
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets trailing comments that are directly attached to a class member.
|
|
108
|
+
* Same-line trailing comments are preserved when another token shares
|
|
109
|
+
* the line, because the comment might describe the remaining code rather
|
|
110
|
+
* than the unused member alone.
|
|
111
|
+
* @param {ASTNode} classMemberNode The class member node
|
|
112
|
+
* @returns {Comment[]} Trailing comments to remove with the member
|
|
113
|
+
*/
|
|
114
|
+
function getTrailingComments(classMemberNode) {
|
|
115
|
+
if (sharesLineWithAnotherToken(classMemberNode)) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return sourceCode
|
|
120
|
+
.getCommentsAfter(classMemberNode)
|
|
121
|
+
.filter(
|
|
122
|
+
comment =>
|
|
123
|
+
comment.loc.start.line === classMemberNode.loc.end.line,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the token after which a semicolon should be inserted when removing a class member.
|
|
129
|
+
* @param {ASTNode} classMemberNode The member that would be removed
|
|
130
|
+
* @returns {Token|null} The token after which a semicolon should be inserted, or null if no semicolon is needed
|
|
131
|
+
*/
|
|
132
|
+
function getSemicolonInsertionToken(classMemberNode) {
|
|
133
|
+
const nextToken = sourceCode.getTokenAfter(classMemberNode);
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
astUtils.canContinueExpressionInClassBody(nextToken) &&
|
|
137
|
+
astUtils.needsPrecedingSemicolon(sourceCode, classMemberNode)
|
|
138
|
+
) {
|
|
139
|
+
return sourceCode.getTokenBefore(classMemberNode);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets the replacement range for removing an unused class member.
|
|
147
|
+
* @param {ASTNode} classMemberNode The member that would be removed
|
|
148
|
+
* @returns {number[]} The text range to remove
|
|
149
|
+
*/
|
|
150
|
+
function getMemberRemovalRange(classMemberNode) {
|
|
151
|
+
const leadingComments = getLeadingComments(classMemberNode);
|
|
152
|
+
const trailingComments = getTrailingComments(classMemberNode);
|
|
153
|
+
const shouldRemoveLeadingComments =
|
|
154
|
+
leadingComments.length > 0 &&
|
|
155
|
+
!sharesLineWithAnotherToken(classMemberNode);
|
|
156
|
+
const lastItemToRemove =
|
|
157
|
+
trailingComments.length > 0
|
|
158
|
+
? trailingComments.at(-1)
|
|
159
|
+
: classMemberNode;
|
|
160
|
+
|
|
161
|
+
const previousToken = sourceCode.getTokenBefore(classMemberNode);
|
|
162
|
+
const nextToken = sourceCode.getTokenAfter(lastItemToRemove, {
|
|
163
|
+
includeComments: true,
|
|
164
|
+
});
|
|
165
|
+
const nextTokenStartsOnNewLine =
|
|
166
|
+
nextToken.loc.start.line > lastItemToRemove.loc.end.line;
|
|
167
|
+
const shouldRemoveOwnLine =
|
|
168
|
+
!shouldRemoveLeadingComments &&
|
|
169
|
+
startsOnOwnLine(classMemberNode) &&
|
|
170
|
+
nextTokenStartsOnNewLine;
|
|
171
|
+
let start = classMemberNode.range[0];
|
|
172
|
+
let end = lastItemToRemove.range[1];
|
|
173
|
+
|
|
174
|
+
if (shouldRemoveLeadingComments) {
|
|
175
|
+
start = nextTokenStartsOnNewLine
|
|
176
|
+
? getLineStartIndex(leadingComments[0])
|
|
177
|
+
: leadingComments[0].range[0];
|
|
178
|
+
end = nextTokenStartsOnNewLine
|
|
179
|
+
? getLineStartIndex(nextToken)
|
|
180
|
+
: nextToken.range[0];
|
|
181
|
+
} else if (shouldRemoveOwnLine) {
|
|
182
|
+
start = getLineStartIndex(classMemberNode);
|
|
183
|
+
end = getLineStartIndex(nextToken);
|
|
184
|
+
} else if (
|
|
185
|
+
previousToken.loc.end.line === classMemberNode.loc.start.line
|
|
186
|
+
) {
|
|
187
|
+
start = previousToken.range[1];
|
|
188
|
+
} else if (
|
|
189
|
+
nextToken.loc.start.line === lastItemToRemove.loc.end.line
|
|
190
|
+
) {
|
|
191
|
+
end = nextToken.range[0];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return [start, end];
|
|
195
|
+
}
|
|
196
|
+
|
|
34
197
|
/**
|
|
35
198
|
* Check whether the current node is in a write only assignment.
|
|
36
199
|
* @param {ASTNode} privateIdentifierNode Node referring to a private identifier
|
|
@@ -86,6 +249,7 @@ module.exports = {
|
|
|
86
249
|
if (bodyMember.key.type === "PrivateIdentifier") {
|
|
87
250
|
privateMembers.set(bodyMember.key.name, {
|
|
88
251
|
declaredNode: bodyMember,
|
|
252
|
+
hasReference: false,
|
|
89
253
|
isAccessor:
|
|
90
254
|
bodyMember.type === "MethodDefinition" &&
|
|
91
255
|
(bodyMember.kind === "set" ||
|
|
@@ -128,6 +292,8 @@ module.exports = {
|
|
|
128
292
|
return;
|
|
129
293
|
}
|
|
130
294
|
|
|
295
|
+
memberDefinition.hasReference = true;
|
|
296
|
+
|
|
131
297
|
/*
|
|
132
298
|
* Any usage of an accessor is considered a read, as the getter/setter can have
|
|
133
299
|
* side-effects in its definition.
|
|
@@ -199,11 +365,12 @@ module.exports = {
|
|
|
199
365
|
|
|
200
366
|
for (const [
|
|
201
367
|
classMemberName,
|
|
202
|
-
{ declaredNode, isUsed },
|
|
368
|
+
{ declaredNode, hasReference, isUsed },
|
|
203
369
|
] of unusedPrivateMembers.entries()) {
|
|
204
370
|
if (isUsed) {
|
|
205
371
|
continue;
|
|
206
372
|
}
|
|
373
|
+
|
|
207
374
|
context.report({
|
|
208
375
|
node: declaredNode,
|
|
209
376
|
loc: declaredNode.key.loc,
|
|
@@ -211,6 +378,39 @@ module.exports = {
|
|
|
211
378
|
data: {
|
|
212
379
|
classMemberName: `#${classMemberName}`,
|
|
213
380
|
},
|
|
381
|
+
suggest: [
|
|
382
|
+
{
|
|
383
|
+
messageId: "removeUnusedPrivateClassMember",
|
|
384
|
+
data: {
|
|
385
|
+
classMemberName: `#${classMemberName}`,
|
|
386
|
+
},
|
|
387
|
+
*fix(fixer) {
|
|
388
|
+
if (hasReference) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const removalRange =
|
|
393
|
+
getMemberRemovalRange(declaredNode);
|
|
394
|
+
const semicolonInsertionToken =
|
|
395
|
+
getSemicolonInsertionToken(
|
|
396
|
+
declaredNode,
|
|
397
|
+
);
|
|
398
|
+
const removalFix = fixer.replaceTextRange(
|
|
399
|
+
removalRange,
|
|
400
|
+
"",
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
yield removalFix;
|
|
404
|
+
|
|
405
|
+
if (semicolonInsertionToken) {
|
|
406
|
+
yield fixer.insertTextAfter(
|
|
407
|
+
semicolonInsertionToken,
|
|
408
|
+
";",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
],
|
|
214
414
|
});
|
|
215
415
|
}
|
|
216
416
|
},
|
|
@@ -33,7 +33,7 @@ function isConcatOperatorToken(token) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Gets the right most node on the left side of a BinaryExpression with + operator.
|
|
37
37
|
* @param {ASTNode} node A BinaryExpression node to check.
|
|
38
38
|
* @returns {ASTNode} node
|
|
39
39
|
*/
|
|
@@ -47,7 +47,7 @@ function getLeft(node) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Gets the left most node on the right side of a BinaryExpression with + operator.
|
|
51
51
|
* @param {ASTNode} node A BinaryExpression node to check.
|
|
52
52
|
* @returns {ASTNode} node
|
|
53
53
|
*/
|
|
@@ -237,8 +237,9 @@ module.exports = {
|
|
|
237
237
|
const nextToken =
|
|
238
238
|
sourceCode.getTokenAfter(node);
|
|
239
239
|
const addSemiColon =
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
astUtils.canContinueExpressionInClassBody(
|
|
241
|
+
nextToken,
|
|
242
|
+
) &&
|
|
242
243
|
astUtils.needsPrecedingSemicolon(
|
|
243
244
|
sourceCode,
|
|
244
245
|
node,
|
|
@@ -110,12 +110,14 @@ module.exports = {
|
|
|
110
110
|
*/
|
|
111
111
|
const nextToken = sourceCode.getTokenAfter(asyncToken);
|
|
112
112
|
const addSemiColon =
|
|
113
|
-
nextToken
|
|
114
|
-
(nextToken.value === "[" || nextToken.value === "(") &&
|
|
115
|
-
(nodeWithAsyncKeyword.type === "MethodDefinition" ||
|
|
113
|
+
((astUtils.isOpeningParenToken(nextToken) &&
|
|
116
114
|
astUtils.isStartOfExpressionStatement(
|
|
117
115
|
nodeWithAsyncKeyword,
|
|
118
|
-
))
|
|
116
|
+
)) ||
|
|
117
|
+
(nodeWithAsyncKeyword.type === "MethodDefinition" &&
|
|
118
|
+
astUtils.canContinueExpressionInClassBody(
|
|
119
|
+
nextToken,
|
|
120
|
+
))) &&
|
|
119
121
|
astUtils.needsPrecedingSemicolon(
|
|
120
122
|
sourceCode,
|
|
121
123
|
nodeWithAsyncKeyword,
|
|
@@ -1258,12 +1258,31 @@ function isStartOfExpressionStatement(node) {
|
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
1260
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1261
|
+
* Checks whether a token can cause continuation of a preceding expression
|
|
1262
|
+
* (for example, of a class field initializer expression) in a class body.
|
|
1263
|
+
* This function checks specifically for tokens that can appear at the start
|
|
1264
|
+
* of a class member: `[` (computed key), `*` (generator method), `in` or `instanceof` (valid keys)
|
|
1265
|
+
* Without a preceding semicolon, these tokens would be parsed as index access or operators.
|
|
1266
|
+
* @param {Token} token The token to check.
|
|
1267
|
+
* @returns {boolean} Whether the token can cause continuation of a preceding expression.
|
|
1268
|
+
*/
|
|
1269
|
+
function canContinueExpressionInClassBody(token) {
|
|
1270
|
+
return (
|
|
1271
|
+
(token.type === "Punctuator" &&
|
|
1272
|
+
(token.value === "[" || token.value === "*")) ||
|
|
1273
|
+
// Different parsers may return these tokens as either "Identifier" or "Keyword"
|
|
1274
|
+
((token.type === "Identifier" || token.type === "Keyword") &&
|
|
1275
|
+
(token.value === "in" || token.value === "instanceof"))
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Determines whether an opening parenthesis `(`, bracket `[`, asterisk `*`, or backtick ``` ` ``` needs to be preceded by a semicolon.
|
|
1281
|
+
* This opening parenthesis or bracket should be at the start of an `ExpressionStatement`, a `MethodDefinition`, a `PropertyDefinition`,
|
|
1282
|
+
* or at the start of the body of an `ArrowFunctionExpression`.
|
|
1264
1283
|
* @type {(sourceCode: SourceCode, node: ASTNode) => boolean}
|
|
1265
1284
|
* @param {SourceCode} sourceCode The source code object.
|
|
1266
|
-
* @param {ASTNode} node A node at the position where an opening parenthesis or
|
|
1285
|
+
* @param {ASTNode} node A node at the position where an opening parenthesis, bracket, or asterisk will be inserted.
|
|
1267
1286
|
* @returns {boolean} Whether a semicolon is required before the opening parenthesis or bracket.
|
|
1268
1287
|
*/
|
|
1269
1288
|
let needsPrecedingSemicolon;
|
|
@@ -1354,6 +1373,18 @@ let needsPrecedingSemicolon;
|
|
|
1354
1373
|
|
|
1355
1374
|
const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
|
|
1356
1375
|
|
|
1376
|
+
// Uninitialized class fields don't need a semicolon
|
|
1377
|
+
if (
|
|
1378
|
+
// Key
|
|
1379
|
+
(prevNode.parent.type === "PropertyDefinition" &&
|
|
1380
|
+
prevNode.parent.key === prevNode) ||
|
|
1381
|
+
// Closing bracket of a computed key
|
|
1382
|
+
(prevNode.type === "PropertyDefinition" &&
|
|
1383
|
+
isClosingBracketToken(prevToken))
|
|
1384
|
+
) {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1357
1388
|
if (
|
|
1358
1389
|
prevNode.type === "TSDeclareFunction" ||
|
|
1359
1390
|
prevNode.parent.type === "TSImportEqualsDeclaration" ||
|
|
@@ -2842,6 +2873,7 @@ module.exports = {
|
|
|
2842
2873
|
isTopLevelExpressionStatement,
|
|
2843
2874
|
isDirective,
|
|
2844
2875
|
isStartOfExpressionStatement,
|
|
2876
|
+
canContinueExpressionInClassBody,
|
|
2845
2877
|
needsPrecedingSemicolon,
|
|
2846
2878
|
isImportAttributeKey,
|
|
2847
2879
|
getOpeningParenOfParams,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.3.0",
|
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -72,6 +72,8 @@
|
|
|
72
72
|
"test": "node Makefile.js test",
|
|
73
73
|
"test:browser": "node Makefile.js cypress",
|
|
74
74
|
"test:cli": "mocha",
|
|
75
|
+
"test:ecosystem": "node tools/test-ecosystem/index.mjs",
|
|
76
|
+
"test:ecosystem:update": "node tools/test-ecosystem/update.mjs",
|
|
75
77
|
"test:emfile": "node tools/check-emfile-handling.js",
|
|
76
78
|
"test:fuzz": "node Makefile.js fuzz",
|
|
77
79
|
"test:performance": "node Makefile.js perf",
|
|
@@ -119,10 +121,10 @@
|
|
|
119
121
|
"dependencies": {
|
|
120
122
|
"@eslint-community/eslint-utils": "^4.8.0",
|
|
121
123
|
"@eslint-community/regexpp": "^4.12.2",
|
|
122
|
-
"@eslint/config-array": "^0.23.
|
|
123
|
-
"@eslint/config-helpers": "^0.5.
|
|
124
|
-
"@eslint/core": "^1.2.
|
|
125
|
-
"@eslint/plugin-kit": "^0.7.
|
|
124
|
+
"@eslint/config-array": "^0.23.5",
|
|
125
|
+
"@eslint/config-helpers": "^0.5.5",
|
|
126
|
+
"@eslint/core": "^1.2.1",
|
|
127
|
+
"@eslint/plugin-kit": "^0.7.1",
|
|
126
128
|
"@humanfs/node": "^0.16.6",
|
|
127
129
|
"@humanwhocodes/module-importer": "^1.0.1",
|
|
128
130
|
"@humanwhocodes/retry": "^0.4.2",
|
|
@@ -157,7 +159,7 @@
|
|
|
157
159
|
"@eslint/json": "^1.2.0",
|
|
158
160
|
"@types/esquery": "^1.5.4",
|
|
159
161
|
"@types/node": "^22.13.14",
|
|
160
|
-
"@typescript-eslint/parser": "^8.
|
|
162
|
+
"@typescript-eslint/parser": "^8.58.2",
|
|
161
163
|
"babel-loader": "^8.0.5",
|
|
162
164
|
"c8": "^11.0.0",
|
|
163
165
|
"chai": "^4.0.1",
|
|
@@ -196,7 +198,7 @@
|
|
|
196
198
|
"mocha": "^11.7.1",
|
|
197
199
|
"node-polyfill-webpack-plugin": "^1.0.3",
|
|
198
200
|
"npm-license": "^0.3.3",
|
|
199
|
-
"prettier": "3.8.
|
|
201
|
+
"prettier": "3.8.3",
|
|
200
202
|
"progress": "^2.0.3",
|
|
201
203
|
"proxyquire": "^2.0.1",
|
|
202
204
|
"recast": "^0.23.0",
|
|
@@ -204,7 +206,7 @@
|
|
|
204
206
|
"semver": "^7.5.3",
|
|
205
207
|
"shelljs": "^0.10.0",
|
|
206
208
|
"sinon": "^11.0.0",
|
|
207
|
-
"typescript": "^
|
|
209
|
+
"typescript": "^6.0.3",
|
|
208
210
|
"webpack": "^5.23.0",
|
|
209
211
|
"webpack-cli": "^4.5.0",
|
|
210
212
|
"yorkie": "^2.0.0"
|