eslint 9.26.0 → 9.28.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 +7 -2
- package/bin/eslint.js +7 -11
- package/conf/rule-type-list.json +2 -1
- package/lib/cli-engine/cli-engine.js +7 -7
- package/lib/cli.js +19 -16
- package/lib/config/config-loader.js +42 -39
- package/lib/config/config.js +362 -16
- package/lib/eslint/eslint-helpers.js +3 -1
- package/lib/eslint/eslint.js +31 -13
- package/lib/eslint/legacy-eslint.js +6 -6
- package/lib/languages/js/source-code/source-code.js +40 -6
- package/lib/linter/apply-disable-directives.js +1 -1
- package/lib/linter/file-context.js +11 -0
- package/lib/linter/linter.js +102 -140
- package/lib/linter/report-translator.js +2 -1
- package/lib/linter/{node-event-generator.js → source-code-traverser.js} +143 -87
- package/lib/options.js +7 -0
- package/lib/rule-tester/rule-tester.js +3 -3
- package/lib/rules/func-style.js +57 -7
- package/lib/rules/index.js +1 -0
- package/lib/rules/max-params.js +32 -7
- package/lib/rules/no-array-constructor.js +51 -1
- package/lib/rules/no-implicit-globals.js +31 -15
- package/lib/rules/no-magic-numbers.js +98 -5
- package/lib/rules/no-shadow.js +262 -6
- package/lib/rules/no-unassigned-vars.js +80 -0
- package/lib/rules/no-use-before-define.js +97 -1
- package/lib/rules/no-useless-escape.js +24 -2
- package/lib/rules/prefer-arrow-callback.js +9 -0
- package/lib/rules/prefer-named-capture-group.js +7 -1
- package/lib/services/processor-service.js +1 -1
- package/lib/services/suppressions-service.js +5 -3
- package/lib/services/warning-service.js +85 -0
- package/lib/shared/flags.js +1 -0
- package/lib/types/index.d.ts +132 -9
- package/lib/types/rules.d.ts +66 -3
- package/package.json +11 -11
- package/lib/config/flat-config-helpers.js +0 -128
- package/lib/config/rule-validator.js +0 -199
- package/lib/mcp/mcp-server.js +0 -66
- package/lib/shared/types.js +0 -229
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @fileoverview
|
3
|
-
* @author
|
2
|
+
* @fileoverview Traverser for SourceCode objects.
|
3
|
+
* @author Nicholas C. Zakas
|
4
4
|
*/
|
5
5
|
|
6
6
|
"use strict";
|
@@ -10,6 +10,7 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
const { parse, matches } = require("./esquery");
|
13
|
+
const vk = require("eslint-visitor-keys");
|
13
14
|
|
14
15
|
//-----------------------------------------------------------------------------
|
15
16
|
// Typedefs
|
@@ -17,12 +18,16 @@ const { parse, matches } = require("./esquery");
|
|
17
18
|
|
18
19
|
/**
|
19
20
|
* @import { ESQueryParsedSelector } from "./esquery.js";
|
21
|
+
* @import { Language, SourceCode } from "@eslint/core";
|
20
22
|
*/
|
21
23
|
|
22
24
|
//-----------------------------------------------------------------------------
|
23
25
|
// Helpers
|
24
26
|
//-----------------------------------------------------------------------------
|
25
27
|
|
28
|
+
const STEP_KIND_VISIT = 1;
|
29
|
+
const STEP_KIND_CALL = 2;
|
30
|
+
|
26
31
|
/**
|
27
32
|
* Compares two ESQuery selectors by specificity.
|
28
33
|
* @param {ESQueryParsedSelector} a The first selector to compare.
|
@@ -33,69 +38,10 @@ function compareSpecificity(a, b) {
|
|
33
38
|
return a.compare(b);
|
34
39
|
}
|
35
40
|
|
36
|
-
//------------------------------------------------------------------------------
|
37
|
-
// Public Interface
|
38
|
-
//------------------------------------------------------------------------------
|
39
|
-
|
40
41
|
/**
|
41
|
-
*
|
42
|
-
* This implements below interface.
|
43
|
-
*
|
44
|
-
* ```ts
|
45
|
-
* interface EventGenerator {
|
46
|
-
* emitter: SafeEmitter;
|
47
|
-
* enterNode(node: ASTNode): void;
|
48
|
-
* leaveNode(node: ASTNode): void;
|
49
|
-
* }
|
50
|
-
* ```
|
42
|
+
* Helper to wrap ESQuery operations.
|
51
43
|
*/
|
52
|
-
class
|
53
|
-
/**
|
54
|
-
* The emitter to use during traversal.
|
55
|
-
* @type {SafeEmitter}
|
56
|
-
*/
|
57
|
-
emitter;
|
58
|
-
|
59
|
-
/**
|
60
|
-
* The options for `esquery` to use during matching.
|
61
|
-
* @type {ESQueryOptions}
|
62
|
-
*/
|
63
|
-
esqueryOptions;
|
64
|
-
|
65
|
-
/**
|
66
|
-
* The ancestry of the currently visited node.
|
67
|
-
* @type {ASTNode[]}
|
68
|
-
*/
|
69
|
-
currentAncestry = [];
|
70
|
-
|
71
|
-
/**
|
72
|
-
* A map of node type to selectors targeting that node type on the
|
73
|
-
* enter phase of traversal.
|
74
|
-
* @type {Map<string, ESQueryParsedSelector[]>}
|
75
|
-
*/
|
76
|
-
enterSelectorsByNodeType = new Map();
|
77
|
-
|
78
|
-
/**
|
79
|
-
* A map of node type to selectors targeting that node type on the
|
80
|
-
* exit phase of traversal.
|
81
|
-
* @type {Map<string, ESQueryParsedSelector[]>}
|
82
|
-
*/
|
83
|
-
exitSelectorsByNodeType = new Map();
|
84
|
-
|
85
|
-
/**
|
86
|
-
* An array of selectors that match any node type on the
|
87
|
-
* enter phase of traversal.
|
88
|
-
* @type {ESQueryParsedSelector[]}
|
89
|
-
*/
|
90
|
-
anyTypeEnterSelectors = [];
|
91
|
-
|
92
|
-
/**
|
93
|
-
* An array of selectors that match any node type on the
|
94
|
-
* exit phase of traversal.
|
95
|
-
* @type {ESQueryParsedSelector[]}
|
96
|
-
*/
|
97
|
-
anyTypeExitSelectors = [];
|
98
|
-
|
44
|
+
class ESQueryHelper {
|
99
45
|
/**
|
100
46
|
* @param {SafeEmitter} emitter
|
101
47
|
* An SafeEmitter which is the destination of events. This emitter must already
|
@@ -105,9 +51,46 @@ class NodeEventGenerator {
|
|
105
51
|
* @returns {NodeEventGenerator} new instance
|
106
52
|
*/
|
107
53
|
constructor(emitter, esqueryOptions) {
|
54
|
+
/**
|
55
|
+
* The emitter to use during traversal.
|
56
|
+
* @type {SafeEmitter}
|
57
|
+
*/
|
108
58
|
this.emitter = emitter;
|
59
|
+
|
60
|
+
/**
|
61
|
+
* The options for `esquery` to use during matching.
|
62
|
+
* @type {ESQueryOptions}
|
63
|
+
*/
|
109
64
|
this.esqueryOptions = esqueryOptions;
|
110
65
|
|
66
|
+
/**
|
67
|
+
* A map of node type to selectors targeting that node type on the
|
68
|
+
* enter phase of traversal.
|
69
|
+
* @type {Map<string, ESQueryParsedSelector[]>}
|
70
|
+
*/
|
71
|
+
this.enterSelectorsByNodeType = new Map();
|
72
|
+
|
73
|
+
/**
|
74
|
+
* A map of node type to selectors targeting that node type on the
|
75
|
+
* exit phase of traversal.
|
76
|
+
* @type {Map<string, ESQueryParsedSelector[]>}
|
77
|
+
*/
|
78
|
+
this.exitSelectorsByNodeType = new Map();
|
79
|
+
|
80
|
+
/**
|
81
|
+
* An array of selectors that match any node type on the
|
82
|
+
* enter phase of traversal.
|
83
|
+
* @type {ESQueryParsedSelector[]}
|
84
|
+
*/
|
85
|
+
this.anyTypeEnterSelectors = [];
|
86
|
+
|
87
|
+
/**
|
88
|
+
* An array of selectors that match any node type on the
|
89
|
+
* exit phase of traversal.
|
90
|
+
* @type {ESQueryParsedSelector[]}
|
91
|
+
*/
|
92
|
+
this.anyTypeExitSelectors = [];
|
93
|
+
|
111
94
|
emitter.eventNames().forEach(rawSelector => {
|
112
95
|
const selector = parse(rawSelector);
|
113
96
|
|
@@ -156,18 +139,12 @@ class NodeEventGenerator {
|
|
156
139
|
/**
|
157
140
|
* Checks a selector against a node, and emits it if it matches
|
158
141
|
* @param {ASTNode} node The node to check
|
142
|
+
* @param {ASTNode[]} ancestry The ancestry of the node being checked.
|
159
143
|
* @param {ESQueryParsedSelector} selector An AST selector descriptor
|
160
144
|
* @returns {void}
|
161
145
|
*/
|
162
|
-
applySelector(node, selector) {
|
163
|
-
if (
|
164
|
-
matches(
|
165
|
-
node,
|
166
|
-
selector.root,
|
167
|
-
this.currentAncestry,
|
168
|
-
this.esqueryOptions,
|
169
|
-
)
|
170
|
-
) {
|
146
|
+
#applySelector(node, ancestry, selector) {
|
147
|
+
if (matches(node, selector.root, ancestry, this.esqueryOptions)) {
|
171
148
|
this.emitter.emit(selector.source, node);
|
172
149
|
}
|
173
150
|
}
|
@@ -175,10 +152,11 @@ class NodeEventGenerator {
|
|
175
152
|
/**
|
176
153
|
* Applies all appropriate selectors to a node, in specificity order
|
177
154
|
* @param {ASTNode} node The node to check
|
155
|
+
* @param {ASTNode[]} ancestry The ancestry of the node being checked.
|
178
156
|
* @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
|
179
157
|
* @returns {void}
|
180
158
|
*/
|
181
|
-
applySelectors(node, isExit) {
|
159
|
+
applySelectors(node, ancestry, isExit) {
|
182
160
|
const nodeTypeKey = this.esqueryOptions?.nodeTypeKey || "type";
|
183
161
|
|
184
162
|
/*
|
@@ -218,39 +196,117 @@ class NodeEventGenerator {
|
|
218
196
|
selectorsByNodeType[selectorsByNodeTypeIndex],
|
219
197
|
) < 0)
|
220
198
|
) {
|
221
|
-
this
|
199
|
+
this.#applySelector(
|
222
200
|
node,
|
201
|
+
ancestry,
|
223
202
|
anyTypeSelectors[anyTypeSelectorsIndex++],
|
224
203
|
);
|
225
204
|
} else {
|
226
205
|
// otherwise apply the node type selector
|
227
|
-
this
|
206
|
+
this.#applySelector(
|
228
207
|
node,
|
208
|
+
ancestry,
|
229
209
|
selectorsByNodeType[selectorsByNodeTypeIndex++],
|
230
210
|
);
|
231
211
|
}
|
232
212
|
}
|
233
213
|
}
|
214
|
+
}
|
215
|
+
|
216
|
+
//------------------------------------------------------------------------------
|
217
|
+
// Public Interface
|
218
|
+
//------------------------------------------------------------------------------
|
234
219
|
|
220
|
+
/**
|
221
|
+
* Traverses source code and ensures that visitor methods are called when
|
222
|
+
* entering and leaving each node.
|
223
|
+
*/
|
224
|
+
class SourceCodeTraverser {
|
235
225
|
/**
|
236
|
-
*
|
237
|
-
* @
|
238
|
-
|
226
|
+
* The language of the source code being traversed.
|
227
|
+
* @type {Language}
|
228
|
+
*/
|
229
|
+
#language;
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Map of languages to instances of this class.
|
233
|
+
* @type {WeakMap<Language, SourceCodeTraverser>}
|
234
|
+
*/
|
235
|
+
static instances = new WeakMap();
|
236
|
+
|
237
|
+
/**
|
238
|
+
* Creates a new instance.
|
239
|
+
* @param {Language} language The language of the source code being traversed.
|
239
240
|
*/
|
240
|
-
|
241
|
-
this
|
242
|
-
|
241
|
+
constructor(language) {
|
242
|
+
this.#language = language;
|
243
|
+
}
|
244
|
+
|
245
|
+
static getInstance(language) {
|
246
|
+
if (!this.instances.has(language)) {
|
247
|
+
this.instances.set(language, new this(language));
|
248
|
+
}
|
249
|
+
|
250
|
+
return this.instances.get(language);
|
243
251
|
}
|
244
252
|
|
245
253
|
/**
|
246
|
-
*
|
247
|
-
* @param {
|
254
|
+
* Traverses the given source code synchronously.
|
255
|
+
* @param {SourceCode} sourceCode The source code to traverse.
|
256
|
+
* @param {SafeEmitter} emitter The emitter to use for events.
|
257
|
+
* @param {Object} options Options for traversal.
|
258
|
+
* @param {ReturnType<SourceCode["traverse"]>} options.steps The steps to take during traversal.
|
248
259
|
* @returns {void}
|
260
|
+
* @throws {Error} If an error occurs during traversal.
|
249
261
|
*/
|
250
|
-
|
251
|
-
|
252
|
-
|
262
|
+
traverseSync(sourceCode, emitter, { steps } = {}) {
|
263
|
+
const esquery = new ESQueryHelper(emitter, {
|
264
|
+
visitorKeys: sourceCode.visitorKeys ?? this.#language.visitorKeys,
|
265
|
+
fallback: vk.getKeys,
|
266
|
+
matchClass: this.#language.matchesSelectorClass ?? (() => false),
|
267
|
+
nodeTypeKey: this.#language.nodeTypeKey,
|
268
|
+
});
|
269
|
+
|
270
|
+
const currentAncestry = [];
|
271
|
+
|
272
|
+
for (const step of steps ?? sourceCode.traverse()) {
|
273
|
+
switch (step.kind) {
|
274
|
+
case STEP_KIND_VISIT: {
|
275
|
+
try {
|
276
|
+
if (step.phase === 1) {
|
277
|
+
esquery.applySelectors(
|
278
|
+
step.target,
|
279
|
+
currentAncestry,
|
280
|
+
false,
|
281
|
+
);
|
282
|
+
currentAncestry.unshift(step.target);
|
283
|
+
} else {
|
284
|
+
currentAncestry.shift();
|
285
|
+
esquery.applySelectors(
|
286
|
+
step.target,
|
287
|
+
currentAncestry,
|
288
|
+
true,
|
289
|
+
);
|
290
|
+
}
|
291
|
+
} catch (err) {
|
292
|
+
err.currentNode = step.target;
|
293
|
+
throw err;
|
294
|
+
}
|
295
|
+
break;
|
296
|
+
}
|
297
|
+
|
298
|
+
case STEP_KIND_CALL: {
|
299
|
+
emitter.emit(step.target, ...step.args);
|
300
|
+
break;
|
301
|
+
}
|
302
|
+
|
303
|
+
default:
|
304
|
+
throw new Error(
|
305
|
+
`Invalid traversal step found: "${step.kind}".`,
|
306
|
+
);
|
307
|
+
}
|
308
|
+
}
|
253
309
|
}
|
254
310
|
}
|
255
311
|
|
256
|
-
module.exports =
|
312
|
+
module.exports = { SourceCodeTraverser };
|
package/lib/options.js
CHANGED
@@ -66,6 +66,7 @@ const optionator = require("optionator");
|
|
66
66
|
* @property {string[]} [suppressRule] Suppress specific rules
|
67
67
|
* @property {string} [suppressionsLocation] Path to the suppressions file or directory
|
68
68
|
* @property {boolean} [pruneSuppressions] Prune unused suppressions
|
69
|
+
* @property {boolean} [passOnUnprunedSuppressions] Ignore unused suppressions
|
69
70
|
*/
|
70
71
|
|
71
72
|
//------------------------------------------------------------------------------
|
@@ -449,6 +450,12 @@ module.exports = function (usingFlatConfig) {
|
|
449
450
|
default: "false",
|
450
451
|
description: "Prune unused suppressions",
|
451
452
|
},
|
453
|
+
{
|
454
|
+
option: "pass-on-unpruned-suppressions",
|
455
|
+
type: "Boolean",
|
456
|
+
default: "false",
|
457
|
+
description: "Ignore unused suppressions",
|
458
|
+
},
|
452
459
|
{
|
453
460
|
heading: "Miscellaneous",
|
454
461
|
},
|
@@ -15,7 +15,7 @@ const assert = require("node:assert"),
|
|
15
15
|
path = require("node:path"),
|
16
16
|
equal = require("fast-deep-equal"),
|
17
17
|
Traverser = require("../shared/traverser"),
|
18
|
-
{
|
18
|
+
{ Config } = require("../config/config"),
|
19
19
|
{ Linter, SourceCodeFixer } = require("../linter"),
|
20
20
|
{ interpolate, getPlaceholderMatcher } = require("../linter/interpolate"),
|
21
21
|
stringify = require("json-stable-stringify-without-jsonify");
|
@@ -41,7 +41,7 @@ const { SourceCode } = require("../languages/js/source-code");
|
|
41
41
|
|
42
42
|
/** @import { LanguageOptions, RuleDefinition } from "@eslint/core" */
|
43
43
|
|
44
|
-
/** @typedef {import("../
|
44
|
+
/** @typedef {import("../types").Linter.Parser} Parser */
|
45
45
|
|
46
46
|
/**
|
47
47
|
* A test case that is expected to pass lint.
|
@@ -767,7 +767,7 @@ class RuleTester {
|
|
767
767
|
let schema;
|
768
768
|
|
769
769
|
try {
|
770
|
-
schema = getRuleOptionsSchema(rule);
|
770
|
+
schema = Config.getRuleOptionsSchema(rule);
|
771
771
|
} catch (err) {
|
772
772
|
err.message += metaSchemaDescription;
|
773
773
|
throw err;
|
package/lib/rules/func-style.js
CHANGED
@@ -11,12 +11,15 @@
|
|
11
11
|
/** @type {import('../types').Rule.RuleModule} */
|
12
12
|
module.exports = {
|
13
13
|
meta: {
|
14
|
+
dialects: ["javascript", "typescript"],
|
15
|
+
language: "javascript",
|
14
16
|
type: "suggestion",
|
15
17
|
|
16
18
|
defaultOptions: [
|
17
19
|
"expression",
|
18
20
|
{
|
19
21
|
allowArrowFunctions: false,
|
22
|
+
allowTypeAnnotation: false,
|
20
23
|
overrides: {},
|
21
24
|
},
|
22
25
|
],
|
@@ -39,6 +42,9 @@ module.exports = {
|
|
39
42
|
allowArrowFunctions: {
|
40
43
|
type: "boolean",
|
41
44
|
},
|
45
|
+
allowTypeAnnotation: {
|
46
|
+
type: "boolean",
|
47
|
+
},
|
42
48
|
overrides: {
|
43
49
|
type: "object",
|
44
50
|
properties: {
|
@@ -60,11 +66,49 @@ module.exports = {
|
|
60
66
|
},
|
61
67
|
|
62
68
|
create(context) {
|
63
|
-
const [style, { allowArrowFunctions, overrides }] =
|
69
|
+
const [style, { allowArrowFunctions, allowTypeAnnotation, overrides }] =
|
70
|
+
context.options;
|
64
71
|
const enforceDeclarations = style === "declaration";
|
65
72
|
const { namedExports: exportFunctionStyle } = overrides;
|
66
73
|
const stack = [];
|
67
74
|
|
75
|
+
/**
|
76
|
+
* Checks if a function declaration is part of an overloaded function
|
77
|
+
* @param {ASTNode} node The function declaration node to check
|
78
|
+
* @returns {boolean} True if the function is overloaded
|
79
|
+
*/
|
80
|
+
function isOverloadedFunction(node) {
|
81
|
+
const functionName = node.id.name;
|
82
|
+
|
83
|
+
if (node.parent.type === "ExportNamedDeclaration") {
|
84
|
+
return node.parent.parent.body.some(
|
85
|
+
member =>
|
86
|
+
member.type === "ExportNamedDeclaration" &&
|
87
|
+
member.declaration?.type === "TSDeclareFunction" &&
|
88
|
+
member.declaration.id.name === functionName,
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
if (node.parent.type === "SwitchCase") {
|
93
|
+
return node.parent.parent.cases.some(switchCase =>
|
94
|
+
switchCase.consequent.some(
|
95
|
+
member =>
|
96
|
+
member.type === "TSDeclareFunction" &&
|
97
|
+
member.id.name === functionName,
|
98
|
+
),
|
99
|
+
);
|
100
|
+
}
|
101
|
+
|
102
|
+
return (
|
103
|
+
Array.isArray(node.parent.body) &&
|
104
|
+
node.parent.body.some(
|
105
|
+
member =>
|
106
|
+
member.type === "TSDeclareFunction" &&
|
107
|
+
member.id.name === functionName,
|
108
|
+
)
|
109
|
+
);
|
110
|
+
}
|
111
|
+
|
68
112
|
const nodesToCheck = {
|
69
113
|
FunctionDeclaration(node) {
|
70
114
|
stack.push(false);
|
@@ -73,14 +117,16 @@ module.exports = {
|
|
73
117
|
!enforceDeclarations &&
|
74
118
|
node.parent.type !== "ExportDefaultDeclaration" &&
|
75
119
|
(typeof exportFunctionStyle === "undefined" ||
|
76
|
-
node.parent.type !== "ExportNamedDeclaration")
|
120
|
+
node.parent.type !== "ExportNamedDeclaration") &&
|
121
|
+
!isOverloadedFunction(node)
|
77
122
|
) {
|
78
123
|
context.report({ node, messageId: "expression" });
|
79
124
|
}
|
80
125
|
|
81
126
|
if (
|
82
127
|
node.parent.type === "ExportNamedDeclaration" &&
|
83
|
-
exportFunctionStyle === "expression"
|
128
|
+
exportFunctionStyle === "expression" &&
|
129
|
+
!isOverloadedFunction(node)
|
84
130
|
) {
|
85
131
|
context.report({ node, messageId: "expression" });
|
86
132
|
}
|
@@ -97,7 +143,8 @@ module.exports = {
|
|
97
143
|
node.parent.type === "VariableDeclarator" &&
|
98
144
|
(typeof exportFunctionStyle === "undefined" ||
|
99
145
|
node.parent.parent.parent.type !==
|
100
|
-
"ExportNamedDeclaration")
|
146
|
+
"ExportNamedDeclaration") &&
|
147
|
+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
|
101
148
|
) {
|
102
149
|
context.report({
|
103
150
|
node: node.parent,
|
@@ -109,7 +156,8 @@ module.exports = {
|
|
109
156
|
node.parent.type === "VariableDeclarator" &&
|
110
157
|
node.parent.parent.parent.type ===
|
111
158
|
"ExportNamedDeclaration" &&
|
112
|
-
exportFunctionStyle === "declaration"
|
159
|
+
exportFunctionStyle === "declaration" &&
|
160
|
+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
|
113
161
|
) {
|
114
162
|
context.report({
|
115
163
|
node: node.parent,
|
@@ -144,7 +192,8 @@ module.exports = {
|
|
144
192
|
enforceDeclarations &&
|
145
193
|
(typeof exportFunctionStyle === "undefined" ||
|
146
194
|
node.parent.parent.parent.type !==
|
147
|
-
"ExportNamedDeclaration")
|
195
|
+
"ExportNamedDeclaration") &&
|
196
|
+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
|
148
197
|
) {
|
149
198
|
context.report({
|
150
199
|
node: node.parent,
|
@@ -155,7 +204,8 @@ module.exports = {
|
|
155
204
|
if (
|
156
205
|
node.parent.parent.parent.type ===
|
157
206
|
"ExportNamedDeclaration" &&
|
158
|
-
exportFunctionStyle === "declaration"
|
207
|
+
exportFunctionStyle === "declaration" &&
|
208
|
+
!(allowTypeAnnotation && node.parent.id.typeAnnotation)
|
159
209
|
) {
|
160
210
|
context.report({
|
161
211
|
node: node.parent,
|
package/lib/rules/index.js
CHANGED
@@ -225,6 +225,7 @@ module.exports = new LazyLoadingRuleMap(
|
|
225
225
|
"no-this-before-super": () => require("./no-this-before-super"),
|
226
226
|
"no-throw-literal": () => require("./no-throw-literal"),
|
227
227
|
"no-trailing-spaces": () => require("./no-trailing-spaces"),
|
228
|
+
"no-unassigned-vars": () => require("./no-unassigned-vars"),
|
228
229
|
"no-undef": () => require("./no-undef"),
|
229
230
|
"no-undef-init": () => require("./no-undef-init"),
|
230
231
|
"no-undefined": () => require("./no-undefined"),
|
package/lib/rules/max-params.js
CHANGED
@@ -20,6 +20,8 @@ const { upperCaseFirst } = require("../shared/string-utils");
|
|
20
20
|
module.exports = {
|
21
21
|
meta: {
|
22
22
|
type: "suggestion",
|
23
|
+
dialects: ["typescript", "javascript"],
|
24
|
+
language: "javascript",
|
23
25
|
|
24
26
|
docs: {
|
25
27
|
description:
|
@@ -46,6 +48,11 @@ module.exports = {
|
|
46
48
|
type: "integer",
|
47
49
|
minimum: 0,
|
48
50
|
},
|
51
|
+
countVoidThis: {
|
52
|
+
type: "boolean",
|
53
|
+
description:
|
54
|
+
"Whether to count a `this` declaration when the type is `void`.",
|
55
|
+
},
|
49
56
|
},
|
50
57
|
additionalProperties: false,
|
51
58
|
},
|
@@ -61,12 +68,16 @@ module.exports = {
|
|
61
68
|
const sourceCode = context.sourceCode;
|
62
69
|
const option = context.options[0];
|
63
70
|
let numParams = 3;
|
71
|
+
let countVoidThis = false;
|
64
72
|
|
65
|
-
if (
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
73
|
+
if (typeof option === "object") {
|
74
|
+
if (
|
75
|
+
Object.hasOwn(option, "maximum") ||
|
76
|
+
Object.hasOwn(option, "max")
|
77
|
+
) {
|
78
|
+
numParams = option.maximum || option.max;
|
79
|
+
}
|
80
|
+
countVoidThis = option.countVoidThis;
|
70
81
|
}
|
71
82
|
if (typeof option === "number") {
|
72
83
|
numParams = option;
|
@@ -79,7 +90,19 @@ module.exports = {
|
|
79
90
|
* @private
|
80
91
|
*/
|
81
92
|
function checkFunction(node) {
|
82
|
-
|
93
|
+
const hasVoidThisParam =
|
94
|
+
node.params.length > 0 &&
|
95
|
+
node.params[0].type === "Identifier" &&
|
96
|
+
node.params[0].name === "this" &&
|
97
|
+
node.params[0].typeAnnotation?.typeAnnotation.type ===
|
98
|
+
"TSVoidKeyword";
|
99
|
+
|
100
|
+
const effectiveParamCount =
|
101
|
+
hasVoidThisParam && !countVoidThis
|
102
|
+
? node.params.length - 1
|
103
|
+
: node.params.length;
|
104
|
+
|
105
|
+
if (effectiveParamCount > numParams) {
|
83
106
|
context.report({
|
84
107
|
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
|
85
108
|
node,
|
@@ -88,7 +111,7 @@ module.exports = {
|
|
88
111
|
name: upperCaseFirst(
|
89
112
|
astUtils.getFunctionNameWithKind(node),
|
90
113
|
),
|
91
|
-
count:
|
114
|
+
count: effectiveParamCount,
|
92
115
|
max: numParams,
|
93
116
|
},
|
94
117
|
});
|
@@ -99,6 +122,8 @@ module.exports = {
|
|
99
122
|
FunctionDeclaration: checkFunction,
|
100
123
|
ArrowFunctionExpression: checkFunction,
|
101
124
|
FunctionExpression: checkFunction,
|
125
|
+
TSDeclareFunction: checkFunction,
|
126
|
+
TSFunctionType: checkFunction,
|
102
127
|
};
|
103
128
|
},
|
104
129
|
};
|
@@ -34,6 +34,8 @@ module.exports = {
|
|
34
34
|
url: "https://eslint.org/docs/latest/rules/no-array-constructor",
|
35
35
|
},
|
36
36
|
|
37
|
+
fixable: "code",
|
38
|
+
|
37
39
|
hasSuggestions: true,
|
38
40
|
|
39
41
|
schema: [],
|
@@ -49,6 +51,30 @@ module.exports = {
|
|
49
51
|
create(context) {
|
50
52
|
const sourceCode = context.sourceCode;
|
51
53
|
|
54
|
+
/**
|
55
|
+
* Checks if there are comments in Array constructor expressions.
|
56
|
+
* @param {ASTNode} node A CallExpression or NewExpression node.
|
57
|
+
* @returns {boolean} True if there are comments, false otherwise.
|
58
|
+
*/
|
59
|
+
function hasCommentsInArrayConstructor(node) {
|
60
|
+
const firstToken = sourceCode.getFirstToken(node);
|
61
|
+
const lastToken = sourceCode.getLastToken(node);
|
62
|
+
|
63
|
+
let lastRelevantToken = sourceCode.getLastToken(node.callee);
|
64
|
+
|
65
|
+
while (
|
66
|
+
lastRelevantToken !== lastToken &&
|
67
|
+
!isOpeningParenToken(lastRelevantToken)
|
68
|
+
) {
|
69
|
+
lastRelevantToken = sourceCode.getTokenAfter(lastRelevantToken);
|
70
|
+
}
|
71
|
+
|
72
|
+
return sourceCode.commentsExistBetween(
|
73
|
+
firstToken,
|
74
|
+
lastRelevantToken,
|
75
|
+
);
|
76
|
+
}
|
77
|
+
|
52
78
|
/**
|
53
79
|
* Gets the text between the calling parentheses of a CallExpression or NewExpression.
|
54
80
|
* @param {ASTNode} node A CallExpression or NewExpression node.
|
@@ -107,6 +133,17 @@ module.exports = {
|
|
107
133
|
let fixText;
|
108
134
|
let messageId;
|
109
135
|
|
136
|
+
const nonSpreadCount = node.arguments.reduce(
|
137
|
+
(count, arg) =>
|
138
|
+
arg.type !== "SpreadElement" ? count + 1 : count,
|
139
|
+
0,
|
140
|
+
);
|
141
|
+
|
142
|
+
const shouldSuggest =
|
143
|
+
node.optional ||
|
144
|
+
(node.arguments.length > 0 && nonSpreadCount < 2) ||
|
145
|
+
hasCommentsInArrayConstructor(node);
|
146
|
+
|
110
147
|
/*
|
111
148
|
* Check if the suggested change should include a preceding semicolon or not.
|
112
149
|
* Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
|
@@ -127,10 +164,23 @@ module.exports = {
|
|
127
164
|
context.report({
|
128
165
|
node,
|
129
166
|
messageId: "preferLiteral",
|
167
|
+
fix(fixer) {
|
168
|
+
if (shouldSuggest) {
|
169
|
+
return null;
|
170
|
+
}
|
171
|
+
|
172
|
+
return fixer.replaceText(node, fixText);
|
173
|
+
},
|
130
174
|
suggest: [
|
131
175
|
{
|
132
176
|
messageId,
|
133
|
-
fix
|
177
|
+
fix(fixer) {
|
178
|
+
if (shouldSuggest) {
|
179
|
+
return fixer.replaceText(node, fixText);
|
180
|
+
}
|
181
|
+
|
182
|
+
return null;
|
183
|
+
},
|
134
184
|
},
|
135
185
|
],
|
136
186
|
});
|