eslint 8.46.0 → 8.48.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 +6 -1
- package/lib/linter/code-path-analysis/code-path-segment.js +52 -24
- package/lib/rule-tester/flat-rule-tester.js +26 -19
- package/lib/rule-tester/rule-tester.js +30 -20
- package/lib/rules/for-direction.js +15 -8
- package/lib/rules/no-new-wrappers.js +19 -7
- package/lib/rules/no-promise-executor-return.js +154 -16
- package/lib/rules/utils/ast-utils.js +4 -4
- package/messages/eslintrc-incompat.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
@@ -249,6 +249,11 @@ Bryan Mishkin
|
|
249
249
|
<img src="https://github.com/fasttime.png?s=75" width="75" height="75"><br />
|
250
250
|
Francesco Trotta
|
251
251
|
</a>
|
252
|
+
</td><td align="center" valign="top" width="11%">
|
253
|
+
<a href="https://github.com/ota-meshi">
|
254
|
+
<img src="https://github.com/ota-meshi.png?s=75" width="75" height="75"><br />
|
255
|
+
Yosuke Ota
|
256
|
+
</a>
|
252
257
|
</td></tr></tbody></table>
|
253
258
|
|
254
259
|
### Website Team
|
@@ -284,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
284
289
|
<p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
|
285
290
|
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
|
286
291
|
<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
|
287
|
-
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://
|
292
|
+
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
|
288
293
|
<!--sponsorsend-->
|
289
294
|
|
290
295
|
## Technology Sponsors
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @fileoverview
|
2
|
+
* @fileoverview The CodePathSegment class.
|
3
3
|
* @author Toru Nagashima
|
4
4
|
*/
|
5
5
|
|
@@ -30,10 +30,22 @@ function isReachable(segment) {
|
|
30
30
|
|
31
31
|
/**
|
32
32
|
* A code path segment.
|
33
|
+
*
|
34
|
+
* Each segment is arranged in a series of linked lists (implemented by arrays)
|
35
|
+
* that keep track of the previous and next segments in a code path. In this way,
|
36
|
+
* you can navigate between all segments in any code path so long as you have a
|
37
|
+
* reference to any segment in that code path.
|
38
|
+
*
|
39
|
+
* When first created, the segment is in a detached state, meaning that it knows the
|
40
|
+
* segments that came before it but those segments don't know that this new segment
|
41
|
+
* follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it
|
42
|
+
* officially become part of the code path by updating the previous segments to know
|
43
|
+
* that this new segment follows.
|
33
44
|
*/
|
34
45
|
class CodePathSegment {
|
35
46
|
|
36
47
|
/**
|
48
|
+
* Creates a new instance.
|
37
49
|
* @param {string} id An identifier.
|
38
50
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
|
39
51
|
* This array includes unreachable segments.
|
@@ -49,27 +61,25 @@ class CodePathSegment {
|
|
49
61
|
this.id = id;
|
50
62
|
|
51
63
|
/**
|
52
|
-
* An array of the next segments.
|
64
|
+
* An array of the next reachable segments.
|
53
65
|
* @type {CodePathSegment[]}
|
54
66
|
*/
|
55
67
|
this.nextSegments = [];
|
56
68
|
|
57
69
|
/**
|
58
|
-
* An array of the previous segments.
|
70
|
+
* An array of the previous reachable segments.
|
59
71
|
* @type {CodePathSegment[]}
|
60
72
|
*/
|
61
73
|
this.prevSegments = allPrevSegments.filter(isReachable);
|
62
74
|
|
63
75
|
/**
|
64
|
-
* An array of
|
65
|
-
* This array includes unreachable segments.
|
76
|
+
* An array of all next segments including reachable and unreachable.
|
66
77
|
* @type {CodePathSegment[]}
|
67
78
|
*/
|
68
79
|
this.allNextSegments = [];
|
69
80
|
|
70
81
|
/**
|
71
|
-
* An array of
|
72
|
-
* This array includes unreachable segments.
|
82
|
+
* An array of all previous segments including reachable and unreachable.
|
73
83
|
* @type {CodePathSegment[]}
|
74
84
|
*/
|
75
85
|
this.allPrevSegments = allPrevSegments;
|
@@ -83,7 +93,11 @@ class CodePathSegment {
|
|
83
93
|
// Internal data.
|
84
94
|
Object.defineProperty(this, "internal", {
|
85
95
|
value: {
|
96
|
+
|
97
|
+
// determines if the segment has been attached to the code path
|
86
98
|
used: false,
|
99
|
+
|
100
|
+
// array of previous segments coming from the end of a loop
|
87
101
|
loopedPrevSegments: []
|
88
102
|
}
|
89
103
|
});
|
@@ -113,9 +127,10 @@ class CodePathSegment {
|
|
113
127
|
}
|
114
128
|
|
115
129
|
/**
|
116
|
-
* Creates a segment
|
130
|
+
* Creates a new segment and appends it after the given segments.
|
117
131
|
* @param {string} id An identifier.
|
118
|
-
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments
|
132
|
+
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments
|
133
|
+
* to append to.
|
119
134
|
* @returns {CodePathSegment} The created segment.
|
120
135
|
*/
|
121
136
|
static newNext(id, allPrevSegments) {
|
@@ -127,7 +142,7 @@ class CodePathSegment {
|
|
127
142
|
}
|
128
143
|
|
129
144
|
/**
|
130
|
-
* Creates an unreachable segment
|
145
|
+
* Creates an unreachable segment and appends it after the given segments.
|
131
146
|
* @param {string} id An identifier.
|
132
147
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
|
133
148
|
* @returns {CodePathSegment} The created segment.
|
@@ -137,7 +152,7 @@ class CodePathSegment {
|
|
137
152
|
|
138
153
|
/*
|
139
154
|
* In `if (a) return a; foo();` case, the unreachable segment preceded by
|
140
|
-
* the return statement is not used but must not be
|
155
|
+
* the return statement is not used but must not be removed.
|
141
156
|
*/
|
142
157
|
CodePathSegment.markUsed(segment);
|
143
158
|
|
@@ -157,7 +172,7 @@ class CodePathSegment {
|
|
157
172
|
}
|
158
173
|
|
159
174
|
/**
|
160
|
-
*
|
175
|
+
* Marks a given segment as used.
|
161
176
|
*
|
162
177
|
* And this function registers the segment into the previous segments as a next.
|
163
178
|
* @param {CodePathSegment} segment A segment to mark.
|
@@ -172,6 +187,13 @@ class CodePathSegment {
|
|
172
187
|
let i;
|
173
188
|
|
174
189
|
if (segment.reachable) {
|
190
|
+
|
191
|
+
/*
|
192
|
+
* If the segment is reachable, then it's officially part of the
|
193
|
+
* code path. This loops through all previous segments to update
|
194
|
+
* their list of next segments. Because the segment is reachable,
|
195
|
+
* it's added to both `nextSegments` and `allNextSegments`.
|
196
|
+
*/
|
175
197
|
for (i = 0; i < segment.allPrevSegments.length; ++i) {
|
176
198
|
const prevSegment = segment.allPrevSegments[i];
|
177
199
|
|
@@ -179,6 +201,13 @@ class CodePathSegment {
|
|
179
201
|
prevSegment.nextSegments.push(segment);
|
180
202
|
}
|
181
203
|
} else {
|
204
|
+
|
205
|
+
/*
|
206
|
+
* If the segment is not reachable, then it's not officially part of the
|
207
|
+
* code path. This loops through all previous segments to update
|
208
|
+
* their list of next segments. Because the segment is not reachable,
|
209
|
+
* it's added only to `allNextSegments`.
|
210
|
+
*/
|
182
211
|
for (i = 0; i < segment.allPrevSegments.length; ++i) {
|
183
212
|
segment.allPrevSegments[i].allNextSegments.push(segment);
|
184
213
|
}
|
@@ -196,19 +225,20 @@ class CodePathSegment {
|
|
196
225
|
}
|
197
226
|
|
198
227
|
/**
|
199
|
-
*
|
200
|
-
*
|
201
|
-
*
|
228
|
+
* Creates a new array based on an array of segments. If any segment in the
|
229
|
+
* array is unused, then it is replaced by all of its previous segments.
|
230
|
+
* All used segments are returned as-is without replacement.
|
231
|
+
* @param {CodePathSegment[]} segments The array of segments to flatten.
|
232
|
+
* @returns {CodePathSegment[]} The flattened array.
|
202
233
|
*/
|
203
234
|
static flattenUnusedSegments(segments) {
|
204
|
-
const done =
|
205
|
-
const retv = [];
|
235
|
+
const done = new Set();
|
206
236
|
|
207
237
|
for (let i = 0; i < segments.length; ++i) {
|
208
238
|
const segment = segments[i];
|
209
239
|
|
210
240
|
// Ignores duplicated.
|
211
|
-
if (done
|
241
|
+
if (done.has(segment)) {
|
212
242
|
continue;
|
213
243
|
}
|
214
244
|
|
@@ -217,18 +247,16 @@ class CodePathSegment {
|
|
217
247
|
for (let j = 0; j < segment.allPrevSegments.length; ++j) {
|
218
248
|
const prevSegment = segment.allPrevSegments[j];
|
219
249
|
|
220
|
-
if (!done
|
221
|
-
done
|
222
|
-
retv.push(prevSegment);
|
250
|
+
if (!done.has(prevSegment)) {
|
251
|
+
done.add(prevSegment);
|
223
252
|
}
|
224
253
|
}
|
225
254
|
} else {
|
226
|
-
done
|
227
|
-
retv.push(segment);
|
255
|
+
done.add(segment);
|
228
256
|
}
|
229
257
|
}
|
230
258
|
|
231
|
-
return
|
259
|
+
return [...done];
|
232
260
|
}
|
233
261
|
}
|
234
262
|
|
@@ -32,6 +32,7 @@ const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
|
|
32
32
|
|
33
33
|
/** @typedef {import("../shared/types").Parser} Parser */
|
34
34
|
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
|
35
|
+
/** @typedef {import("../shared/types").Rule} Rule */
|
35
36
|
|
36
37
|
|
37
38
|
/**
|
@@ -446,7 +447,7 @@ class FlatRuleTester {
|
|
446
447
|
/**
|
447
448
|
* Adds a new rule test to execute.
|
448
449
|
* @param {string} ruleName The name of the rule to run.
|
449
|
-
* @param {Function} rule The rule to test.
|
450
|
+
* @param {Function | Rule} rule The rule to test.
|
450
451
|
* @param {{
|
451
452
|
* valid: (ValidTestCase | string)[],
|
452
453
|
* invalid: InvalidTestCase[]
|
@@ -1011,29 +1012,35 @@ class FlatRuleTester {
|
|
1011
1012
|
/*
|
1012
1013
|
* This creates a mocha test suite and pipes all supplied info through
|
1013
1014
|
* one of the templates above.
|
1015
|
+
* The test suites for valid/invalid are created conditionally as
|
1016
|
+
* test runners (eg. vitest) fail for empty test suites.
|
1014
1017
|
*/
|
1015
1018
|
this.constructor.describe(ruleName, () => {
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1019
|
+
if (test.valid.length > 0) {
|
1020
|
+
this.constructor.describe("valid", () => {
|
1021
|
+
test.valid.forEach(valid => {
|
1022
|
+
this.constructor[valid.only ? "itOnly" : "it"](
|
1023
|
+
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
|
1024
|
+
() => {
|
1025
|
+
testValidTemplate(valid);
|
1026
|
+
}
|
1027
|
+
);
|
1028
|
+
});
|
1024
1029
|
});
|
1025
|
-
}
|
1030
|
+
}
|
1026
1031
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1032
|
+
if (test.invalid.length > 0) {
|
1033
|
+
this.constructor.describe("invalid", () => {
|
1034
|
+
test.invalid.forEach(invalid => {
|
1035
|
+
this.constructor[invalid.only ? "itOnly" : "it"](
|
1036
|
+
sanitize(invalid.name || invalid.code),
|
1037
|
+
() => {
|
1038
|
+
testInvalidTemplate(invalid);
|
1039
|
+
}
|
1040
|
+
);
|
1041
|
+
});
|
1035
1042
|
});
|
1036
|
-
}
|
1043
|
+
}
|
1037
1044
|
});
|
1038
1045
|
}
|
1039
1046
|
}
|
@@ -62,6 +62,7 @@ const { SourceCode } = require("../source-code");
|
|
62
62
|
//------------------------------------------------------------------------------
|
63
63
|
|
64
64
|
/** @typedef {import("../shared/types").Parser} Parser */
|
65
|
+
/** @typedef {import("../shared/types").Rule} Rule */
|
65
66
|
|
66
67
|
|
67
68
|
/**
|
@@ -508,17 +509,20 @@ class RuleTester {
|
|
508
509
|
/**
|
509
510
|
* Define a rule for one particular run of tests.
|
510
511
|
* @param {string} name The name of the rule to define.
|
511
|
-
* @param {Function} rule The rule definition.
|
512
|
+
* @param {Function | Rule} rule The rule definition.
|
512
513
|
* @returns {void}
|
513
514
|
*/
|
514
515
|
defineRule(name, rule) {
|
516
|
+
if (typeof rule === "function") {
|
517
|
+
emitLegacyRuleAPIWarning(name);
|
518
|
+
}
|
515
519
|
this.rules[name] = rule;
|
516
520
|
}
|
517
521
|
|
518
522
|
/**
|
519
523
|
* Adds a new rule test to execute.
|
520
524
|
* @param {string} ruleName The name of the rule to run.
|
521
|
-
* @param {Function} rule The rule to test.
|
525
|
+
* @param {Function | Rule} rule The rule to test.
|
522
526
|
* @param {{
|
523
527
|
* valid: (ValidTestCase | string)[],
|
524
528
|
* invalid: InvalidTestCase[]
|
@@ -1021,29 +1025,35 @@ class RuleTester {
|
|
1021
1025
|
/*
|
1022
1026
|
* This creates a mocha test suite and pipes all supplied info through
|
1023
1027
|
* one of the templates above.
|
1028
|
+
* The test suites for valid/invalid are created conditionally as
|
1029
|
+
* test runners (eg. vitest) fail for empty test suites.
|
1024
1030
|
*/
|
1025
1031
|
this.constructor.describe(ruleName, () => {
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1032
|
+
if (test.valid.length > 0) {
|
1033
|
+
this.constructor.describe("valid", () => {
|
1034
|
+
test.valid.forEach(valid => {
|
1035
|
+
this.constructor[valid.only ? "itOnly" : "it"](
|
1036
|
+
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
|
1037
|
+
() => {
|
1038
|
+
testValidTemplate(valid);
|
1039
|
+
}
|
1040
|
+
);
|
1041
|
+
});
|
1034
1042
|
});
|
1035
|
-
}
|
1043
|
+
}
|
1036
1044
|
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
+
if (test.invalid.length > 0) {
|
1046
|
+
this.constructor.describe("invalid", () => {
|
1047
|
+
test.invalid.forEach(invalid => {
|
1048
|
+
this.constructor[invalid.only ? "itOnly" : "it"](
|
1049
|
+
sanitize(invalid.name || invalid.code),
|
1050
|
+
() => {
|
1051
|
+
testInvalidTemplate(invalid);
|
1052
|
+
}
|
1053
|
+
);
|
1054
|
+
});
|
1045
1055
|
});
|
1046
|
-
}
|
1056
|
+
}
|
1047
1057
|
});
|
1048
1058
|
}
|
1049
1059
|
}
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { getStaticValue } = require("@eslint-community/eslint-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -29,6 +35,7 @@ module.exports = {
|
|
29
35
|
},
|
30
36
|
|
31
37
|
create(context) {
|
38
|
+
const { sourceCode } = context;
|
32
39
|
|
33
40
|
/**
|
34
41
|
* report an error.
|
@@ -46,17 +53,17 @@ module.exports = {
|
|
46
53
|
* check the right side of the assignment
|
47
54
|
* @param {ASTNode} update UpdateExpression to check
|
48
55
|
* @param {int} dir expected direction that could either be turned around or invalidated
|
49
|
-
* @returns {int} return dir, the negated dir or zero if
|
56
|
+
* @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear
|
50
57
|
*/
|
51
58
|
function getRightDirection(update, dir) {
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
return
|
59
|
+
const staticValue = getStaticValue(update.right, sourceCode.getScope(update));
|
60
|
+
|
61
|
+
if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) {
|
62
|
+
const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0
|
63
|
+
|
64
|
+
return dir * sign;
|
58
65
|
}
|
59
|
-
return
|
66
|
+
return 0;
|
60
67
|
}
|
61
68
|
|
62
69
|
/**
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { getVariableByName } = require("./utils/ast-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -28,18 +34,24 @@ module.exports = {
|
|
28
34
|
},
|
29
35
|
|
30
36
|
create(context) {
|
37
|
+
const { sourceCode } = context;
|
31
38
|
|
32
39
|
return {
|
33
40
|
|
34
41
|
NewExpression(node) {
|
35
42
|
const wrapperObjects = ["String", "Number", "Boolean"];
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
const { name } = node.callee;
|
44
|
+
|
45
|
+
if (wrapperObjects.includes(name)) {
|
46
|
+
const variable = getVariableByName(sourceCode.getScope(node), name);
|
47
|
+
|
48
|
+
if (variable && variable.identifiers.length === 0) {
|
49
|
+
context.report({
|
50
|
+
node,
|
51
|
+
messageId: "noConstructor",
|
52
|
+
data: { fn: name }
|
53
|
+
});
|
54
|
+
}
|
43
55
|
}
|
44
56
|
}
|
45
57
|
};
|
@@ -10,6 +10,7 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
const { findVariable } = require("@eslint-community/eslint-utils");
|
13
|
+
const astUtils = require("./utils/ast-utils");
|
13
14
|
|
14
15
|
//------------------------------------------------------------------------------
|
15
16
|
// Helpers
|
@@ -59,6 +60,78 @@ function isPromiseExecutor(node, scope) {
|
|
59
60
|
isGlobalReference(parent.callee, getOuterScope(scope));
|
60
61
|
}
|
61
62
|
|
63
|
+
/**
|
64
|
+
* Checks if the given node is a void expression.
|
65
|
+
* @param {ASTNode} node The node to check.
|
66
|
+
* @returns {boolean} - `true` if the node is a void expression
|
67
|
+
*/
|
68
|
+
function expressionIsVoid(node) {
|
69
|
+
return node.type === "UnaryExpression" && node.operator === "void";
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Fixes the linting error by prepending "void " to the given node
|
74
|
+
* @param {Object} sourceCode context given by context.sourceCode
|
75
|
+
* @param {ASTNode} node The node to fix.
|
76
|
+
* @param {Object} fixer The fixer object provided by ESLint.
|
77
|
+
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
78
|
+
*/
|
79
|
+
function voidPrependFixer(sourceCode, node, fixer) {
|
80
|
+
|
81
|
+
const requiresParens =
|
82
|
+
|
83
|
+
// prepending `void ` will fail if the node has a lower precedence than void
|
84
|
+
astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
|
85
|
+
|
86
|
+
// check if there are parentheses around the node to avoid redundant parentheses
|
87
|
+
!astUtils.isParenthesised(sourceCode, node);
|
88
|
+
|
89
|
+
// avoid parentheses issues
|
90
|
+
const returnOrArrowToken = sourceCode.getTokenBefore(
|
91
|
+
node,
|
92
|
+
node.parent.type === "ArrowFunctionExpression"
|
93
|
+
? astUtils.isArrowToken
|
94
|
+
|
95
|
+
// isReturnToken
|
96
|
+
: token => token.type === "Keyword" && token.value === "return"
|
97
|
+
);
|
98
|
+
|
99
|
+
const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
|
100
|
+
|
101
|
+
const prependSpace =
|
102
|
+
|
103
|
+
// is return token, as => allows void to be adjacent
|
104
|
+
returnOrArrowToken.value === "return" &&
|
105
|
+
|
106
|
+
// If two tokens (return and "(") are adjacent
|
107
|
+
returnOrArrowToken.range[1] === firstToken.range[0];
|
108
|
+
|
109
|
+
return [
|
110
|
+
fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
|
111
|
+
fixer.insertTextAfter(node, requiresParens ? ")" : "")
|
112
|
+
];
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Fixes the linting error by `wrapping {}` around the given node's body.
|
117
|
+
* @param {Object} sourceCode context given by context.sourceCode
|
118
|
+
* @param {ASTNode} node The node to fix.
|
119
|
+
* @param {Object} fixer The fixer object provided by ESLint.
|
120
|
+
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
121
|
+
*/
|
122
|
+
function curlyWrapFixer(sourceCode, node, fixer) {
|
123
|
+
|
124
|
+
// https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923
|
125
|
+
const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
|
126
|
+
const firstToken = sourceCode.getTokenAfter(arrowToken);
|
127
|
+
const lastToken = sourceCode.getLastToken(node);
|
128
|
+
|
129
|
+
return [
|
130
|
+
fixer.insertTextBefore(firstToken, "{"),
|
131
|
+
fixer.insertTextAfter(lastToken, "}")
|
132
|
+
];
|
133
|
+
}
|
134
|
+
|
62
135
|
//------------------------------------------------------------------------------
|
63
136
|
// Rule Definition
|
64
137
|
//------------------------------------------------------------------------------
|
@@ -74,10 +147,27 @@ module.exports = {
|
|
74
147
|
url: "https://eslint.org/docs/latest/rules/no-promise-executor-return"
|
75
148
|
},
|
76
149
|
|
77
|
-
|
150
|
+
hasSuggestions: true,
|
151
|
+
|
152
|
+
schema: [{
|
153
|
+
type: "object",
|
154
|
+
properties: {
|
155
|
+
allowVoid: {
|
156
|
+
type: "boolean",
|
157
|
+
default: false
|
158
|
+
}
|
159
|
+
},
|
160
|
+
additionalProperties: false
|
161
|
+
}],
|
78
162
|
|
79
163
|
messages: {
|
80
|
-
returnsValue: "Return values from promise executor functions cannot be read."
|
164
|
+
returnsValue: "Return values from promise executor functions cannot be read.",
|
165
|
+
|
166
|
+
// arrow and function suggestions
|
167
|
+
prependVoid: "Prepend `void` to the expression.",
|
168
|
+
|
169
|
+
// only arrow suggestions
|
170
|
+
wrapBraces: "Wrap the expression in `{}`."
|
81
171
|
}
|
82
172
|
},
|
83
173
|
|
@@ -85,26 +175,52 @@ module.exports = {
|
|
85
175
|
|
86
176
|
let funcInfo = null;
|
87
177
|
const sourceCode = context.sourceCode;
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
* @param {ASTNode} node Node to report.
|
92
|
-
* @returns {void}
|
93
|
-
*/
|
94
|
-
function report(node) {
|
95
|
-
context.report({ node, messageId: "returnsValue" });
|
96
|
-
}
|
178
|
+
const {
|
179
|
+
allowVoid = false
|
180
|
+
} = context.options[0] || {};
|
97
181
|
|
98
182
|
return {
|
99
183
|
|
100
184
|
onCodePathStart(_, node) {
|
101
185
|
funcInfo = {
|
102
186
|
upper: funcInfo,
|
103
|
-
shouldCheck:
|
187
|
+
shouldCheck:
|
188
|
+
functionTypesToCheck.has(node.type) &&
|
189
|
+
isPromiseExecutor(node, sourceCode.getScope(node))
|
104
190
|
};
|
105
191
|
|
106
|
-
if (
|
107
|
-
|
192
|
+
if (// Is a Promise executor
|
193
|
+
funcInfo.shouldCheck &&
|
194
|
+
node.type === "ArrowFunctionExpression" &&
|
195
|
+
node.expression &&
|
196
|
+
|
197
|
+
// Except void
|
198
|
+
!(allowVoid && expressionIsVoid(node.body))
|
199
|
+
) {
|
200
|
+
const suggest = [];
|
201
|
+
|
202
|
+
// prevent useless refactors
|
203
|
+
if (allowVoid) {
|
204
|
+
suggest.push({
|
205
|
+
messageId: "prependVoid",
|
206
|
+
fix(fixer) {
|
207
|
+
return voidPrependFixer(sourceCode, node.body, fixer);
|
208
|
+
}
|
209
|
+
});
|
210
|
+
}
|
211
|
+
|
212
|
+
suggest.push({
|
213
|
+
messageId: "wrapBraces",
|
214
|
+
fix(fixer) {
|
215
|
+
return curlyWrapFixer(sourceCode, node, fixer);
|
216
|
+
}
|
217
|
+
});
|
218
|
+
|
219
|
+
context.report({
|
220
|
+
node: node.body,
|
221
|
+
messageId: "returnsValue",
|
222
|
+
suggest
|
223
|
+
});
|
108
224
|
}
|
109
225
|
},
|
110
226
|
|
@@ -113,9 +229,31 @@ module.exports = {
|
|
113
229
|
},
|
114
230
|
|
115
231
|
ReturnStatement(node) {
|
116
|
-
if (funcInfo.shouldCheck && node.argument) {
|
117
|
-
|
232
|
+
if (!(funcInfo.shouldCheck && node.argument)) {
|
233
|
+
return;
|
118
234
|
}
|
235
|
+
|
236
|
+
// node is `return <expression>`
|
237
|
+
if (!allowVoid) {
|
238
|
+
context.report({ node, messageId: "returnsValue" });
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
|
242
|
+
if (expressionIsVoid(node.argument)) {
|
243
|
+
return;
|
244
|
+
}
|
245
|
+
|
246
|
+
// allowVoid && !expressionIsVoid
|
247
|
+
context.report({
|
248
|
+
node,
|
249
|
+
messageId: "returnsValue",
|
250
|
+
suggest: [{
|
251
|
+
messageId: "prependVoid",
|
252
|
+
fix(fixer) {
|
253
|
+
return voidPrependFixer(sourceCode, node.argument, fixer);
|
254
|
+
}
|
255
|
+
}]
|
256
|
+
});
|
119
257
|
}
|
120
258
|
};
|
121
259
|
}
|
@@ -26,8 +26,8 @@ const {
|
|
26
26
|
|
27
27
|
const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
|
28
28
|
const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
|
29
|
+
const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u;
|
29
30
|
const arrayOrTypedArrayPattern = /Array$/u;
|
30
|
-
const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u;
|
31
31
|
const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
|
32
32
|
const thisTagPattern = /^[\s*]*@this/mu;
|
33
33
|
|
@@ -467,12 +467,12 @@ function isArrayFromMethod(node) {
|
|
467
467
|
}
|
468
468
|
|
469
469
|
/**
|
470
|
-
* Checks whether or not a node is a method which
|
470
|
+
* Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
471
471
|
* @param {ASTNode} node A node to check.
|
472
|
-
* @returns {boolean} Whether or not the node is a method which
|
472
|
+
* @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
473
473
|
*/
|
474
474
|
function isMethodWhichHasThisArg(node) {
|
475
|
-
return isSpecificMemberAccess(node, null,
|
475
|
+
return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern);
|
476
476
|
}
|
477
477
|
|
478
478
|
/**
|
@@ -19,7 +19,7 @@ A config object is using the "extends" key, which is not supported in flat confi
|
|
19
19
|
Instead of "extends", you can include config objects that you'd like to extend from directly in the flat config array.
|
20
20
|
|
21
21
|
Please see the following page for more information:
|
22
|
-
https://eslint.org/docs/latest/use/configure/migration-guide#predefined-configs
|
22
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#predefined-and-shareable-configs
|
23
23
|
`,
|
24
24
|
|
25
25
|
globals: `
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.48.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -62,8 +62,8 @@
|
|
62
62
|
"dependencies": {
|
63
63
|
"@eslint-community/eslint-utils": "^4.2.0",
|
64
64
|
"@eslint-community/regexpp": "^4.6.1",
|
65
|
-
"@eslint/eslintrc": "^2.1.
|
66
|
-
"@eslint/js": "
|
65
|
+
"@eslint/eslintrc": "^2.1.2",
|
66
|
+
"@eslint/js": "8.48.0",
|
67
67
|
"@humanwhocodes/config-array": "^0.11.10",
|
68
68
|
"@humanwhocodes/module-importer": "^1.0.1",
|
69
69
|
"@nodelib/fs.walk": "^1.2.8",
|
@@ -74,7 +74,7 @@
|
|
74
74
|
"doctrine": "^3.0.0",
|
75
75
|
"escape-string-regexp": "^4.0.0",
|
76
76
|
"eslint-scope": "^7.2.2",
|
77
|
-
"eslint-visitor-keys": "^3.4.
|
77
|
+
"eslint-visitor-keys": "^3.4.3",
|
78
78
|
"espree": "^9.6.1",
|
79
79
|
"esquery": "^1.4.2",
|
80
80
|
"esutils": "^2.0.2",
|