eslint 9.0.0-rc.0 → 9.1.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 -11
- package/conf/globals.js +1 -0
- package/lib/cli.js +42 -1
- package/lib/config/flat-config-array.js +110 -4
- package/lib/eslint/eslint-helpers.js +84 -45
- package/lib/eslint/eslint.js +24 -5
- package/lib/linter/apply-disable-directives.js +24 -24
- package/lib/linter/linter.js +181 -66
- package/lib/linter/timing.js +16 -8
- package/lib/options.js +27 -3
- package/lib/rule-tester/rule-tester.js +18 -2
- package/lib/rules/camelcase.js +3 -5
- package/lib/rules/constructor-super.js +62 -93
- package/lib/rules/no-constant-condition.js +18 -7
- package/lib/rules/no-lone-blocks.js +1 -1
- package/lib/rules/no-unused-vars.js +179 -29
- package/lib/rules/use-isnan.js +2 -2
- package/lib/shared/stats.js +30 -0
- package/lib/shared/string-utils.js +9 -11
- package/lib/shared/types.js +34 -0
- package/lib/source-code/source-code.js +128 -0
- package/lib/source-code/token-store/backward-token-cursor.js +3 -3
- package/lib/source-code/token-store/cursors.js +4 -2
- package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
- package/lib/source-code/token-store/forward-token-cursor.js +3 -3
- package/messages/plugin-conflict.js +1 -1
- package/messages/plugin-invalid.js +1 -1
- package/messages/plugin-missing.js +1 -1
- package/package.json +11 -9
package/lib/options.js
CHANGED
@@ -38,7 +38,7 @@ const optionator = require("optionator");
|
|
38
38
|
* @property {boolean} [help] Show help
|
39
39
|
* @property {boolean} ignore Disable use of ignore files and patterns
|
40
40
|
* @property {string} [ignorePath] Specify path of ignore file
|
41
|
-
* @property {string[]} [ignorePattern]
|
41
|
+
* @property {string[]} [ignorePattern] Patterns of files to ignore. In eslintrc mode, these are in addition to `.eslintignore`
|
42
42
|
* @property {boolean} init Run config initialization wizard
|
43
43
|
* @property {boolean} inlineConfig Prevent comments from changing config or rules
|
44
44
|
* @property {number} maxWarnings Number of warnings to trigger nonzero exit code
|
@@ -60,6 +60,7 @@ const optionator = require("optionator");
|
|
60
60
|
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
|
61
61
|
* the linting operation to short circuit and not report any failures.
|
62
62
|
* @property {string[]} _ Positional filenames or patterns
|
63
|
+
* @property {boolean} [stats] Report additional statistics
|
63
64
|
*/
|
64
65
|
|
65
66
|
//------------------------------------------------------------------------------
|
@@ -103,6 +104,16 @@ module.exports = function(usingFlatConfig) {
|
|
103
104
|
};
|
104
105
|
}
|
105
106
|
|
107
|
+
let inspectConfigFlag;
|
108
|
+
|
109
|
+
if (usingFlatConfig) {
|
110
|
+
inspectConfigFlag = {
|
111
|
+
option: "inspect-config",
|
112
|
+
type: "Boolean",
|
113
|
+
description: "Open the config inspector with the current configuration"
|
114
|
+
};
|
115
|
+
}
|
116
|
+
|
106
117
|
let extFlag;
|
107
118
|
|
108
119
|
if (!usingFlatConfig) {
|
@@ -143,6 +154,17 @@ module.exports = function(usingFlatConfig) {
|
|
143
154
|
};
|
144
155
|
}
|
145
156
|
|
157
|
+
let statsFlag;
|
158
|
+
|
159
|
+
if (usingFlatConfig) {
|
160
|
+
statsFlag = {
|
161
|
+
option: "stats",
|
162
|
+
type: "Boolean",
|
163
|
+
default: "false",
|
164
|
+
description: "Add statistics to the lint report"
|
165
|
+
};
|
166
|
+
}
|
167
|
+
|
146
168
|
let warnIgnoredFlag;
|
147
169
|
|
148
170
|
if (usingFlatConfig) {
|
@@ -173,6 +195,7 @@ module.exports = function(usingFlatConfig) {
|
|
173
195
|
? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs"
|
174
196
|
: "Use this configuration, overriding .eslintrc.* config options if present"
|
175
197
|
},
|
198
|
+
inspectConfigFlag,
|
176
199
|
envFlag,
|
177
200
|
extFlag,
|
178
201
|
{
|
@@ -238,7 +261,7 @@ module.exports = function(usingFlatConfig) {
|
|
238
261
|
{
|
239
262
|
option: "ignore-pattern",
|
240
263
|
type: "[String]",
|
241
|
-
description:
|
264
|
+
description: `Patterns of files to ignore${usingFlatConfig ? "" : " (in addition to those in .eslintignore)"}`,
|
242
265
|
concatRepeatedArrays: [true, {
|
243
266
|
oneValuePerFlag: true
|
244
267
|
}]
|
@@ -400,7 +423,8 @@ module.exports = function(usingFlatConfig) {
|
|
400
423
|
option: "print-config",
|
401
424
|
type: "path::String",
|
402
425
|
description: "Print the configuration for the given file"
|
403
|
-
}
|
426
|
+
},
|
427
|
+
statsFlag
|
404
428
|
].filter(value => !!value)
|
405
429
|
});
|
406
430
|
};
|
@@ -136,6 +136,15 @@ const suggestionObjectParameters = new Set([
|
|
136
136
|
]);
|
137
137
|
const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
|
138
138
|
|
139
|
+
/*
|
140
|
+
* Ignored test case properties when checking for test case duplicates.
|
141
|
+
*/
|
142
|
+
const duplicationIgnoredParameters = new Set([
|
143
|
+
"name",
|
144
|
+
"errors",
|
145
|
+
"output"
|
146
|
+
]);
|
147
|
+
|
139
148
|
const forbiddenMethods = [
|
140
149
|
"applyInlineConfig",
|
141
150
|
"applyLanguageOptions",
|
@@ -848,7 +857,7 @@ class RuleTester {
|
|
848
857
|
|
849
858
|
/**
|
850
859
|
* Check if this test case is a duplicate of one we have seen before.
|
851
|
-
* @param {Object} item test case object
|
860
|
+
* @param {string|Object} item test case object
|
852
861
|
* @param {Set<string>} seenTestCases set of serialized test cases we have seen so far (managed by this function)
|
853
862
|
* @returns {void}
|
854
863
|
* @private
|
@@ -863,7 +872,14 @@ class RuleTester {
|
|
863
872
|
return;
|
864
873
|
}
|
865
874
|
|
866
|
-
const
|
875
|
+
const normalizedItem = typeof item === "string" ? { code: item } : item;
|
876
|
+
const serializedTestCase = stringify(normalizedItem, {
|
877
|
+
replacer(key, value) {
|
878
|
+
|
879
|
+
// "this" is the currently stringified object --> only ignore top-level properties
|
880
|
+
return (normalizedItem !== this || !duplicationIgnoredParameters.has(key)) ? value : void 0;
|
881
|
+
}
|
882
|
+
});
|
867
883
|
|
868
884
|
assert(
|
869
885
|
!seenTestCases.has(serializedTestCase),
|
package/lib/rules/camelcase.js
CHANGED
@@ -9,22 +9,6 @@
|
|
9
9
|
// Helpers
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
/**
|
13
|
-
* Checks all segments in a set and returns true if any are reachable.
|
14
|
-
* @param {Set<CodePathSegment>} segments The segments to check.
|
15
|
-
* @returns {boolean} True if any segment is reachable; false otherwise.
|
16
|
-
*/
|
17
|
-
function isAnySegmentReachable(segments) {
|
18
|
-
|
19
|
-
for (const segment of segments) {
|
20
|
-
if (segment.reachable) {
|
21
|
-
return true;
|
22
|
-
}
|
23
|
-
}
|
24
|
-
|
25
|
-
return false;
|
26
|
-
}
|
27
|
-
|
28
12
|
/**
|
29
13
|
* Checks whether or not a given node is a constructor.
|
30
14
|
* @param {ASTNode} node A node to check. This node type is one of
|
@@ -165,8 +149,7 @@ module.exports = {
|
|
165
149
|
missingAll: "Expected to call 'super()'.",
|
166
150
|
|
167
151
|
duplicate: "Unexpected duplicate 'super()'.",
|
168
|
-
badSuper: "Unexpected 'super()' because 'super' is not a constructor."
|
169
|
-
unexpected: "Unexpected 'super()'."
|
152
|
+
badSuper: "Unexpected 'super()' because 'super' is not a constructor."
|
170
153
|
}
|
171
154
|
},
|
172
155
|
|
@@ -186,7 +169,7 @@ module.exports = {
|
|
186
169
|
/**
|
187
170
|
* @type {Record<string, SegmentInfo>}
|
188
171
|
*/
|
189
|
-
|
172
|
+
const segInfoMap = Object.create(null);
|
190
173
|
|
191
174
|
/**
|
192
175
|
* Gets the flag which shows `super()` is called in some paths.
|
@@ -194,7 +177,7 @@ module.exports = {
|
|
194
177
|
* @returns {boolean} The flag which shows `super()` is called in some paths
|
195
178
|
*/
|
196
179
|
function isCalledInSomePath(segment) {
|
197
|
-
return segment.reachable && segInfoMap[segment.id]
|
180
|
+
return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
|
198
181
|
}
|
199
182
|
|
200
183
|
/**
|
@@ -212,17 +195,6 @@ module.exports = {
|
|
212
195
|
* @returns {boolean} The flag which shows `super()` is called in all paths.
|
213
196
|
*/
|
214
197
|
function isCalledInEveryPath(segment) {
|
215
|
-
|
216
|
-
/*
|
217
|
-
* If specific segment is the looped segment of the current segment,
|
218
|
-
* skip the segment.
|
219
|
-
* If not skipped, this never becomes true after a loop.
|
220
|
-
*/
|
221
|
-
if (segment.nextSegments.length === 1 &&
|
222
|
-
segment.nextSegments[0]?.isLoopedPrevSegment(segment)) {
|
223
|
-
return true;
|
224
|
-
}
|
225
|
-
|
226
198
|
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
|
227
199
|
}
|
228
200
|
|
@@ -279,9 +251,9 @@ module.exports = {
|
|
279
251
|
}
|
280
252
|
|
281
253
|
// Reports if `super()` lacked.
|
282
|
-
const
|
283
|
-
const calledInEveryPaths =
|
284
|
-
const calledInSomePaths =
|
254
|
+
const returnedSegments = codePath.returnedSegments;
|
255
|
+
const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath);
|
256
|
+
const calledInSomePaths = returnedSegments.some(isCalledInSomePath);
|
285
257
|
|
286
258
|
if (!calledInEveryPaths) {
|
287
259
|
context.report({
|
@@ -296,28 +268,38 @@ module.exports = {
|
|
296
268
|
/**
|
297
269
|
* Initialize information of a given code path segment.
|
298
270
|
* @param {CodePathSegment} segment A code path segment to initialize.
|
271
|
+
* @param {CodePathSegment} node Node that starts the segment.
|
299
272
|
* @returns {void}
|
300
273
|
*/
|
301
|
-
onCodePathSegmentStart(segment) {
|
274
|
+
onCodePathSegmentStart(segment, node) {
|
302
275
|
|
303
276
|
funcInfo.currentSegments.add(segment);
|
304
277
|
|
305
|
-
if (!(funcInfo
|
278
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
306
279
|
return;
|
307
280
|
}
|
308
281
|
|
309
282
|
// Initialize info.
|
310
283
|
const info = segInfoMap[segment.id] = new SegmentInfo();
|
311
284
|
|
312
|
-
|
313
|
-
const prevSegments = segment.prevSegments;
|
314
|
-
|
315
|
-
if (prevSegments.length > 0) {
|
316
|
-
const seenPrevSegments = prevSegments.filter(hasSegmentBeenSeen);
|
285
|
+
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
317
286
|
|
287
|
+
// When there are previous segments, aggregates these.
|
288
|
+
if (seenPrevSegments.length > 0) {
|
318
289
|
info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
|
319
290
|
info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
|
320
291
|
}
|
292
|
+
|
293
|
+
/*
|
294
|
+
* ForStatement > *.update segments are a special case as they are created in advance,
|
295
|
+
* without seen previous segments. Since they logically don't affect `calledInEveryPaths`
|
296
|
+
* calculations, and they can never be a lone previous segment of another one, we'll set
|
297
|
+
* their `calledInEveryPaths` to `true` to effectively ignore them in those calculations.
|
298
|
+
* .
|
299
|
+
*/
|
300
|
+
if (node.parent && node.parent.type === "ForStatement" && node.parent.update === node) {
|
301
|
+
info.calledInEveryPaths = true;
|
302
|
+
}
|
321
303
|
},
|
322
304
|
|
323
305
|
onUnreachableCodePathSegmentStart(segment) {
|
@@ -343,25 +325,30 @@ module.exports = {
|
|
343
325
|
* @returns {void}
|
344
326
|
*/
|
345
327
|
onCodePathSegmentLoop(fromSegment, toSegment) {
|
346
|
-
if (!(funcInfo
|
328
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
347
329
|
return;
|
348
330
|
}
|
349
331
|
|
350
|
-
// Update information inside of the loop.
|
351
|
-
const isRealLoop = toSegment.prevSegments.length >= 2;
|
352
|
-
|
353
332
|
funcInfo.codePath.traverseSegments(
|
354
333
|
{ first: toSegment, last: fromSegment },
|
355
|
-
segment => {
|
356
|
-
const info = segInfoMap[segment.id]
|
334
|
+
(segment, controller) => {
|
335
|
+
const info = segInfoMap[segment.id];
|
336
|
+
|
337
|
+
// skip segments after the loop
|
338
|
+
if (!info) {
|
339
|
+
controller.skip();
|
340
|
+
return;
|
341
|
+
}
|
342
|
+
|
357
343
|
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
344
|
+
const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath);
|
345
|
+
const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath);
|
358
346
|
|
359
|
-
|
360
|
-
info.
|
361
|
-
info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
|
347
|
+
info.calledInSomePaths ||= calledInSomePreviousPaths;
|
348
|
+
info.calledInEveryPaths ||= calledInEveryPreviousPaths;
|
362
349
|
|
363
350
|
// If flags become true anew, reports the valid nodes.
|
364
|
-
if (
|
351
|
+
if (calledInSomePreviousPaths) {
|
365
352
|
const nodes = info.validNodes;
|
366
353
|
|
367
354
|
info.validNodes = [];
|
@@ -375,9 +362,6 @@ module.exports = {
|
|
375
362
|
});
|
376
363
|
}
|
377
364
|
}
|
378
|
-
|
379
|
-
// save just in case we created a new SegmentInfo object
|
380
|
-
segInfoMap[segment.id] = info;
|
381
365
|
}
|
382
366
|
);
|
383
367
|
},
|
@@ -388,7 +372,7 @@ module.exports = {
|
|
388
372
|
* @returns {void}
|
389
373
|
*/
|
390
374
|
"CallExpression:exit"(node) {
|
391
|
-
if (!(funcInfo && funcInfo.
|
375
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
392
376
|
return;
|
393
377
|
}
|
394
378
|
|
@@ -398,41 +382,34 @@ module.exports = {
|
|
398
382
|
}
|
399
383
|
|
400
384
|
// Reports if needed.
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
let info = null;
|
385
|
+
const segments = funcInfo.currentSegments;
|
386
|
+
let duplicate = false;
|
387
|
+
let info = null;
|
405
388
|
|
406
|
-
|
389
|
+
for (const segment of segments) {
|
407
390
|
|
408
|
-
|
409
|
-
|
391
|
+
if (segment.reachable) {
|
392
|
+
info = segInfoMap[segment.id];
|
410
393
|
|
411
|
-
|
412
|
-
|
413
|
-
}
|
394
|
+
duplicate = duplicate || info.calledInSomePaths;
|
395
|
+
info.calledInSomePaths = info.calledInEveryPaths = true;
|
414
396
|
}
|
397
|
+
}
|
415
398
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
}
|
399
|
+
if (info) {
|
400
|
+
if (duplicate) {
|
401
|
+
context.report({
|
402
|
+
messageId: "duplicate",
|
403
|
+
node
|
404
|
+
});
|
405
|
+
} else if (!funcInfo.superIsConstructor) {
|
406
|
+
context.report({
|
407
|
+
messageId: "badSuper",
|
408
|
+
node
|
409
|
+
});
|
410
|
+
} else {
|
411
|
+
info.validNodes.push(node);
|
430
412
|
}
|
431
|
-
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
|
432
|
-
context.report({
|
433
|
-
messageId: "unexpected",
|
434
|
-
node
|
435
|
-
});
|
436
413
|
}
|
437
414
|
},
|
438
415
|
|
@@ -442,7 +419,7 @@ module.exports = {
|
|
442
419
|
* @returns {void}
|
443
420
|
*/
|
444
421
|
ReturnStatement(node) {
|
445
|
-
if (!(funcInfo
|
422
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
446
423
|
return;
|
447
424
|
}
|
448
425
|
|
@@ -462,14 +439,6 @@ module.exports = {
|
|
462
439
|
info.calledInSomePaths = info.calledInEveryPaths = true;
|
463
440
|
}
|
464
441
|
}
|
465
|
-
},
|
466
|
-
|
467
|
-
/**
|
468
|
-
* Resets state.
|
469
|
-
* @returns {void}
|
470
|
-
*/
|
471
|
-
"Program:exit"() {
|
472
|
-
segInfoMap = Object.create(null);
|
473
442
|
}
|
474
443
|
};
|
475
444
|
}
|
@@ -31,8 +31,7 @@ module.exports = {
|
|
31
31
|
type: "object",
|
32
32
|
properties: {
|
33
33
|
checkLoops: {
|
34
|
-
|
35
|
-
default: true
|
34
|
+
enum: ["all", "allExceptWhileTrue", "none", true, false]
|
36
35
|
}
|
37
36
|
},
|
38
37
|
additionalProperties: false
|
@@ -45,11 +44,17 @@ module.exports = {
|
|
45
44
|
},
|
46
45
|
|
47
46
|
create(context) {
|
48
|
-
const options = context.options[0] || {}
|
49
|
-
|
50
|
-
|
47
|
+
const options = context.options[0] || {};
|
48
|
+
let checkLoops = options.checkLoops ?? "allExceptWhileTrue";
|
49
|
+
const loopSetStack = [];
|
51
50
|
const sourceCode = context.sourceCode;
|
52
51
|
|
52
|
+
if (options.checkLoops === true) {
|
53
|
+
checkLoops = "all";
|
54
|
+
} else if (options.checkLoops === false) {
|
55
|
+
checkLoops = "none";
|
56
|
+
}
|
57
|
+
|
53
58
|
let loopsInCurrentScope = new Set();
|
54
59
|
|
55
60
|
//--------------------------------------------------------------------------
|
@@ -120,7 +125,7 @@ module.exports = {
|
|
120
125
|
* @private
|
121
126
|
*/
|
122
127
|
function checkLoop(node) {
|
123
|
-
if (checkLoops) {
|
128
|
+
if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") {
|
124
129
|
trackConstantConditionLoop(node);
|
125
130
|
}
|
126
131
|
}
|
@@ -132,7 +137,13 @@ module.exports = {
|
|
132
137
|
return {
|
133
138
|
ConditionalExpression: reportIfConstant,
|
134
139
|
IfStatement: reportIfConstant,
|
135
|
-
WhileStatement
|
140
|
+
WhileStatement(node) {
|
141
|
+
if (node.test.type === "Literal" && node.test.value === true && checkLoops === "allExceptWhileTrue") {
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
145
|
+
checkLoop(node);
|
146
|
+
},
|
136
147
|
"WhileStatement:exit": checkConstantConditionLoopInSet,
|
137
148
|
DoWhileStatement: checkLoop,
|
138
149
|
"DoWhileStatement:exit": checkConstantConditionLoopInSet,
|