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
@@ -48,7 +48,8 @@ const
|
|
48
48
|
equal = require("fast-deep-equal"),
|
49
49
|
Traverser = require("../../lib/shared/traverser"),
|
50
50
|
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
|
51
|
-
{ Linter, SourceCodeFixer, interpolate } = require("../linter")
|
51
|
+
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
|
52
|
+
CodePath = require("../linter/code-path-analysis/code-path");
|
52
53
|
|
53
54
|
const ajv = require("../shared/ajv")({ strictDefaults: true });
|
54
55
|
|
@@ -162,8 +163,43 @@ const suggestionObjectParameters = new Set([
|
|
162
163
|
]);
|
163
164
|
const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
|
164
165
|
|
166
|
+
const forbiddenMethods = [
|
167
|
+
"applyInlineConfig",
|
168
|
+
"applyLanguageOptions",
|
169
|
+
"finalize"
|
170
|
+
];
|
171
|
+
|
165
172
|
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
|
166
173
|
|
174
|
+
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
175
|
+
getSource: "getText",
|
176
|
+
getSourceLines: "getLines",
|
177
|
+
getAllComments: "getAllComments",
|
178
|
+
getNodeByRangeIndex: "getNodeByRangeIndex",
|
179
|
+
|
180
|
+
// getComments: "getComments", -- already handled by a separate error
|
181
|
+
getCommentsBefore: "getCommentsBefore",
|
182
|
+
getCommentsAfter: "getCommentsAfter",
|
183
|
+
getCommentsInside: "getCommentsInside",
|
184
|
+
getJSDocComment: "getJSDocComment",
|
185
|
+
getFirstToken: "getFirstToken",
|
186
|
+
getFirstTokens: "getFirstTokens",
|
187
|
+
getLastToken: "getLastToken",
|
188
|
+
getLastTokens: "getLastTokens",
|
189
|
+
getTokenAfter: "getTokenAfter",
|
190
|
+
getTokenBefore: "getTokenBefore",
|
191
|
+
getTokenByRangeStart: "getTokenByRangeStart",
|
192
|
+
getTokens: "getTokens",
|
193
|
+
getTokensAfter: "getTokensAfter",
|
194
|
+
getTokensBefore: "getTokensBefore",
|
195
|
+
getTokensBetween: "getTokensBetween",
|
196
|
+
|
197
|
+
getScope: "getScope",
|
198
|
+
getAncestors: "getAncestors",
|
199
|
+
getDeclaredVariables: "getDeclaredVariables",
|
200
|
+
markVariableAsUsed: "markVariableAsUsed"
|
201
|
+
};
|
202
|
+
|
167
203
|
/**
|
168
204
|
* Clones a given value deeply.
|
169
205
|
* Note: This ignores `parent` property.
|
@@ -305,6 +341,19 @@ function getCommentsDeprecation() {
|
|
305
341
|
);
|
306
342
|
}
|
307
343
|
|
344
|
+
/**
|
345
|
+
* Function to replace forbidden `SourceCode` methods.
|
346
|
+
* @param {string} methodName The name of the method to forbid.
|
347
|
+
* @returns {Function} The function that throws the error.
|
348
|
+
*/
|
349
|
+
function throwForbiddenMethodError(methodName) {
|
350
|
+
return () => {
|
351
|
+
throw new Error(
|
352
|
+
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
|
353
|
+
);
|
354
|
+
};
|
355
|
+
}
|
356
|
+
|
308
357
|
/**
|
309
358
|
* Emit a deprecation warning if function-style format is being used.
|
310
359
|
* @param {string} ruleName Name of the rule.
|
@@ -335,6 +384,53 @@ function emitMissingSchemaWarning(ruleName) {
|
|
335
384
|
}
|
336
385
|
}
|
337
386
|
|
387
|
+
/**
|
388
|
+
* Emit a deprecation warning if a rule uses a deprecated `context` method.
|
389
|
+
* @param {string} ruleName Name of the rule.
|
390
|
+
* @param {string} methodName The name of the method on `context` that was used.
|
391
|
+
* @returns {void}
|
392
|
+
*/
|
393
|
+
function emitDeprecatedContextMethodWarning(ruleName, methodName) {
|
394
|
+
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
|
395
|
+
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
|
396
|
+
process.emitWarning(
|
397
|
+
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
|
398
|
+
"DeprecationWarning"
|
399
|
+
);
|
400
|
+
}
|
401
|
+
}
|
402
|
+
|
403
|
+
/**
|
404
|
+
* Emit a deprecation warning if rule uses CodePath#currentSegments.
|
405
|
+
* @param {string} ruleName Name of the rule.
|
406
|
+
* @returns {void}
|
407
|
+
*/
|
408
|
+
function emitCodePathCurrentSegmentsWarning(ruleName) {
|
409
|
+
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
|
410
|
+
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
|
411
|
+
process.emitWarning(
|
412
|
+
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
|
413
|
+
"DeprecationWarning"
|
414
|
+
);
|
415
|
+
}
|
416
|
+
}
|
417
|
+
|
418
|
+
/**
|
419
|
+
* Emit a deprecation warning if `context.parserServices` is used.
|
420
|
+
* @param {string} ruleName Name of the rule.
|
421
|
+
* @returns {void}
|
422
|
+
*/
|
423
|
+
function emitParserServicesWarning(ruleName) {
|
424
|
+
if (!emitParserServicesWarning[`warned-${ruleName}`]) {
|
425
|
+
emitParserServicesWarning[`warned-${ruleName}`] = true;
|
426
|
+
process.emitWarning(
|
427
|
+
`"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
|
428
|
+
"DeprecationWarning"
|
429
|
+
);
|
430
|
+
}
|
431
|
+
}
|
432
|
+
|
433
|
+
|
338
434
|
//------------------------------------------------------------------------------
|
339
435
|
// Public Interface
|
340
436
|
//------------------------------------------------------------------------------
|
@@ -566,7 +662,38 @@ class RuleTester {
|
|
566
662
|
freezeDeeply(context.settings);
|
567
663
|
freezeDeeply(context.parserOptions);
|
568
664
|
|
569
|
-
|
665
|
+
// wrap all deprecated methods
|
666
|
+
const newContext = Object.create(
|
667
|
+
context,
|
668
|
+
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
|
669
|
+
methodName,
|
670
|
+
{
|
671
|
+
value(...args) {
|
672
|
+
|
673
|
+
// emit deprecation warning
|
674
|
+
emitDeprecatedContextMethodWarning(ruleName, methodName);
|
675
|
+
|
676
|
+
// call the original method
|
677
|
+
return context[methodName].call(this, ...args);
|
678
|
+
},
|
679
|
+
enumerable: true
|
680
|
+
}
|
681
|
+
]))
|
682
|
+
);
|
683
|
+
|
684
|
+
// emit warning about context.parserServices
|
685
|
+
const parserServices = context.parserServices;
|
686
|
+
|
687
|
+
Object.defineProperty(newContext, "parserServices", {
|
688
|
+
get() {
|
689
|
+
emitParserServicesWarning(ruleName);
|
690
|
+
return parserServices;
|
691
|
+
}
|
692
|
+
});
|
693
|
+
|
694
|
+
Object.freeze(newContext);
|
695
|
+
|
696
|
+
return (typeof rule === "function" ? rule : rule.create)(newContext);
|
570
697
|
}
|
571
698
|
}));
|
572
699
|
|
@@ -685,14 +812,30 @@ class RuleTester {
|
|
685
812
|
validate(config, "rule-tester", id => (id === ruleName ? rule : null));
|
686
813
|
|
687
814
|
// Verify the code.
|
688
|
-
const { getComments } = SourceCode.prototype;
|
815
|
+
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
|
816
|
+
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
|
689
817
|
let messages;
|
690
818
|
|
691
819
|
try {
|
692
820
|
SourceCode.prototype.getComments = getCommentsDeprecation;
|
821
|
+
Object.defineProperty(CodePath.prototype, "currentSegments", {
|
822
|
+
get() {
|
823
|
+
emitCodePathCurrentSegmentsWarning(ruleName);
|
824
|
+
return originalCurrentSegments.get.call(this);
|
825
|
+
}
|
826
|
+
});
|
827
|
+
|
828
|
+
forbiddenMethods.forEach(methodName => {
|
829
|
+
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
|
830
|
+
});
|
831
|
+
|
693
832
|
messages = linter.verify(code, config, filename);
|
694
833
|
} finally {
|
695
834
|
SourceCode.prototype.getComments = getComments;
|
835
|
+
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
|
836
|
+
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
|
837
|
+
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
|
838
|
+
SourceCode.prototype.finalize = finalize;
|
696
839
|
}
|
697
840
|
|
698
841
|
const fatalErrorMessage = messages.find(m => m.fatal);
|
@@ -18,15 +18,6 @@ const astUtils = require("./utils/ast-utils");
|
|
18
18
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
19
19
|
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;
|
20
20
|
|
21
|
-
/**
|
22
|
-
* Checks a given code path segment is reachable.
|
23
|
-
* @param {CodePathSegment} segment A segment to check.
|
24
|
-
* @returns {boolean} `true` if the segment is reachable.
|
25
|
-
*/
|
26
|
-
function isReachable(segment) {
|
27
|
-
return segment.reachable;
|
28
|
-
}
|
29
|
-
|
30
21
|
/**
|
31
22
|
* Checks a given node is a member access which has the specified name's
|
32
23
|
* property.
|
@@ -38,6 +29,22 @@ function isTargetMethod(node) {
|
|
38
29
|
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
|
39
30
|
}
|
40
31
|
|
32
|
+
/**
|
33
|
+
* Checks all segments in a set and returns true if any are reachable.
|
34
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
35
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
36
|
+
*/
|
37
|
+
function isAnySegmentReachable(segments) {
|
38
|
+
|
39
|
+
for (const segment of segments) {
|
40
|
+
if (segment.reachable) {
|
41
|
+
return true;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
|
41
48
|
/**
|
42
49
|
* Returns a human-legible description of an array method
|
43
50
|
* @param {string} arrayMethodName A method name to fully qualify
|
@@ -129,6 +136,76 @@ function getArrayMethodName(node) {
|
|
129
136
|
return null;
|
130
137
|
}
|
131
138
|
|
139
|
+
/**
|
140
|
+
* Checks if the given node is a void expression.
|
141
|
+
* @param {ASTNode} node The node to check.
|
142
|
+
* @returns {boolean} - `true` if the node is a void expression
|
143
|
+
*/
|
144
|
+
function isExpressionVoid(node) {
|
145
|
+
return node.type === "UnaryExpression" && node.operator === "void";
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Fixes the linting error by prepending "void " to the given node
|
150
|
+
* @param {Object} sourceCode context given by context.sourceCode
|
151
|
+
* @param {ASTNode} node The node to fix.
|
152
|
+
* @param {Object} fixer The fixer object provided by ESLint.
|
153
|
+
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
154
|
+
*/
|
155
|
+
function voidPrependFixer(sourceCode, node, fixer) {
|
156
|
+
|
157
|
+
const requiresParens =
|
158
|
+
|
159
|
+
// prepending `void ` will fail if the node has a lower precedence than void
|
160
|
+
astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
|
161
|
+
|
162
|
+
// check if there are parentheses around the node to avoid redundant parentheses
|
163
|
+
!astUtils.isParenthesised(sourceCode, node);
|
164
|
+
|
165
|
+
// avoid parentheses issues
|
166
|
+
const returnOrArrowToken = sourceCode.getTokenBefore(
|
167
|
+
node,
|
168
|
+
node.parent.type === "ArrowFunctionExpression"
|
169
|
+
? astUtils.isArrowToken
|
170
|
+
|
171
|
+
// isReturnToken
|
172
|
+
: token => token.type === "Keyword" && token.value === "return"
|
173
|
+
);
|
174
|
+
|
175
|
+
const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
|
176
|
+
|
177
|
+
const prependSpace =
|
178
|
+
|
179
|
+
// is return token, as => allows void to be adjacent
|
180
|
+
returnOrArrowToken.value === "return" &&
|
181
|
+
|
182
|
+
// If two tokens (return and "(") are adjacent
|
183
|
+
returnOrArrowToken.range[1] === firstToken.range[0];
|
184
|
+
|
185
|
+
return [
|
186
|
+
fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
|
187
|
+
fixer.insertTextAfter(node, requiresParens ? ")" : "")
|
188
|
+
];
|
189
|
+
}
|
190
|
+
|
191
|
+
/**
|
192
|
+
* Fixes the linting error by `wrapping {}` around the given node's body.
|
193
|
+
* @param {Object} sourceCode context given by context.sourceCode
|
194
|
+
* @param {ASTNode} node The node to fix.
|
195
|
+
* @param {Object} fixer The fixer object provided by ESLint.
|
196
|
+
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
197
|
+
*/
|
198
|
+
function curlyWrapFixer(sourceCode, node, fixer) {
|
199
|
+
const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
|
200
|
+
const firstToken = sourceCode.getTokenAfter(arrowToken);
|
201
|
+
const lastToken = sourceCode.getLastToken(node);
|
202
|
+
|
203
|
+
return [
|
204
|
+
fixer.insertTextBefore(firstToken, "{"),
|
205
|
+
fixer.insertTextAfter(lastToken, "}")
|
206
|
+
];
|
207
|
+
}
|
208
|
+
|
132
209
|
//------------------------------------------------------------------------------
|
133
210
|
// Rule Definition
|
134
211
|
//------------------------------------------------------------------------------
|
@@ -144,6 +221,9 @@ module.exports = {
|
|
144
221
|
url: "https://eslint.org/docs/latest/rules/array-callback-return"
|
145
222
|
},
|
146
223
|
|
224
|
+
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
|
225
|
+
hasSuggestions: true,
|
226
|
+
|
147
227
|
schema: [
|
148
228
|
{
|
149
229
|
type: "object",
|
@@ -155,6 +235,10 @@ module.exports = {
|
|
155
235
|
checkForEach: {
|
156
236
|
type: "boolean",
|
157
237
|
default: false
|
238
|
+
},
|
239
|
+
allowVoid: {
|
240
|
+
type: "boolean",
|
241
|
+
default: false
|
158
242
|
}
|
159
243
|
},
|
160
244
|
additionalProperties: false
|
@@ -165,13 +249,15 @@ module.exports = {
|
|
165
249
|
expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
|
166
250
|
expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
|
167
251
|
expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
|
168
|
-
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
|
252
|
+
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
|
253
|
+
wrapBraces: "Wrap the expression in `{}`.",
|
254
|
+
prependVoid: "Prepend `void` to the expression."
|
169
255
|
}
|
170
256
|
},
|
171
257
|
|
172
258
|
create(context) {
|
173
259
|
|
174
|
-
const options = context.options[0] || { allowImplicit: false, checkForEach: false };
|
260
|
+
const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
|
175
261
|
const sourceCode = context.sourceCode;
|
176
262
|
|
177
263
|
let funcInfo = {
|
@@ -198,26 +284,56 @@ module.exports = {
|
|
198
284
|
return;
|
199
285
|
}
|
200
286
|
|
201
|
-
|
287
|
+
const messageAndSuggestions = { messageId: "", suggest: [] };
|
202
288
|
|
203
289
|
if (funcInfo.arrayMethodName === "forEach") {
|
204
290
|
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
|
205
|
-
|
291
|
+
|
292
|
+
if (options.allowVoid) {
|
293
|
+
if (isExpressionVoid(node.body)) {
|
294
|
+
return;
|
295
|
+
}
|
296
|
+
|
297
|
+
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
298
|
+
messageAndSuggestions.suggest = [
|
299
|
+
{
|
300
|
+
messageId: "wrapBraces",
|
301
|
+
fix(fixer) {
|
302
|
+
return curlyWrapFixer(sourceCode, node, fixer);
|
303
|
+
}
|
304
|
+
},
|
305
|
+
{
|
306
|
+
messageId: "prependVoid",
|
307
|
+
fix(fixer) {
|
308
|
+
return voidPrependFixer(sourceCode, node.body, fixer);
|
309
|
+
}
|
310
|
+
}
|
311
|
+
];
|
312
|
+
} else {
|
313
|
+
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
314
|
+
messageAndSuggestions.suggest = [{
|
315
|
+
messageId: "wrapBraces",
|
316
|
+
fix(fixer) {
|
317
|
+
return curlyWrapFixer(sourceCode, node, fixer);
|
318
|
+
}
|
319
|
+
}];
|
320
|
+
}
|
206
321
|
}
|
207
322
|
} else {
|
208
|
-
if (node.body.type === "BlockStatement" && funcInfo.
|
209
|
-
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
|
323
|
+
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
|
324
|
+
messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
|
210
325
|
}
|
211
326
|
}
|
212
327
|
|
213
|
-
if (messageId) {
|
328
|
+
if (messageAndSuggestions.messageId) {
|
214
329
|
const name = astUtils.getFunctionNameWithKind(node);
|
215
330
|
|
216
331
|
context.report({
|
217
332
|
node,
|
218
333
|
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
|
219
|
-
messageId,
|
220
|
-
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
|
334
|
+
messageId: messageAndSuggestions.messageId,
|
335
|
+
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
|
336
|
+
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
|
221
337
|
});
|
222
338
|
}
|
223
339
|
}
|
@@ -242,7 +358,8 @@ module.exports = {
|
|
242
358
|
methodName &&
|
243
359
|
!node.async &&
|
244
360
|
!node.generator,
|
245
|
-
node
|
361
|
+
node,
|
362
|
+
currentSegments: new Set()
|
246
363
|
};
|
247
364
|
},
|
248
365
|
|
@@ -251,6 +368,23 @@ module.exports = {
|
|
251
368
|
funcInfo = funcInfo.upper;
|
252
369
|
},
|
253
370
|
|
371
|
+
onUnreachableCodePathSegmentStart(segment) {
|
372
|
+
funcInfo.currentSegments.add(segment);
|
373
|
+
},
|
374
|
+
|
375
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
376
|
+
funcInfo.currentSegments.delete(segment);
|
377
|
+
},
|
378
|
+
|
379
|
+
onCodePathSegmentStart(segment) {
|
380
|
+
funcInfo.currentSegments.add(segment);
|
381
|
+
},
|
382
|
+
|
383
|
+
onCodePathSegmentEnd(segment) {
|
384
|
+
funcInfo.currentSegments.delete(segment);
|
385
|
+
},
|
386
|
+
|
387
|
+
|
254
388
|
// Checks the return statement is valid.
|
255
389
|
ReturnStatement(node) {
|
256
390
|
|
@@ -260,30 +394,46 @@ module.exports = {
|
|
260
394
|
|
261
395
|
funcInfo.hasReturn = true;
|
262
396
|
|
263
|
-
|
397
|
+
const messageAndSuggestions = { messageId: "", suggest: [] };
|
264
398
|
|
265
399
|
if (funcInfo.arrayMethodName === "forEach") {
|
266
400
|
|
267
401
|
// if checkForEach: true, returning a value at any path inside a forEach is not allowed
|
268
402
|
if (options.checkForEach && node.argument) {
|
269
|
-
|
403
|
+
|
404
|
+
if (options.allowVoid) {
|
405
|
+
if (isExpressionVoid(node.argument)) {
|
406
|
+
return;
|
407
|
+
}
|
408
|
+
|
409
|
+
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
410
|
+
messageAndSuggestions.suggest = [{
|
411
|
+
messageId: "prependVoid",
|
412
|
+
fix(fixer) {
|
413
|
+
return voidPrependFixer(sourceCode, node.argument, fixer);
|
414
|
+
}
|
415
|
+
}];
|
416
|
+
} else {
|
417
|
+
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
418
|
+
}
|
270
419
|
}
|
271
420
|
} else {
|
272
421
|
|
273
422
|
// if allowImplicit: false, should also check node.argument
|
274
423
|
if (!options.allowImplicit && !node.argument) {
|
275
|
-
messageId = "expectedReturnValue";
|
424
|
+
messageAndSuggestions.messageId = "expectedReturnValue";
|
276
425
|
}
|
277
426
|
}
|
278
427
|
|
279
|
-
if (messageId) {
|
428
|
+
if (messageAndSuggestions.messageId) {
|
280
429
|
context.report({
|
281
430
|
node,
|
282
|
-
messageId,
|
431
|
+
messageId: messageAndSuggestions.messageId,
|
283
432
|
data: {
|
284
433
|
name: astUtils.getFunctionNameWithKind(funcInfo.node),
|
285
434
|
arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
|
286
|
-
}
|
435
|
+
},
|
436
|
+
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
|
287
437
|
});
|
288
438
|
}
|
289
439
|
},
|
@@ -16,12 +16,19 @@ const { upperCaseFirst } = require("../shared/string-utils");
|
|
16
16
|
//------------------------------------------------------------------------------
|
17
17
|
|
18
18
|
/**
|
19
|
-
* Checks
|
20
|
-
* @param {CodePathSegment}
|
21
|
-
* @returns {boolean}
|
19
|
+
* Checks all segments in a set and returns true if all are unreachable.
|
20
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
21
|
+
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
22
22
|
*/
|
23
|
-
function
|
24
|
-
|
23
|
+
function areAllSegmentsUnreachable(segments) {
|
24
|
+
|
25
|
+
for (const segment of segments) {
|
26
|
+
if (segment.reachable) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
return true;
|
25
32
|
}
|
26
33
|
|
27
34
|
/**
|
@@ -88,7 +95,7 @@ module.exports = {
|
|
88
95
|
* When unreachable, all paths are returned or thrown.
|
89
96
|
*/
|
90
97
|
if (!funcInfo.hasReturnValue ||
|
91
|
-
funcInfo.
|
98
|
+
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
|
92
99
|
astUtils.isES5Constructor(node) ||
|
93
100
|
isClassConstructor(node)
|
94
101
|
) {
|
@@ -141,13 +148,31 @@ module.exports = {
|
|
141
148
|
hasReturn: false,
|
142
149
|
hasReturnValue: false,
|
143
150
|
messageId: "",
|
144
|
-
node
|
151
|
+
node,
|
152
|
+
currentSegments: new Set()
|
145
153
|
};
|
146
154
|
},
|
147
155
|
onCodePathEnd() {
|
148
156
|
funcInfo = funcInfo.upper;
|
149
157
|
},
|
150
158
|
|
159
|
+
onUnreachableCodePathSegmentStart(segment) {
|
160
|
+
funcInfo.currentSegments.add(segment);
|
161
|
+
},
|
162
|
+
|
163
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
164
|
+
funcInfo.currentSegments.delete(segment);
|
165
|
+
},
|
166
|
+
|
167
|
+
onCodePathSegmentStart(segment) {
|
168
|
+
funcInfo.currentSegments.add(segment);
|
169
|
+
},
|
170
|
+
|
171
|
+
onCodePathSegmentEnd(segment) {
|
172
|
+
funcInfo.currentSegments.delete(segment);
|
173
|
+
},
|
174
|
+
|
175
|
+
|
151
176
|
// Reports a given return statement if it's inconsistent.
|
152
177
|
ReturnStatement(node) {
|
153
178
|
const argument = node.argument;
|
@@ -10,12 +10,19 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
/**
|
13
|
-
* Checks
|
14
|
-
* @param {CodePathSegment}
|
15
|
-
* @returns {boolean}
|
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
16
|
*/
|
17
|
-
function
|
18
|
-
|
17
|
+
function isAnySegmentReachable(segments) {
|
18
|
+
|
19
|
+
for (const segment of segments) {
|
20
|
+
if (segment.reachable) {
|
21
|
+
return true;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
return false;
|
19
26
|
}
|
20
27
|
|
21
28
|
/**
|
@@ -210,7 +217,8 @@ module.exports = {
|
|
210
217
|
isConstructor: true,
|
211
218
|
hasExtends: Boolean(superClass),
|
212
219
|
superIsConstructor: isPossibleConstructor(superClass),
|
213
|
-
codePath
|
220
|
+
codePath,
|
221
|
+
currentSegments: new Set()
|
214
222
|
};
|
215
223
|
} else {
|
216
224
|
funcInfo = {
|
@@ -218,7 +226,8 @@ module.exports = {
|
|
218
226
|
isConstructor: false,
|
219
227
|
hasExtends: false,
|
220
228
|
superIsConstructor: false,
|
221
|
-
codePath
|
229
|
+
codePath,
|
230
|
+
currentSegments: new Set()
|
222
231
|
};
|
223
232
|
}
|
224
233
|
},
|
@@ -261,6 +270,9 @@ module.exports = {
|
|
261
270
|
* @returns {void}
|
262
271
|
*/
|
263
272
|
onCodePathSegmentStart(segment) {
|
273
|
+
|
274
|
+
funcInfo.currentSegments.add(segment);
|
275
|
+
|
264
276
|
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
|
265
277
|
return;
|
266
278
|
}
|
@@ -281,6 +293,19 @@ module.exports = {
|
|
281
293
|
}
|
282
294
|
},
|
283
295
|
|
296
|
+
onUnreachableCodePathSegmentStart(segment) {
|
297
|
+
funcInfo.currentSegments.add(segment);
|
298
|
+
},
|
299
|
+
|
300
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
301
|
+
funcInfo.currentSegments.delete(segment);
|
302
|
+
},
|
303
|
+
|
304
|
+
onCodePathSegmentEnd(segment) {
|
305
|
+
funcInfo.currentSegments.delete(segment);
|
306
|
+
},
|
307
|
+
|
308
|
+
|
284
309
|
/**
|
285
310
|
* Update information of the code path segment when a code path was
|
286
311
|
* looped.
|
@@ -344,12 +369,11 @@ module.exports = {
|
|
344
369
|
|
345
370
|
// Reports if needed.
|
346
371
|
if (funcInfo.hasExtends) {
|
347
|
-
const segments = funcInfo.
|
372
|
+
const segments = funcInfo.currentSegments;
|
348
373
|
let duplicate = false;
|
349
374
|
let info = null;
|
350
375
|
|
351
|
-
for (
|
352
|
-
const segment = segments[i];
|
376
|
+
for (const segment of segments) {
|
353
377
|
|
354
378
|
if (segment.reachable) {
|
355
379
|
info = segInfoMap[segment.id];
|
@@ -374,7 +398,7 @@ module.exports = {
|
|
374
398
|
info.validNodes.push(node);
|
375
399
|
}
|
376
400
|
}
|
377
|
-
} else if (funcInfo.
|
401
|
+
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
|
378
402
|
context.report({
|
379
403
|
messageId: "unexpected",
|
380
404
|
node
|
@@ -398,10 +422,9 @@ module.exports = {
|
|
398
422
|
}
|
399
423
|
|
400
424
|
// Returning argument is a substitute of 'super()'.
|
401
|
-
const segments = funcInfo.
|
425
|
+
const segments = funcInfo.currentSegments;
|
402
426
|
|
403
|
-
for (
|
404
|
-
const segment = segments[i];
|
427
|
+
for (const segment of segments) {
|
405
428
|
|
406
429
|
if (segment.reachable) {
|
407
430
|
const info = segInfoMap[segment.id];
|