eslint 9.0.0-beta.2 → 9.0.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 +8 -12
- package/bin/eslint.js +14 -1
- package/lib/cli.js +30 -11
- package/lib/config/flat-config-schema.js +1 -2
- package/lib/eslint/eslint-helpers.js +5 -1
- package/lib/eslint/eslint.js +16 -1
- package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
- package/lib/linter/index.js +1 -3
- package/lib/linter/linter.js +184 -40
- package/lib/linter/timing.js +16 -8
- package/lib/options.js +25 -1
- package/lib/rule-tester/index.js +3 -1
- package/lib/rule-tester/rule-tester.js +18 -2
- package/lib/rules/camelcase.js +3 -5
- package/lib/rules/constructor-super.js +98 -99
- package/lib/rules/no-fallthrough.js +41 -16
- package/lib/rules/no-lone-blocks.js +1 -1
- package/lib/rules/no-this-before-super.js +28 -9
- package/lib/rules/no-unused-vars.js +179 -29
- package/lib/rules/no-useless-return.js +7 -2
- package/lib/rules/use-isnan.js +2 -2
- package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
- package/lib/rules/utils/unicode/index.js +9 -4
- package/lib/shared/runtime-info.js +1 -0
- package/lib/shared/stats.js +30 -0
- package/lib/shared/types.js +34 -0
- package/lib/source-code/index.js +3 -1
- package/lib/source-code/source-code.js +165 -1
- 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 +12 -8
- package/lib/cli-engine/xml-escape.js +0 -34
- package/lib/shared/deprecation-warnings.js +0 -58
@@ -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
|
@@ -119,6 +103,30 @@ function isPossibleConstructor(node) {
|
|
119
103
|
}
|
120
104
|
}
|
121
105
|
|
106
|
+
/**
|
107
|
+
* A class to store information about a code path segment.
|
108
|
+
*/
|
109
|
+
class SegmentInfo {
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Indicates if super() is called in all code paths.
|
113
|
+
* @type {boolean}
|
114
|
+
*/
|
115
|
+
calledInEveryPaths = false;
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Indicates if super() is called in any code paths.
|
119
|
+
* @type {boolean}
|
120
|
+
*/
|
121
|
+
calledInSomePaths = false;
|
122
|
+
|
123
|
+
/**
|
124
|
+
* The nodes which have been validated and don't need to be reconsidered.
|
125
|
+
* @type {ASTNode[]}
|
126
|
+
*/
|
127
|
+
validNodes = [];
|
128
|
+
}
|
129
|
+
|
122
130
|
//------------------------------------------------------------------------------
|
123
131
|
// Rule Definition
|
124
132
|
//------------------------------------------------------------------------------
|
@@ -141,8 +149,7 @@ module.exports = {
|
|
141
149
|
missingAll: "Expected to call 'super()'.",
|
142
150
|
|
143
151
|
duplicate: "Unexpected duplicate 'super()'.",
|
144
|
-
badSuper: "Unexpected 'super()' because 'super' is not a constructor."
|
145
|
-
unexpected: "Unexpected 'super()'."
|
152
|
+
badSuper: "Unexpected 'super()' because 'super' is not a constructor."
|
146
153
|
}
|
147
154
|
},
|
148
155
|
|
@@ -159,14 +166,10 @@ module.exports = {
|
|
159
166
|
*/
|
160
167
|
let funcInfo = null;
|
161
168
|
|
162
|
-
|
163
|
-
* {
|
164
|
-
* Information for each code path segment.
|
165
|
-
* - calledInSomePaths: A flag of be called `super()` in some code paths.
|
166
|
-
* - calledInEveryPaths: A flag of be called `super()` in all code paths.
|
167
|
-
* - validNodes:
|
169
|
+
/**
|
170
|
+
* @type {Record<string, SegmentInfo>}
|
168
171
|
*/
|
169
|
-
|
172
|
+
const segInfoMap = Object.create(null);
|
170
173
|
|
171
174
|
/**
|
172
175
|
* Gets the flag which shows `super()` is called in some paths.
|
@@ -177,23 +180,21 @@ module.exports = {
|
|
177
180
|
return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
|
178
181
|
}
|
179
182
|
|
183
|
+
/**
|
184
|
+
* Determines if a segment has been seen in the traversal.
|
185
|
+
* @param {CodePathSegment} segment A code path segment to check.
|
186
|
+
* @returns {boolean} `true` if the segment has been seen.
|
187
|
+
*/
|
188
|
+
function hasSegmentBeenSeen(segment) {
|
189
|
+
return !!segInfoMap[segment.id];
|
190
|
+
}
|
191
|
+
|
180
192
|
/**
|
181
193
|
* Gets the flag which shows `super()` is called in all paths.
|
182
194
|
* @param {CodePathSegment} segment A code path segment to get.
|
183
195
|
* @returns {boolean} The flag which shows `super()` is called in all paths.
|
184
196
|
*/
|
185
197
|
function isCalledInEveryPath(segment) {
|
186
|
-
|
187
|
-
/*
|
188
|
-
* If specific segment is the looped segment of the current segment,
|
189
|
-
* skip the segment.
|
190
|
-
* If not skipped, this never becomes true after a loop.
|
191
|
-
*/
|
192
|
-
if (segment.nextSegments.length === 1 &&
|
193
|
-
segment.nextSegments[0].isLoopedPrevSegment(segment)
|
194
|
-
) {
|
195
|
-
return true;
|
196
|
-
}
|
197
198
|
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
|
198
199
|
}
|
199
200
|
|
@@ -250,9 +251,9 @@ module.exports = {
|
|
250
251
|
}
|
251
252
|
|
252
253
|
// Reports if `super()` lacked.
|
253
|
-
const
|
254
|
-
const calledInEveryPaths =
|
255
|
-
const calledInSomePaths =
|
254
|
+
const returnedSegments = codePath.returnedSegments;
|
255
|
+
const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath);
|
256
|
+
const calledInSomePaths = returnedSegments.some(isCalledInSomePath);
|
256
257
|
|
257
258
|
if (!calledInEveryPaths) {
|
258
259
|
context.report({
|
@@ -267,29 +268,37 @@ module.exports = {
|
|
267
268
|
/**
|
268
269
|
* Initialize information of a given code path segment.
|
269
270
|
* @param {CodePathSegment} segment A code path segment to initialize.
|
271
|
+
* @param {CodePathSegment} node Node that starts the segment.
|
270
272
|
* @returns {void}
|
271
273
|
*/
|
272
|
-
onCodePathSegmentStart(segment) {
|
274
|
+
onCodePathSegmentStart(segment, node) {
|
273
275
|
|
274
276
|
funcInfo.currentSegments.add(segment);
|
275
277
|
|
276
|
-
if (!(funcInfo
|
278
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
277
279
|
return;
|
278
280
|
}
|
279
281
|
|
280
282
|
// Initialize info.
|
281
|
-
const info = segInfoMap[segment.id] =
|
282
|
-
|
283
|
-
|
284
|
-
validNodes: []
|
285
|
-
};
|
283
|
+
const info = segInfoMap[segment.id] = new SegmentInfo();
|
284
|
+
|
285
|
+
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
286
286
|
|
287
287
|
// When there are previous segments, aggregates these.
|
288
|
-
|
288
|
+
if (seenPrevSegments.length > 0) {
|
289
|
+
info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
|
290
|
+
info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
|
291
|
+
}
|
289
292
|
|
290
|
-
|
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;
|
293
302
|
}
|
294
303
|
},
|
295
304
|
|
@@ -316,25 +325,30 @@ module.exports = {
|
|
316
325
|
* @returns {void}
|
317
326
|
*/
|
318
327
|
onCodePathSegmentLoop(fromSegment, toSegment) {
|
319
|
-
if (!(funcInfo
|
328
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
320
329
|
return;
|
321
330
|
}
|
322
331
|
|
323
|
-
// Update information inside of the loop.
|
324
|
-
const isRealLoop = toSegment.prevSegments.length >= 2;
|
325
|
-
|
326
332
|
funcInfo.codePath.traverseSegments(
|
327
333
|
{ first: toSegment, last: fromSegment },
|
328
|
-
segment => {
|
334
|
+
(segment, controller) => {
|
329
335
|
const info = segInfoMap[segment.id];
|
330
|
-
const prevSegments = segment.prevSegments;
|
331
336
|
|
332
|
-
//
|
333
|
-
|
334
|
-
|
337
|
+
// skip segments after the loop
|
338
|
+
if (!info) {
|
339
|
+
controller.skip();
|
340
|
+
return;
|
341
|
+
}
|
342
|
+
|
343
|
+
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
344
|
+
const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath);
|
345
|
+
const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath);
|
346
|
+
|
347
|
+
info.calledInSomePaths ||= calledInSomePreviousPaths;
|
348
|
+
info.calledInEveryPaths ||= calledInEveryPreviousPaths;
|
335
349
|
|
336
350
|
// If flags become true anew, reports the valid nodes.
|
337
|
-
if (
|
351
|
+
if (calledInSomePreviousPaths) {
|
338
352
|
const nodes = info.validNodes;
|
339
353
|
|
340
354
|
info.validNodes = [];
|
@@ -358,7 +372,7 @@ module.exports = {
|
|
358
372
|
* @returns {void}
|
359
373
|
*/
|
360
374
|
"CallExpression:exit"(node) {
|
361
|
-
if (!(funcInfo && funcInfo.
|
375
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
362
376
|
return;
|
363
377
|
}
|
364
378
|
|
@@ -368,41 +382,34 @@ module.exports = {
|
|
368
382
|
}
|
369
383
|
|
370
384
|
// Reports if needed.
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
let info = null;
|
385
|
+
const segments = funcInfo.currentSegments;
|
386
|
+
let duplicate = false;
|
387
|
+
let info = null;
|
375
388
|
|
376
|
-
|
389
|
+
for (const segment of segments) {
|
377
390
|
|
378
|
-
|
379
|
-
|
391
|
+
if (segment.reachable) {
|
392
|
+
info = segInfoMap[segment.id];
|
380
393
|
|
381
|
-
|
382
|
-
|
383
|
-
}
|
394
|
+
duplicate = duplicate || info.calledInSomePaths;
|
395
|
+
info.calledInSomePaths = info.calledInEveryPaths = true;
|
384
396
|
}
|
397
|
+
}
|
385
398
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
}
|
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);
|
400
412
|
}
|
401
|
-
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
|
402
|
-
context.report({
|
403
|
-
messageId: "unexpected",
|
404
|
-
node
|
405
|
-
});
|
406
413
|
}
|
407
414
|
},
|
408
415
|
|
@@ -412,7 +419,7 @@ module.exports = {
|
|
412
419
|
* @returns {void}
|
413
420
|
*/
|
414
421
|
ReturnStatement(node) {
|
415
|
-
if (!(funcInfo
|
422
|
+
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
416
423
|
return;
|
417
424
|
}
|
418
425
|
|
@@ -432,14 +439,6 @@ module.exports = {
|
|
432
439
|
info.calledInSomePaths = info.calledInEveryPaths = true;
|
433
440
|
}
|
434
441
|
}
|
435
|
-
},
|
436
|
-
|
437
|
-
/**
|
438
|
-
* Resets state.
|
439
|
-
* @returns {void}
|
440
|
-
*/
|
441
|
-
"Program:exit"() {
|
442
|
-
segInfoMap = Object.create(null);
|
443
442
|
}
|
444
443
|
};
|
445
444
|
}
|
@@ -48,9 +48,9 @@ function isFallThroughComment(comment, fallthroughCommentPattern) {
|
|
48
48
|
* @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
|
49
49
|
* @param {RuleContext} context A rule context which stores comments.
|
50
50
|
* @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
|
51
|
-
* @returns {
|
51
|
+
* @returns {null | object} the comment if the case has a valid fallthrough comment, otherwise null
|
52
52
|
*/
|
53
|
-
function
|
53
|
+
function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
|
54
54
|
const sourceCode = context.sourceCode;
|
55
55
|
|
56
56
|
if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
|
@@ -58,13 +58,17 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
|
|
58
58
|
const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
|
59
59
|
|
60
60
|
if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) {
|
61
|
-
return
|
61
|
+
return commentInBlock;
|
62
62
|
}
|
63
63
|
}
|
64
64
|
|
65
65
|
const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
|
66
66
|
|
67
|
-
|
67
|
+
if (comment && isFallThroughComment(comment.value, fallthroughCommentPattern)) {
|
68
|
+
return comment;
|
69
|
+
}
|
70
|
+
|
71
|
+
return null;
|
68
72
|
}
|
69
73
|
|
70
74
|
/**
|
@@ -103,12 +107,17 @@ module.exports = {
|
|
103
107
|
allowEmptyCase: {
|
104
108
|
type: "boolean",
|
105
109
|
default: false
|
110
|
+
},
|
111
|
+
reportUnusedFallthroughComment: {
|
112
|
+
type: "boolean",
|
113
|
+
default: false
|
106
114
|
}
|
107
115
|
},
|
108
116
|
additionalProperties: false
|
109
117
|
}
|
110
118
|
],
|
111
119
|
messages: {
|
120
|
+
unusedFallthroughComment: "Found a comment that would permit fallthrough, but case cannot fall through.",
|
112
121
|
case: "Expected a 'break' statement before 'case'.",
|
113
122
|
default: "Expected a 'break' statement before 'default'."
|
114
123
|
}
|
@@ -120,12 +129,13 @@ module.exports = {
|
|
120
129
|
let currentCodePathSegments = new Set();
|
121
130
|
const sourceCode = context.sourceCode;
|
122
131
|
const allowEmptyCase = options.allowEmptyCase || false;
|
132
|
+
const reportUnusedFallthroughComment = options.reportUnusedFallthroughComment || false;
|
123
133
|
|
124
134
|
/*
|
125
135
|
* We need to use leading comments of the next SwitchCase node because
|
126
136
|
* trailing comments is wrong if semicolons are omitted.
|
127
137
|
*/
|
128
|
-
let
|
138
|
+
let previousCase = null;
|
129
139
|
let fallthroughCommentPattern = null;
|
130
140
|
|
131
141
|
if (options.commentPattern) {
|
@@ -168,13 +178,23 @@ module.exports = {
|
|
168
178
|
* And reports the previous fallthrough node if that does not exist.
|
169
179
|
*/
|
170
180
|
|
171
|
-
if (
|
172
|
-
context
|
173
|
-
|
174
|
-
|
175
|
-
|
181
|
+
if (previousCase && previousCase.node.parent === node.parent) {
|
182
|
+
const previousCaseFallthroughComment = getFallthroughComment(previousCase.node, node, context, fallthroughCommentPattern);
|
183
|
+
|
184
|
+
if (previousCase.isFallthrough && !(previousCaseFallthroughComment)) {
|
185
|
+
context.report({
|
186
|
+
messageId: node.test ? "case" : "default",
|
187
|
+
node
|
188
|
+
});
|
189
|
+
} else if (reportUnusedFallthroughComment && !previousCase.isSwitchExitReachable && previousCaseFallthroughComment) {
|
190
|
+
context.report({
|
191
|
+
messageId: "unusedFallthroughComment",
|
192
|
+
node: previousCaseFallthroughComment
|
193
|
+
});
|
194
|
+
}
|
195
|
+
|
176
196
|
}
|
177
|
-
|
197
|
+
previousCase = null;
|
178
198
|
},
|
179
199
|
|
180
200
|
"SwitchCase:exit"(node) {
|
@@ -185,11 +205,16 @@ module.exports = {
|
|
185
205
|
* `break`, `return`, or `throw` are unreachable.
|
186
206
|
* And allows empty cases and the last case.
|
187
207
|
*/
|
188
|
-
|
189
|
-
|
190
|
-
node.parent.cases.at(-1) !== node
|
191
|
-
|
192
|
-
|
208
|
+
const isSwitchExitReachable = isAnySegmentReachable(currentCodePathSegments);
|
209
|
+
const isFallthrough = isSwitchExitReachable && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
|
210
|
+
node.parent.cases.at(-1) !== node;
|
211
|
+
|
212
|
+
previousCase = {
|
213
|
+
node,
|
214
|
+
isSwitchExitReachable,
|
215
|
+
isFallthrough
|
216
|
+
};
|
217
|
+
|
193
218
|
}
|
194
219
|
};
|
195
220
|
}
|
@@ -30,6 +30,29 @@ function isConstructorFunction(node) {
|
|
30
30
|
);
|
31
31
|
}
|
32
32
|
|
33
|
+
/*
|
34
|
+
* Information for each code path segment.
|
35
|
+
* - superCalled: The flag which shows `super()` called in all code paths.
|
36
|
+
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
|
37
|
+
*/
|
38
|
+
/**
|
39
|
+
*
|
40
|
+
*/
|
41
|
+
class SegmentInfo {
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Indicates whether `super()` is called in all code paths.
|
45
|
+
* @type {boolean}
|
46
|
+
*/
|
47
|
+
superCalled = false;
|
48
|
+
|
49
|
+
/**
|
50
|
+
* The array of invalid ThisExpression and Super nodes.
|
51
|
+
* @type {ASTNode[]}
|
52
|
+
*/
|
53
|
+
invalidNodes = [];
|
54
|
+
}
|
55
|
+
|
33
56
|
//------------------------------------------------------------------------------
|
34
57
|
// Rule Definition
|
35
58
|
//------------------------------------------------------------------------------
|
@@ -64,13 +87,7 @@ module.exports = {
|
|
64
87
|
*/
|
65
88
|
let funcInfo = null;
|
66
89
|
|
67
|
-
|
68
|
-
* Information for each code path segment.
|
69
|
-
* Each key is the id of a code path segment.
|
70
|
-
* Each value is an object:
|
71
|
-
* - superCalled: The flag which shows `super()` called in all code paths.
|
72
|
-
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
|
73
|
-
*/
|
90
|
+
/** @type {Record<string, SegmentInfo>} */
|
74
91
|
let segInfoMap = Object.create(null);
|
75
92
|
|
76
93
|
/**
|
@@ -79,7 +96,7 @@ module.exports = {
|
|
79
96
|
* @returns {boolean} `true` if `super()` is called.
|
80
97
|
*/
|
81
98
|
function isCalled(segment) {
|
82
|
-
return !segment.reachable || segInfoMap[segment.id]
|
99
|
+
return !segment.reachable || segInfoMap[segment.id]?.superCalled;
|
83
100
|
}
|
84
101
|
|
85
102
|
/**
|
@@ -285,7 +302,7 @@ module.exports = {
|
|
285
302
|
funcInfo.codePath.traverseSegments(
|
286
303
|
{ first: toSegment, last: fromSegment },
|
287
304
|
(segment, controller) => {
|
288
|
-
const info = segInfoMap[segment.id];
|
305
|
+
const info = segInfoMap[segment.id] ?? new SegmentInfo();
|
289
306
|
|
290
307
|
if (info.superCalled) {
|
291
308
|
controller.skip();
|
@@ -295,6 +312,8 @@ module.exports = {
|
|
295
312
|
) {
|
296
313
|
info.superCalled = true;
|
297
314
|
}
|
315
|
+
|
316
|
+
segInfoMap[segment.id] = info;
|
298
317
|
}
|
299
318
|
);
|
300
319
|
},
|