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 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://iboysoft.com/"><img src="https://images.opencollective.com/iboysoft-software/7f9d60e/avatar.png" alt="iBoysoft" height="32"></a> <a href="https://www.bairesdev.com/sponsoring-open-source-projects/"><img src="https://images.opencollective.com/bairesdev/48bb773/logo.png" alt="BairesDev" 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>
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 A class of the code path segment.
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 the next segments.
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 the previous segments.
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 that follows given segments.
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 that follows given segments.
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 remove.
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
- * Makes a given segment being used.
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
- * Replaces unused segments with the previous segments of each unused segment.
200
- * @param {CodePathSegment[]} segments An array of segments to replace.
201
- * @returns {CodePathSegment[]} The replaced array.
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 = Object.create(null);
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[segment.id]) {
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[prevSegment.id]) {
221
- done[prevSegment.id] = true;
222
- retv.push(prevSegment);
250
+ if (!done.has(prevSegment)) {
251
+ done.add(prevSegment);
223
252
  }
224
253
  }
225
254
  } else {
226
- done[segment.id] = true;
227
- retv.push(segment);
255
+ done.add(segment);
228
256
  }
229
257
  }
230
258
 
231
- return retv;
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
- this.constructor.describe("valid", () => {
1017
- test.valid.forEach(valid => {
1018
- this.constructor[valid.only ? "itOnly" : "it"](
1019
- sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
1020
- () => {
1021
- testValidTemplate(valid);
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
- this.constructor.describe("invalid", () => {
1028
- test.invalid.forEach(invalid => {
1029
- this.constructor[invalid.only ? "itOnly" : "it"](
1030
- sanitize(invalid.name || invalid.code),
1031
- () => {
1032
- testInvalidTemplate(invalid);
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
- this.constructor.describe("valid", () => {
1027
- test.valid.forEach(valid => {
1028
- this.constructor[valid.only ? "itOnly" : "it"](
1029
- sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
1030
- () => {
1031
- testValidTemplate(valid);
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
- this.constructor.describe("invalid", () => {
1038
- test.invalid.forEach(invalid => {
1039
- this.constructor[invalid.only ? "itOnly" : "it"](
1040
- sanitize(invalid.name || invalid.code),
1041
- () => {
1042
- testInvalidTemplate(invalid);
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 it's not clear for identifiers
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
- if (update.right.type === "UnaryExpression") {
53
- if (update.right.operator === "-") {
54
- return -dir;
55
- }
56
- } else if (update.right.type === "Identifier") {
57
- return 0;
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 dir;
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
- if (wrapperObjects.includes(node.callee.name)) {
38
- context.report({
39
- node,
40
- messageId: "noConstructor",
41
- data: { fn: node.callee.name }
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
- schema: [],
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
- * Reports the given node.
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: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node))
187
+ shouldCheck:
188
+ functionTypesToCheck.has(node.type) &&
189
+ isPromiseExecutor(node, sourceCode.getScope(node))
104
190
  };
105
191
 
106
- if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) {
107
- report(node.body);
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
- report(node);
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 has `thisArg`.
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 has `thisArg`.
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, arrayMethodPattern);
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.46.0",
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.1",
66
- "@eslint/js": "^8.46.0",
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.2",
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",