eslint 10.4.1 → 10.6.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
@@ -45,7 +45,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
45
45
 
46
46
  ### Prerequisites
47
47
 
48
- To use ESLint, you must have [Node.js](https://nodejs.org/) (`^20.19.0`, `^22.13.0`, or `>=24`) installed and built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
48
+ To use ESLint, you must have [Node.js](https://nodejs.org/) (`^20.19.0`, `^22.13.0`, or `>=24`) installed and built with SSL and ICU support. (If you are using an official Node.js distribution, both SSL and ICU are always built in.)
49
49
 
50
50
  If you use ESLint's TypeScript type definitions, TypeScript 5.3 or later is required.
51
51
 
@@ -320,8 +320,8 @@ lumir
320
320
  </a>
321
321
  </td><td align="center" valign="top" width="11%">
322
322
  <a href="https://github.com/Pixel998">
323
- <img src="https://github.com/Pixel998.png?s=75" width="75" height="75" alt="Pixel998's Avatar"><br />
324
- Pixel998
323
+ <img src="https://github.com/Pixel998.png?s=75" width="75" height="75" alt="Pixel's Avatar"><br />
324
+ Pixel
325
325
  </a>
326
326
  </td></tr></tbody></table>
327
327
 
@@ -358,9 +358,9 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
358
358
 
359
359
  <h3>Platinum Sponsors</h3>
360
360
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
361
- <p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a></p><h3>Silver Sponsors</h3>
361
+ <p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a> <a href="https://www.coderabbit.ai/?utm_source=cr_org&utm_medium=github"><img src="https://avatars.githubusercontent.com/u/132028505" alt="CodeRabbit" height="96"></a></p><h3>Silver Sponsors</h3>
362
362
  <p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
363
- <p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://citadel.co.jp"><img src="https://avatars.githubusercontent.com/u/75781367" alt="Citadel AI" height="32"></a></p>
363
+ <p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://citadel.co.jp"><img src="https://avatars.githubusercontent.com/u/75781367" alt="Citadel AI" height="32"></a></p>
364
364
  <h3>Technology Sponsors</h3>
365
365
  Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
366
366
  <p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
@@ -191,8 +191,8 @@ function pluralize(word, count) {
191
191
 
192
192
  /**
193
193
  * Renders text along the template of x problems (x errors, x warnings)
194
- * @param {string} totalErrors Total errors
195
- * @param {string} totalWarnings Total warnings
194
+ * @param {number} totalErrors Total errors
195
+ * @param {number} totalWarnings Total warnings
196
196
  * @returns {string} The formatted string, pluralized where necessary
197
197
  */
198
198
  function renderSummary(totalErrors, totalWarnings) {
@@ -207,8 +207,8 @@ function renderSummary(totalErrors, totalWarnings) {
207
207
 
208
208
  /**
209
209
  * Get the color based on whether there are errors/warnings...
210
- * @param {string} totalErrors Total errors
211
- * @param {string} totalWarnings Total warnings
210
+ * @param {number} totalErrors Total errors
211
+ * @param {number} totalWarnings Total warnings
212
212
  * @returns {number} The color code (0 = green, 1 = yellow, 2 = red)
213
213
  */
214
214
  function renderColor(totalErrors, totalWarnings) {
@@ -10,6 +10,7 @@
10
10
  //------------------------------------------------------------------------------
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
13
14
 
14
15
  //------------------------------------------------------------------------------
15
16
  // Helpers
@@ -30,21 +31,6 @@ function isTargetMethod(node) {
30
31
  return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
31
32
  }
32
33
 
33
- /**
34
- * Checks all segments in a set and returns true if any are reachable.
35
- * @param {Set<CodePathSegment>} segments The segments to check.
36
- * @returns {boolean} True if any segment is reachable; false otherwise.
37
- */
38
- function isAnySegmentReachable(segments) {
39
- for (const segment of segments) {
40
- if (segment.reachable) {
41
- return true;
42
- }
43
- }
44
-
45
- return false;
46
- }
47
-
48
34
  /**
49
35
  * Returns a human-legible description of an array method
50
36
  * @param {string} arrayMethodName A method name to fully qualify
@@ -10,26 +10,12 @@
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
12
  const { upperCaseFirst } = require("../shared/string-utils");
13
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
13
14
 
14
15
  //------------------------------------------------------------------------------
15
16
  // Helpers
16
17
  //------------------------------------------------------------------------------
17
18
 
18
- /**
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
- */
23
- function areAllSegmentsUnreachable(segments) {
24
- for (const segment of segments) {
25
- if (segment.reachable) {
26
- return false;
27
- }
28
- }
29
-
30
- return true;
31
- }
32
-
33
19
  /**
34
20
  * Checks whether a given node is a `constructor` method in an ES6 class
35
21
  * @param {ASTNode} node A node to check
@@ -100,7 +86,7 @@ module.exports = {
100
86
  */
101
87
  if (
102
88
  !funcInfo.hasReturnValue ||
103
- areAllSegmentsUnreachable(funcInfo.currentSegments) ||
89
+ !isAnySegmentReachable(funcInfo.currentSegments) ||
104
90
  astUtils.isES5Constructor(node) ||
105
91
  isClassConstructor(node)
106
92
  ) {
@@ -10,6 +10,7 @@
10
10
  //------------------------------------------------------------------------------
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
13
14
 
14
15
  //------------------------------------------------------------------------------
15
16
  // Helpers
@@ -17,21 +18,6 @@ const astUtils = require("./utils/ast-utils");
17
18
 
18
19
  const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
19
20
 
20
- /**
21
- * Checks all segments in a set and returns true if any are reachable.
22
- * @param {Set<CodePathSegment>} segments The segments to check.
23
- * @returns {boolean} True if any segment is reachable; false otherwise.
24
- */
25
- function isAnySegmentReachable(segments) {
26
- for (const segment of segments) {
27
- if (segment.reachable) {
28
- return true;
29
- }
30
- }
31
-
32
- return false;
33
- }
34
-
35
21
  //------------------------------------------------------------------------------
36
22
  // Rule Definition
37
23
  //------------------------------------------------------------------------------
@@ -71,6 +71,10 @@ module.exports = {
71
71
  if (classCount > max) {
72
72
  context.report({
73
73
  node,
74
+ loc: {
75
+ start: node.body[0].loc.start,
76
+ end: node.body.at(-1).loc.end,
77
+ },
74
78
  messageId: "maximumExceeded",
75
79
  data: {
76
80
  classCount,
@@ -54,6 +54,8 @@ module.exports = {
54
54
  },
55
55
 
56
56
  create(context) {
57
+ const sourceCode = context.sourceCode;
58
+
57
59
  //--------------------------------------------------------------------------
58
60
  // Helpers
59
61
  //--------------------------------------------------------------------------
@@ -102,6 +104,7 @@ module.exports = {
102
104
  if (len > maxDepth) {
103
105
  context.report({
104
106
  node,
107
+ loc: sourceCode.getFirstToken(node).loc,
105
108
  messageId: "tooDeeply",
106
109
  data: { depth: len, maxDepth },
107
110
  });
@@ -117,6 +120,18 @@ module.exports = {
117
120
  functionStack[functionStack.length - 1]--;
118
121
  }
119
122
 
123
+ /**
124
+ * Checks whether a node is an else-if statement.
125
+ * @param {ASTNode} node node to evaluate
126
+ * @returns {boolean} Whether the node is an else-if statement
127
+ */
128
+ function isElseIf(node) {
129
+ return (
130
+ node.parent.type === "IfStatement" &&
131
+ node.parent.alternate === node
132
+ );
133
+ }
134
+
120
135
  //--------------------------------------------------------------------------
121
136
  // Public API
122
137
  //--------------------------------------------------------------------------
@@ -129,7 +144,7 @@ module.exports = {
129
144
  StaticBlock: startFunction,
130
145
 
131
146
  IfStatement(node) {
132
- if (node.parent.type !== "IfStatement") {
147
+ if (!isElseIf(node)) {
133
148
  pushBlock(node);
134
149
  }
135
150
  },
@@ -142,7 +157,11 @@ module.exports = {
142
157
  ForInStatement: pushBlock,
143
158
  ForOfStatement: pushBlock,
144
159
 
145
- "IfStatement:exit": popBlock,
160
+ "IfStatement:exit"(node) {
161
+ if (!isElseIf(node)) {
162
+ popBlock();
163
+ }
164
+ },
146
165
  "SwitchStatement:exit": popBlock,
147
166
  "TryStatement:exit": popBlock,
148
167
  "DoWhileStatement:exit": popBlock,
@@ -222,6 +222,7 @@ module.exports = {
222
222
 
223
223
  context.report({
224
224
  node,
225
+ loc: astUtils.getFunctionHeadLoc(funcNode, sourceCode),
225
226
  messageId: "exceed",
226
227
  data: { name, lineCount, maxLines },
227
228
  });
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -53,6 +59,8 @@ module.exports = {
53
59
  },
54
60
 
55
61
  create(context) {
62
+ const sourceCode = context.sourceCode;
63
+
56
64
  //--------------------------------------------------------------------------
57
65
  // Constants
58
66
  //--------------------------------------------------------------------------
@@ -83,24 +91,34 @@ module.exports = {
83
91
  function checkFunction(node) {
84
92
  const parent = node.parent;
85
93
 
86
- if (parent.type === "CallExpression") {
87
- callbackStack.push(node);
94
+ if (parent.type !== "CallExpression" || parent.callee === node) {
95
+ return;
88
96
  }
89
97
 
98
+ callbackStack.push(node);
99
+
90
100
  if (callbackStack.length > THRESHOLD) {
91
101
  const opts = { num: callbackStack.length, max: THRESHOLD };
92
102
 
93
- context.report({ node, messageId: "exceed", data: opts });
103
+ context.report({
104
+ node,
105
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
106
+ messageId: "exceed",
107
+ data: opts,
108
+ });
94
109
  }
95
110
  }
96
111
 
97
112
  /**
98
113
  * Pops the call stack.
114
+ * @param {ASTNode} node The node to check.
99
115
  * @returns {void}
100
116
  * @private
101
117
  */
102
- function popStack() {
103
- callbackStack.pop();
118
+ function popStack(node) {
119
+ if (callbackStack.at(-1) === node) {
120
+ callbackStack.pop();
121
+ }
104
122
  }
105
123
 
106
124
  //--------------------------------------------------------------------------
@@ -108,6 +108,7 @@ module.exports = {
108
108
 
109
109
  context.report({
110
110
  node,
111
+ loc: astUtils.getFunctionHeadLoc(node, context.sourceCode),
111
112
  messageId: "exceed",
112
113
  data: { name, count, max },
113
114
  });
@@ -28,6 +28,8 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set([
28
28
  ">>>",
29
29
  ]);
30
30
 
31
+ const RELATIONAL_OPERATORS = new Set(["<", "<=", ">", ">="]);
32
+
31
33
  //------------------------------------------------------------------------------
32
34
  // Helpers
33
35
  //------------------------------------------------------------------------------
@@ -87,7 +89,9 @@ function hasConstantNullishness(scope, node, nonNullish) {
87
89
  return (
88
90
  (functionName === "Boolean" ||
89
91
  functionName === "String" ||
90
- functionName === "Number") &&
92
+ functionName === "Number" ||
93
+ functionName === "Symbol" ||
94
+ functionName === "BigInt") &&
91
95
  isReferenceToGlobalVariable(scope, node.callee)
92
96
  );
93
97
  }
@@ -372,7 +376,10 @@ function hasConstantStrictBooleanComparison(scope, node) {
372
376
  const functionName = node.callee.name;
373
377
 
374
378
  if (
375
- (functionName === "String" || functionName === "Number") &&
379
+ (functionName === "String" ||
380
+ functionName === "Number" ||
381
+ functionName === "BigInt" ||
382
+ functionName === "Symbol") &&
376
383
  isReferenceToGlobalVariable(scope, node.callee)
377
384
  ) {
378
385
  return true;
@@ -485,6 +492,33 @@ function findBinaryExpressionConstantOperand(scope, a, b, operator) {
485
492
  return null;
486
493
  }
487
494
 
495
+ /**
496
+ * Checks if a node is a statically knowable literal.
497
+ * @param {Scope} scope The scope in which the node was found.
498
+ * @param {ASTNode} node The node to test.
499
+ * @returns {boolean} `true` if the node is a literal.
500
+ */
501
+ function isStaticLiteral(scope, node) {
502
+ switch (node.type) {
503
+ case "Literal":
504
+ return true;
505
+ case "UnaryExpression":
506
+ return (
507
+ ["-", "+", "~"].includes(node.operator) &&
508
+ node.argument.type === "Literal"
509
+ );
510
+ case "Identifier":
511
+ return (
512
+ node.name === "undefined" &&
513
+ isReferenceToGlobalVariable(scope, node)
514
+ );
515
+ case "TemplateLiteral":
516
+ return node.expressions.length === 0;
517
+ default:
518
+ return false;
519
+ }
520
+ }
521
+
488
522
  //------------------------------------------------------------------------------
489
523
  // Rule Definition
490
524
  //------------------------------------------------------------------------------
@@ -499,7 +533,22 @@ module.exports = {
499
533
  recommended: true,
500
534
  url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression",
501
535
  },
502
- schema: [],
536
+ defaultOptions: [
537
+ {
538
+ checkRelationalComparisons: false,
539
+ },
540
+ ],
541
+ schema: [
542
+ {
543
+ type: "object",
544
+ properties: {
545
+ checkRelationalComparisons: {
546
+ type: "boolean",
547
+ },
548
+ },
549
+ additionalProperties: false,
550
+ },
551
+ ],
503
552
  messages: {
504
553
  constantBinaryOperand:
505
554
  "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.",
@@ -509,11 +558,14 @@ module.exports = {
509
558
  "Unexpected comparison to newly constructed object. These two values can never be equal.",
510
559
  bothAlwaysNew:
511
560
  "Unexpected comparison of two newly constructed objects. These two values can never be equal.",
561
+ constantRelationalComparison:
562
+ "Unexpected constant relational comparison. Both sides of the `{{operator}}` are literal values.",
512
563
  },
513
564
  },
514
565
 
515
566
  create(context) {
516
567
  const sourceCode = context.sourceCode;
568
+ const { checkRelationalComparisons } = context.options[0];
517
569
 
518
570
  return {
519
571
  LogicalExpression(node) {
@@ -587,6 +639,20 @@ module.exports = {
587
639
  messageId: "bothAlwaysNew",
588
640
  });
589
641
  }
642
+ } else if (
643
+ checkRelationalComparisons &&
644
+ RELATIONAL_OPERATORS.has(operator)
645
+ ) {
646
+ if (
647
+ isStaticLiteral(scope, left) &&
648
+ isStaticLiteral(scope, right)
649
+ ) {
650
+ context.report({
651
+ node,
652
+ messageId: "constantRelationalComparison",
653
+ data: { operator },
654
+ });
655
+ }
590
656
  }
591
657
  },
592
658
 
@@ -91,7 +91,8 @@ module.exports = {
91
91
  (node.type === "CallExpression" ||
92
92
  node.type === "NewExpression") &&
93
93
  node.callee.type === "Identifier" &&
94
- node.callee.name === "Boolean"
94
+ node.callee.name === "Boolean" &&
95
+ sourceCode.isGlobalReference(node.callee)
95
96
  );
96
97
  }
97
98
 
@@ -323,7 +324,8 @@ module.exports = {
323
324
  CallExpression(node) {
324
325
  if (
325
326
  node.callee.type !== "Identifier" ||
326
- node.callee.name !== "Boolean"
327
+ node.callee.name !== "Boolean" ||
328
+ !sourceCode.isGlobalReference(node.callee)
327
329
  ) {
328
330
  return;
329
331
  }
@@ -9,6 +9,7 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const { directivesPattern } = require("../shared/directives");
12
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
12
13
 
13
14
  //------------------------------------------------------------------------------
14
15
  // Helpers
@@ -16,21 +17,6 @@ const { directivesPattern } = require("../shared/directives");
16
17
 
17
18
  const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
18
19
 
19
- /**
20
- * Checks all segments in a set and returns true if any are reachable.
21
- * @param {Set<CodePathSegment>} segments The segments to check.
22
- * @returns {boolean} True if any segment is reachable; false otherwise.
23
- */
24
- function isAnySegmentReachable(segments) {
25
- for (const segment of segments) {
26
- if (segment.reachable) {
27
- return true;
28
- }
29
- }
30
-
31
- return false;
32
- }
33
-
34
20
  /**
35
21
  * Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
36
22
  * @param {string} comment The comment string to check.
@@ -199,10 +199,11 @@ module.exports = {
199
199
  });
200
200
  }
201
201
 
202
- // Do not suggest wrapping an unnamed FunctionExpression in braces as that would be invalid syntax.
202
+ // Do not suggest wrapping an unnamed function or class expression in braces as that would be invalid syntax.
203
203
  if (
204
204
  !(
205
- node.body.type === "FunctionExpression" &&
205
+ (node.body.type === "FunctionExpression" ||
206
+ node.body.type === "ClassExpression") &&
206
207
  !node.body.id
207
208
  )
208
209
  ) {
@@ -31,12 +31,17 @@ module.exports = {
31
31
  },
32
32
 
33
33
  create(context) {
34
+ const sourceCode = context.sourceCode;
35
+
34
36
  return {
35
37
  ThrowStatement(node) {
36
38
  if (!astUtils.couldBeError(node.argument)) {
37
39
  context.report({ node, messageId: "object" });
38
40
  } else if (node.argument.type === "Identifier") {
39
- if (node.argument.name === "undefined") {
41
+ if (
42
+ node.argument.name === "undefined" &&
43
+ sourceCode.isGlobalReference(node.argument)
44
+ ) {
40
45
  context.report({ node, messageId: "undef" });
41
46
  }
42
47
  }
@@ -5,6 +5,8 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
9
+
8
10
  //------------------------------------------------------------------------------
9
11
  // Helpers
10
12
  //------------------------------------------------------------------------------
@@ -17,21 +19,6 @@ const allLoopTypes = [
17
19
  "ForOfStatement",
18
20
  ];
19
21
 
20
- /**
21
- * Checks all segments in a set and returns true if any are reachable.
22
- * @param {Set<CodePathSegment>} segments The segments to check.
23
- * @returns {boolean} True if any segment is reachable; false otherwise.
24
- */
25
- function isAnySegmentReachable(segments) {
26
- for (const segment of segments) {
27
- if (segment.reachable) {
28
- return true;
29
- }
30
- }
31
-
32
- return false;
33
- }
34
-
35
22
  /**
36
23
  * Determines whether the given node is the first node in the code path to which a loop statement
37
24
  * 'loops' for the next iteration.
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Helpers
9
15
  //------------------------------------------------------------------------------
@@ -23,21 +29,6 @@ function isInitialized(node) {
23
29
  return Boolean(node.init);
24
30
  }
25
31
 
26
- /**
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
- */
31
- function areAllSegmentsUnreachable(segments) {
32
- for (const segment of segments) {
33
- if (segment.reachable) {
34
- return false;
35
- }
36
- }
37
-
38
- return true;
39
- }
40
-
41
32
  /**
42
33
  * The class to distinguish consecutive unreachable statements.
43
34
  */
@@ -154,7 +145,7 @@ module.exports = {
154
145
  if (
155
146
  node &&
156
147
  (node.type === "PropertyDefinition" ||
157
- areAllSegmentsUnreachable(currentCodePathSegments))
148
+ !isAnySegmentReachable(currentCodePathSegments))
158
149
  ) {
159
150
  // Store this statement to distinguish consecutive statements.
160
151
  if (range.isEmpty) {
@@ -10,6 +10,7 @@
10
10
 
11
11
  const astUtils = require("./utils/ast-utils"),
12
12
  FixTracker = require("./utils/fix-tracker");
13
+ const { isAnySegmentReachable } = require("./utils/code-path-utils");
13
14
 
14
15
  //------------------------------------------------------------------------------
15
16
  // Helpers
@@ -60,21 +61,6 @@ function isInFinally(node) {
60
61
  return false;
61
62
  }
62
63
 
63
- /**
64
- * Checks all segments in a set and returns true if any are reachable.
65
- * @param {Set<CodePathSegment>} segments The segments to check.
66
- * @returns {boolean} True if any segment is reachable; false otherwise.
67
- */
68
- function isAnySegmentReachable(segments) {
69
- for (const segment of segments) {
70
- if (segment.reachable) {
71
- return true;
72
- }
73
- }
74
-
75
- return false;
76
- }
77
-
78
64
  //------------------------------------------------------------------------------
79
65
  // Rule Definition
80
66
  //------------------------------------------------------------------------------
@@ -28,9 +28,15 @@ module.exports = {
28
28
  },
29
29
 
30
30
  create(context) {
31
+ const sourceCode = context.sourceCode;
32
+
31
33
  return {
32
34
  WithStatement(node) {
33
- context.report({ node, messageId: "unexpectedWith" });
35
+ context.report({
36
+ node,
37
+ loc: sourceCode.getFirstToken(node).loc,
38
+ messageId: "unexpectedWith",
39
+ });
34
40
  },
35
41
  };
36
42
  },
@@ -21,6 +21,11 @@ const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({
21
21
  operator: "**",
22
22
  });
23
23
 
24
+ /*
25
+ * Characters that can cause continuation without preceding semi
26
+ */
27
+ const continuationChars = new Set(["(", "[", "/", "`"]);
28
+
24
29
  /**
25
30
  * Determines whether the given node needs parens if used as the base in an exponentiation binary expression.
26
31
  * @param {ASTNode} base The node to check.
@@ -149,11 +154,36 @@ module.exports = {
149
154
  shouldParenthesizeBase = doesBaseNeedParens(base),
150
155
  shouldParenthesizeExponent =
151
156
  doesExponentNeedParens(exponent),
152
- shouldParenthesizeAll =
153
- doesExponentiationExpressionNeedParens(
154
- node,
155
- sourceCode,
156
- );
157
+ isStartOfExpressionStatement =
158
+ astUtils.isStartOfExpressionStatement(node);
159
+
160
+ let shouldParenthesizeAll =
161
+ doesExponentiationExpressionNeedParens(
162
+ node,
163
+ sourceCode,
164
+ );
165
+
166
+ /*
167
+ * `function`, `class`, or `{` token at the start of an expression statement
168
+ * would cause incorrect parsing.
169
+ * https://github.com/eslint/eslint/issues/20987
170
+ */
171
+ if (
172
+ !shouldParenthesizeAll &&
173
+ !shouldParenthesizeBase &&
174
+ isStartOfExpressionStatement
175
+ ) {
176
+ const firstTokenOfBase = sourceCode.getFirstToken(base);
177
+
178
+ if (
179
+ astUtils.isOpeningBraceToken(firstTokenOfBase) ||
180
+ (firstTokenOfBase.type === "Keyword" &&
181
+ (firstTokenOfBase.value === "function" ||
182
+ firstTokenOfBase.value === "class"))
183
+ ) {
184
+ shouldParenthesizeAll = true;
185
+ }
186
+ }
157
187
 
158
188
  let prefix = "",
159
189
  suffix = "";
@@ -206,6 +236,15 @@ module.exports = {
206
236
  shouldParenthesizeAll,
207
237
  );
208
238
 
239
+ if (
240
+ !prefix &&
241
+ isStartOfExpressionStatement &&
242
+ continuationChars.has(replacement[0]) &&
243
+ astUtils.needsPrecedingSemicolon(sourceCode, node)
244
+ ) {
245
+ prefix = ";";
246
+ }
247
+
209
248
  return fixer.replaceText(
210
249
  node,
211
250
  `${prefix}${replacement}${suffix}`,
@@ -63,11 +63,15 @@ module.exports = {
63
63
  if (!callExpression.arguments.length && allowEmptyReject) {
64
64
  return;
65
65
  }
66
+
67
+ const rejectionReason = callExpression.arguments[0];
68
+
66
69
  if (
67
70
  !callExpression.arguments.length ||
68
- !astUtils.couldBeError(callExpression.arguments[0]) ||
69
- (callExpression.arguments[0].type === "Identifier" &&
70
- callExpression.arguments[0].name === "undefined")
71
+ !astUtils.couldBeError(rejectionReason) ||
72
+ (rejectionReason.type === "Identifier" &&
73
+ rejectionReason.name === "undefined" &&
74
+ sourceCode.isGlobalReference(rejectionReason))
71
75
  ) {
72
76
  context.report({
73
77
  node: callExpression,
@@ -82,10 +86,15 @@ module.exports = {
82
86
  * @returns {boolean} `true` if the call is a Promise.reject() call
83
87
  */
84
88
  function isPromiseRejectCall(node) {
85
- return astUtils.isSpecificMemberAccess(
86
- node.callee,
87
- "Promise",
88
- "reject",
89
+ return (
90
+ astUtils.isSpecificMemberAccess(
91
+ node.callee,
92
+ "Promise",
93
+ "reject",
94
+ ) &&
95
+ sourceCode.isGlobalReference(
96
+ astUtils.skipChainExpression(node.callee).object,
97
+ )
89
98
  );
90
99
  }
91
100
 
@@ -110,6 +119,7 @@ module.exports = {
110
119
  if (
111
120
  node.callee.type === "Identifier" &&
112
121
  node.callee.name === "Promise" &&
122
+ sourceCode.isGlobalReference(node.callee) &&
113
123
  node.arguments.length &&
114
124
  astUtils.isFunction(node.arguments[0]) &&
115
125
  node.arguments[0].params.length > 1 &&
@@ -57,12 +57,15 @@ function isParseIntMethod(node) {
57
57
  * - A literal except integers between 2 and 36.
58
58
  * - undefined.
59
59
  * @param {ASTNode} radix A node of radix to check.
60
+ * @param {SourceCode} sourceCode The source code object.
60
61
  * @returns {boolean} `true` if the node is valid.
61
62
  */
62
- function isValidRadix(radix) {
63
+ function isValidRadix(radix, sourceCode) {
63
64
  return !(
64
65
  (radix.type === "Literal" && !validRadixValues.has(radix.value)) ||
65
- (radix.type === "Identifier" && radix.name === "undefined")
66
+ (radix.type === "Identifier" &&
67
+ radix.name === "undefined" &&
68
+ sourceCode.isGlobalReference(radix))
66
69
  );
67
70
  }
68
71
 
@@ -148,7 +151,7 @@ module.exports = {
148
151
  break;
149
152
 
150
153
  default:
151
- if (!isValidRadix(args[1])) {
154
+ if (!isValidRadix(args[1], sourceCode)) {
152
155
  context.report({
153
156
  node,
154
157
  messageId: "invalidRadix",
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Code path related utilities.
3
+ */
4
+
5
+ "use strict";
6
+
7
+ /**
8
+ * Checks all segments in a set and returns true if any are reachable.
9
+ * @param {Set<CodePathSegment>} segments The segments to check.
10
+ * @returns {boolean} `true` if any segment is reachable; `false` otherwise.
11
+ */
12
+ function isAnySegmentReachable(segments) {
13
+ for (const segment of segments) {
14
+ if (segment.reachable) {
15
+ return true;
16
+ }
17
+ }
18
+
19
+ return false;
20
+ }
21
+
22
+ module.exports = { isAnySegmentReachable };
@@ -2228,7 +2228,16 @@ export interface ESLintRules extends Linter.RulesRecord {
2228
2228
  * @since 8.14.0
2229
2229
  * @see https://eslint.org/docs/latest/rules/no-constant-binary-expression
2230
2230
  */
2231
- "no-constant-binary-expression": Linter.RuleEntry<[]>;
2231
+ "no-constant-binary-expression": Linter.RuleEntry<
2232
+ [
2233
+ Partial<{
2234
+ /**
2235
+ * @default false
2236
+ */
2237
+ checkRelationalComparisons: boolean;
2238
+ }>,
2239
+ ]
2240
+ >;
2232
2241
 
2233
2242
  /**
2234
2243
  * Rule to disallow constant expressions in conditions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "10.4.1",
3
+ "version": "10.6.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "type": "commonjs",
@@ -78,8 +78,15 @@
78
78
  "test:fuzz": "node Makefile.js fuzz",
79
79
  "test:performance": "node Makefile.js perf",
80
80
  "test:pnpm": "cd tests/pnpm && node check.js && pnpm install && pnpm exec tsc",
81
- "test:types": "tsc -p tests/lib/types/tsconfig.json"
81
+ "test:types": "tsc -p tests/lib/types/tsconfig.json && npm run test:types --workspaces --if-present",
82
+ "test:types:5.3": "npx -p typescript@5.3 -y -- tsc -p tsconfig.types-legacy.json",
83
+ "test:types:5.x": "npx -p typescript@5.x -y -- tsc -p tsconfig.types.json",
84
+ "test:types:7.x": "npx -p @typescript/native-preview@latest -y -- tsgo -p tsconfig.types.json",
85
+ "test:types:all": "npm run test:types && npm run test:types:5.3 && npm run test:types:5.x && npm run test:types:7.x"
82
86
  },
87
+ "workspaces": [
88
+ "packages/*"
89
+ ],
83
90
  "gitHooks": {
84
91
  "pre-commit": "lint-staged"
85
92
  },
@@ -151,12 +158,12 @@
151
158
  "optionator": "^0.9.3"
152
159
  },
153
160
  "devDependencies": {
154
- "@arethetypeswrong/cli": "^0.18.0",
161
+ "@arethetypeswrong/cli": "^0.18.3",
155
162
  "@babel/core": "^7.4.3",
156
163
  "@babel/preset-env": "^7.4.3",
157
164
  "@cypress/webpack-preprocessor": "^6.0.2",
158
165
  "@eslint/eslintrc": "^3.3.5",
159
- "@eslint/json": "^1.2.0",
166
+ "@eslint/json": "^2.0.0",
160
167
  "@types/esquery": "^1.5.4",
161
168
  "@types/node": "^22.13.14",
162
169
  "@typescript-eslint/parser": "^8.58.2",
@@ -185,8 +192,8 @@
185
192
  "jiti": "^2.6.1",
186
193
  "knip": "^6.13.1",
187
194
  "lint-staged": "^11.0.0",
188
- "markdown-it": "^12.2.0",
189
- "markdown-it-container": "^3.0.0",
195
+ "markdown-it": "^14.2.0",
196
+ "markdown-it-container": "^4.0.0",
190
197
  "markdownlint-cli2": "^0.22.0",
191
198
  "marked": "^4.0.8",
192
199
  "metascraper": "^5.25.7",
@@ -198,7 +205,7 @@
198
205
  "mocha": "^11.7.1",
199
206
  "node-polyfill-webpack-plugin": "^1.0.3",
200
207
  "npm-license": "^0.3.3",
201
- "prettier": "3.8.3",
208
+ "prettier": "3.8.4",
202
209
  "progress": "^2.0.3",
203
210
  "proxyquire": "^2.0.1",
204
211
  "regenerator-runtime": "^0.14.0",
@@ -228,10 +235,5 @@
228
235
  "license": "MIT",
229
236
  "engines": {
230
237
  "node": "^20.19.0 || ^22.13.0 || >=24"
231
- },
232
- "overrides": {
233
- "@arethetypeswrong/core": {
234
- "fflate": "0.8.2"
235
- }
236
238
  }
237
239
  }