eslint 8.48.0 → 8.50.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 +2 -2
- package/lib/config/flat-config-schema.js +11 -1
- package/lib/config/rule-validator.js +2 -1
- package/lib/linter/code-path-analysis/code-path-analyzer.js +32 -24
- package/lib/linter/code-path-analysis/code-path.js +1 -0
- package/lib/linter/linter.js +173 -57
- package/lib/rule-tester/flat-rule-tester.js +77 -5
- package/lib/rule-tester/rule-tester.js +146 -3
- package/lib/rules/array-callback-return.js +175 -25
- package/lib/rules/consistent-return.js +32 -7
- package/lib/rules/constructor-super.js +37 -14
- package/lib/rules/getter-return.js +33 -8
- package/lib/rules/index.js +1 -0
- package/lib/rules/lines-between-class-members.js +92 -7
- package/lib/rules/no-fallthrough.js +42 -14
- package/lib/rules/no-misleading-character-class.js +65 -15
- package/lib/rules/no-new-object.js +7 -0
- package/lib/rules/no-object-constructor.js +118 -0
- package/lib/rules/no-this-before-super.js +38 -11
- package/lib/rules/no-unreachable-loop.js +47 -12
- package/lib/rules/no-unreachable.js +39 -10
- package/lib/rules/no-useless-return.js +35 -4
- package/lib/rules/require-atomic-updates.js +21 -7
- package/lib/source-code/source-code.js +350 -3
- package/package.json +11 -9
@@ -90,6 +90,21 @@ module.exports = {
|
|
90
90
|
return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
|
91
91
|
}
|
92
92
|
|
93
|
+
/**
|
94
|
+
* Determines if every segment in a set has been called.
|
95
|
+
* @param {Set<CodePathSegment>} segments The segments to search.
|
96
|
+
* @returns {boolean} True if every segment has been called; false otherwise.
|
97
|
+
*/
|
98
|
+
function isEverySegmentCalled(segments) {
|
99
|
+
for (const segment of segments) {
|
100
|
+
if (!isCalled(segment)) {
|
101
|
+
return false;
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
return true;
|
106
|
+
}
|
107
|
+
|
93
108
|
/**
|
94
109
|
* Checks whether or not this is before `super()` is called.
|
95
110
|
* @returns {boolean} `true` if this is before `super()` is called.
|
@@ -97,7 +112,7 @@ module.exports = {
|
|
97
112
|
function isBeforeCallOfSuper() {
|
98
113
|
return (
|
99
114
|
isInConstructorOfDerivedClass() &&
|
100
|
-
!funcInfo.
|
115
|
+
!isEverySegmentCalled(funcInfo.currentSegments)
|
101
116
|
);
|
102
117
|
}
|
103
118
|
|
@@ -108,11 +123,9 @@ module.exports = {
|
|
108
123
|
* @returns {void}
|
109
124
|
*/
|
110
125
|
function setInvalid(node) {
|
111
|
-
const segments = funcInfo.
|
112
|
-
|
113
|
-
for (let i = 0; i < segments.length; ++i) {
|
114
|
-
const segment = segments[i];
|
126
|
+
const segments = funcInfo.currentSegments;
|
115
127
|
|
128
|
+
for (const segment of segments) {
|
116
129
|
if (segment.reachable) {
|
117
130
|
segInfoMap[segment.id].invalidNodes.push(node);
|
118
131
|
}
|
@@ -124,11 +137,9 @@ module.exports = {
|
|
124
137
|
* @returns {void}
|
125
138
|
*/
|
126
139
|
function setSuperCalled() {
|
127
|
-
const segments = funcInfo.
|
128
|
-
|
129
|
-
for (let i = 0; i < segments.length; ++i) {
|
130
|
-
const segment = segments[i];
|
140
|
+
const segments = funcInfo.currentSegments;
|
131
141
|
|
142
|
+
for (const segment of segments) {
|
132
143
|
if (segment.reachable) {
|
133
144
|
segInfoMap[segment.id].superCalled = true;
|
134
145
|
}
|
@@ -156,14 +167,16 @@ module.exports = {
|
|
156
167
|
classNode.superClass &&
|
157
168
|
!astUtils.isNullOrUndefined(classNode.superClass)
|
158
169
|
),
|
159
|
-
codePath
|
170
|
+
codePath,
|
171
|
+
currentSegments: new Set()
|
160
172
|
};
|
161
173
|
} else {
|
162
174
|
funcInfo = {
|
163
175
|
upper: funcInfo,
|
164
176
|
isConstructor: false,
|
165
177
|
hasExtends: false,
|
166
|
-
codePath
|
178
|
+
codePath,
|
179
|
+
currentSegments: new Set()
|
167
180
|
};
|
168
181
|
}
|
169
182
|
},
|
@@ -211,6 +224,8 @@ module.exports = {
|
|
211
224
|
* @returns {void}
|
212
225
|
*/
|
213
226
|
onCodePathSegmentStart(segment) {
|
227
|
+
funcInfo.currentSegments.add(segment);
|
228
|
+
|
214
229
|
if (!isInConstructorOfDerivedClass()) {
|
215
230
|
return;
|
216
231
|
}
|
@@ -225,6 +240,18 @@ module.exports = {
|
|
225
240
|
};
|
226
241
|
},
|
227
242
|
|
243
|
+
onUnreachableCodePathSegmentStart(segment) {
|
244
|
+
funcInfo.currentSegments.add(segment);
|
245
|
+
},
|
246
|
+
|
247
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
248
|
+
funcInfo.currentSegments.delete(segment);
|
249
|
+
},
|
250
|
+
|
251
|
+
onCodePathSegmentEnd(segment) {
|
252
|
+
funcInfo.currentSegments.delete(segment);
|
253
|
+
},
|
254
|
+
|
228
255
|
/**
|
229
256
|
* Update information of the code path segment when a code path was
|
230
257
|
* looped.
|
@@ -11,6 +11,22 @@
|
|
11
11
|
|
12
12
|
const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
|
13
13
|
|
14
|
+
/**
|
15
|
+
* Checks all segments in a set and returns true if any are reachable.
|
16
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
17
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
18
|
+
*/
|
19
|
+
function isAnySegmentReachable(segments) {
|
20
|
+
|
21
|
+
for (const segment of segments) {
|
22
|
+
if (segment.reachable) {
|
23
|
+
return true;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
|
14
30
|
/**
|
15
31
|
* Determines whether the given node is the first node in the code path to which a loop statement
|
16
32
|
* 'loops' for the next iteration.
|
@@ -90,29 +106,36 @@ module.exports = {
|
|
90
106
|
loopsByTargetSegments = new Map(),
|
91
107
|
loopsToReport = new Set();
|
92
108
|
|
93
|
-
|
109
|
+
const codePathSegments = [];
|
110
|
+
let currentCodePathSegments = new Set();
|
94
111
|
|
95
112
|
return {
|
96
|
-
|
97
|
-
|
113
|
+
|
114
|
+
onCodePathStart() {
|
115
|
+
codePathSegments.push(currentCodePathSegments);
|
116
|
+
currentCodePathSegments = new Set();
|
98
117
|
},
|
99
118
|
|
100
119
|
onCodePathEnd() {
|
101
|
-
|
120
|
+
currentCodePathSegments = codePathSegments.pop();
|
102
121
|
},
|
103
122
|
|
104
|
-
|
123
|
+
onUnreachableCodePathSegmentStart(segment) {
|
124
|
+
currentCodePathSegments.add(segment);
|
125
|
+
},
|
105
126
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
}
|
127
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
128
|
+
currentCodePathSegments.delete(segment);
|
129
|
+
},
|
130
|
+
|
131
|
+
onCodePathSegmentEnd(segment) {
|
132
|
+
currentCodePathSegments.delete(segment);
|
113
133
|
},
|
114
134
|
|
115
135
|
onCodePathSegmentStart(segment, node) {
|
136
|
+
|
137
|
+
currentCodePathSegments.add(segment);
|
138
|
+
|
116
139
|
if (isLoopingTarget(node)) {
|
117
140
|
const loop = node.parent;
|
118
141
|
|
@@ -140,6 +163,18 @@ module.exports = {
|
|
140
163
|
}
|
141
164
|
},
|
142
165
|
|
166
|
+
[loopSelector](node) {
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
|
170
|
+
* For unreachable segments, the code path analysis does not raise events required for this implementation.
|
171
|
+
*/
|
172
|
+
if (isAnySegmentReachable(currentCodePathSegments)) {
|
173
|
+
loopsToReport.add(node);
|
174
|
+
}
|
175
|
+
},
|
176
|
+
|
177
|
+
|
143
178
|
"Program:exit"() {
|
144
179
|
loopsToReport.forEach(
|
145
180
|
node => context.report({ node, messageId: "invalid" })
|
@@ -24,12 +24,19 @@ function isInitialized(node) {
|
|
24
24
|
}
|
25
25
|
|
26
26
|
/**
|
27
|
-
* Checks
|
28
|
-
* @param {CodePathSegment}
|
29
|
-
* @returns {boolean}
|
27
|
+
* Checks all segments in a set and returns true if all are unreachable.
|
28
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
29
|
+
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
30
30
|
*/
|
31
|
-
function
|
32
|
-
|
31
|
+
function areAllSegmentsUnreachable(segments) {
|
32
|
+
|
33
|
+
for (const segment of segments) {
|
34
|
+
if (segment.reachable) {
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
return true;
|
33
40
|
}
|
34
41
|
|
35
42
|
/**
|
@@ -124,7 +131,6 @@ module.exports = {
|
|
124
131
|
},
|
125
132
|
|
126
133
|
create(context) {
|
127
|
-
let currentCodePath = null;
|
128
134
|
|
129
135
|
/** @type {ConstructorInfo | null} */
|
130
136
|
let constructorInfo = null;
|
@@ -132,6 +138,12 @@ module.exports = {
|
|
132
138
|
/** @type {ConsecutiveRange} */
|
133
139
|
const range = new ConsecutiveRange(context.sourceCode);
|
134
140
|
|
141
|
+
/** @type {Array<Set<CodePathSegment>>} */
|
142
|
+
const codePathSegments = [];
|
143
|
+
|
144
|
+
/** @type {Set<CodePathSegment>} */
|
145
|
+
let currentCodePathSegments = new Set();
|
146
|
+
|
135
147
|
/**
|
136
148
|
* Reports a given node if it's unreachable.
|
137
149
|
* @param {ASTNode} node A statement node to report.
|
@@ -140,7 +152,7 @@ module.exports = {
|
|
140
152
|
function reportIfUnreachable(node) {
|
141
153
|
let nextNode = null;
|
142
154
|
|
143
|
-
if (node && (node.type === "PropertyDefinition" ||
|
155
|
+
if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) {
|
144
156
|
|
145
157
|
// Store this statement to distinguish consecutive statements.
|
146
158
|
if (range.isEmpty) {
|
@@ -181,12 +193,29 @@ module.exports = {
|
|
181
193
|
return {
|
182
194
|
|
183
195
|
// Manages the current code path.
|
184
|
-
onCodePathStart(
|
185
|
-
|
196
|
+
onCodePathStart() {
|
197
|
+
codePathSegments.push(currentCodePathSegments);
|
198
|
+
currentCodePathSegments = new Set();
|
186
199
|
},
|
187
200
|
|
188
201
|
onCodePathEnd() {
|
189
|
-
|
202
|
+
currentCodePathSegments = codePathSegments.pop();
|
203
|
+
},
|
204
|
+
|
205
|
+
onUnreachableCodePathSegmentStart(segment) {
|
206
|
+
currentCodePathSegments.add(segment);
|
207
|
+
},
|
208
|
+
|
209
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
210
|
+
currentCodePathSegments.delete(segment);
|
211
|
+
},
|
212
|
+
|
213
|
+
onCodePathSegmentEnd(segment) {
|
214
|
+
currentCodePathSegments.delete(segment);
|
215
|
+
},
|
216
|
+
|
217
|
+
onCodePathSegmentStart(segment) {
|
218
|
+
currentCodePathSegments.add(segment);
|
190
219
|
},
|
191
220
|
|
192
221
|
// Registers for all statement nodes (excludes FunctionDeclaration).
|
@@ -57,6 +57,22 @@ function isInFinally(node) {
|
|
57
57
|
return false;
|
58
58
|
}
|
59
59
|
|
60
|
+
/**
|
61
|
+
* Checks all segments in a set and returns true if any are reachable.
|
62
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
63
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
64
|
+
*/
|
65
|
+
function isAnySegmentReachable(segments) {
|
66
|
+
|
67
|
+
for (const segment of segments) {
|
68
|
+
if (segment.reachable) {
|
69
|
+
return true;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
return false;
|
74
|
+
}
|
75
|
+
|
60
76
|
//------------------------------------------------------------------------------
|
61
77
|
// Rule Definition
|
62
78
|
//------------------------------------------------------------------------------
|
@@ -205,7 +221,6 @@ module.exports = {
|
|
205
221
|
*/
|
206
222
|
function markReturnStatementsOnCurrentSegmentsAsUsed() {
|
207
223
|
scopeInfo
|
208
|
-
.codePath
|
209
224
|
.currentSegments
|
210
225
|
.forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
|
211
226
|
}
|
@@ -222,7 +237,8 @@ module.exports = {
|
|
222
237
|
upper: scopeInfo,
|
223
238
|
uselessReturns: [],
|
224
239
|
traversedTryBlockStatements: [],
|
225
|
-
codePath
|
240
|
+
codePath,
|
241
|
+
currentSegments: new Set()
|
226
242
|
};
|
227
243
|
},
|
228
244
|
|
@@ -259,6 +275,9 @@ module.exports = {
|
|
259
275
|
* NOTE: This event is notified for only reachable segments.
|
260
276
|
*/
|
261
277
|
onCodePathSegmentStart(segment) {
|
278
|
+
|
279
|
+
scopeInfo.currentSegments.add(segment);
|
280
|
+
|
262
281
|
const info = {
|
263
282
|
uselessReturns: getUselessReturns([], segment.allPrevSegments),
|
264
283
|
returned: false
|
@@ -268,6 +287,18 @@ module.exports = {
|
|
268
287
|
segmentInfoMap.set(segment, info);
|
269
288
|
},
|
270
289
|
|
290
|
+
onUnreachableCodePathSegmentStart(segment) {
|
291
|
+
scopeInfo.currentSegments.add(segment);
|
292
|
+
},
|
293
|
+
|
294
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
295
|
+
scopeInfo.currentSegments.delete(segment);
|
296
|
+
},
|
297
|
+
|
298
|
+
onCodePathSegmentEnd(segment) {
|
299
|
+
scopeInfo.currentSegments.delete(segment);
|
300
|
+
},
|
301
|
+
|
271
302
|
// Adds ReturnStatement node to check whether it's useless or not.
|
272
303
|
ReturnStatement(node) {
|
273
304
|
if (node.argument) {
|
@@ -279,12 +310,12 @@ module.exports = {
|
|
279
310
|
isInFinally(node) ||
|
280
311
|
|
281
312
|
// Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
|
282
|
-
!scopeInfo.
|
313
|
+
!isAnySegmentReachable(scopeInfo.currentSegments)
|
283
314
|
) {
|
284
315
|
return;
|
285
316
|
}
|
286
317
|
|
287
|
-
for (const segment of scopeInfo.
|
318
|
+
for (const segment of scopeInfo.currentSegments) {
|
288
319
|
const info = segmentInfoMap.get(segment);
|
289
320
|
|
290
321
|
if (info) {
|
@@ -213,7 +213,8 @@ module.exports = {
|
|
213
213
|
stack = {
|
214
214
|
upper: stack,
|
215
215
|
codePath,
|
216
|
-
referenceMap: shouldVerify ? createReferenceMap(scope) : null
|
216
|
+
referenceMap: shouldVerify ? createReferenceMap(scope) : null,
|
217
|
+
currentSegments: new Set()
|
217
218
|
};
|
218
219
|
},
|
219
220
|
onCodePathEnd() {
|
@@ -223,11 +224,25 @@ module.exports = {
|
|
223
224
|
// Initialize the segment information.
|
224
225
|
onCodePathSegmentStart(segment) {
|
225
226
|
segmentInfo.initialize(segment);
|
227
|
+
stack.currentSegments.add(segment);
|
226
228
|
},
|
227
229
|
|
230
|
+
onUnreachableCodePathSegmentStart(segment) {
|
231
|
+
stack.currentSegments.add(segment);
|
232
|
+
},
|
233
|
+
|
234
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
235
|
+
stack.currentSegments.delete(segment);
|
236
|
+
},
|
237
|
+
|
238
|
+
onCodePathSegmentEnd(segment) {
|
239
|
+
stack.currentSegments.delete(segment);
|
240
|
+
},
|
241
|
+
|
242
|
+
|
228
243
|
// Handle references to prepare verification.
|
229
244
|
Identifier(node) {
|
230
|
-
const {
|
245
|
+
const { referenceMap } = stack;
|
231
246
|
const reference = referenceMap && referenceMap.get(node);
|
232
247
|
|
233
248
|
// Ignore if this is not a valid variable reference.
|
@@ -240,7 +255,7 @@ module.exports = {
|
|
240
255
|
|
241
256
|
// Add a fresh read variable.
|
242
257
|
if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
|
243
|
-
segmentInfo.markAsRead(
|
258
|
+
segmentInfo.markAsRead(stack.currentSegments, variable);
|
244
259
|
}
|
245
260
|
|
246
261
|
/*
|
@@ -267,16 +282,15 @@ module.exports = {
|
|
267
282
|
* If the reference exists in `outdatedReadVariables` list, report it.
|
268
283
|
*/
|
269
284
|
":expression:exit"(node) {
|
270
|
-
const { codePath, referenceMap } = stack;
|
271
285
|
|
272
286
|
// referenceMap exists if this is in a resumable function scope.
|
273
|
-
if (!referenceMap) {
|
287
|
+
if (!stack.referenceMap) {
|
274
288
|
return;
|
275
289
|
}
|
276
290
|
|
277
291
|
// Mark the read variables on this code path as outdated.
|
278
292
|
if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
|
279
|
-
segmentInfo.makeOutdated(
|
293
|
+
segmentInfo.makeOutdated(stack.currentSegments);
|
280
294
|
}
|
281
295
|
|
282
296
|
// Verify.
|
@@ -288,7 +302,7 @@ module.exports = {
|
|
288
302
|
for (const reference of references) {
|
289
303
|
const variable = reference.resolved;
|
290
304
|
|
291
|
-
if (segmentInfo.isOutdated(
|
305
|
+
if (segmentInfo.isOutdated(stack.currentSegments, variable)) {
|
292
306
|
if (node.parent.left === reference.identifier) {
|
293
307
|
context.report({
|
294
308
|
node: node.parent,
|