eslint 9.25.1 → 9.27.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.
Files changed (40) hide show
  1. package/README.md +44 -39
  2. package/bin/eslint.js +15 -0
  3. package/conf/rule-type-list.json +2 -1
  4. package/lib/cli-engine/cli-engine.js +8 -8
  5. package/lib/cli.js +6 -5
  6. package/lib/config/config-loader.js +10 -18
  7. package/lib/config/config.js +328 -5
  8. package/lib/eslint/eslint-helpers.js +3 -1
  9. package/lib/eslint/eslint.js +31 -17
  10. package/lib/eslint/legacy-eslint.js +7 -7
  11. package/lib/languages/js/index.js +4 -3
  12. package/lib/languages/js/source-code/source-code.js +10 -6
  13. package/lib/linter/apply-disable-directives.js +1 -1
  14. package/lib/linter/esquery.js +329 -0
  15. package/lib/linter/file-context.js +11 -0
  16. package/lib/linter/linter.js +81 -89
  17. package/lib/linter/node-event-generator.js +94 -251
  18. package/lib/linter/report-translator.js +2 -1
  19. package/lib/options.js +11 -0
  20. package/lib/rule-tester/rule-tester.js +17 -9
  21. package/lib/rules/eqeqeq.js +31 -8
  22. package/lib/rules/index.js +2 -1
  23. package/lib/rules/max-params.js +32 -7
  24. package/lib/rules/no-array-constructor.js +51 -1
  25. package/lib/rules/no-shadow-restricted-names.js +25 -2
  26. package/lib/rules/no-unassigned-vars.js +72 -0
  27. package/lib/rules/no-unused-expressions.js +7 -1
  28. package/lib/rules/no-useless-escape.js +24 -2
  29. package/lib/rules/prefer-named-capture-group.js +7 -1
  30. package/lib/rules/utils/lazy-loading-rule-map.js +2 -2
  31. package/lib/services/processor-service.js +1 -2
  32. package/lib/services/suppressions-service.js +5 -3
  33. package/lib/shared/flags.js +1 -0
  34. package/lib/shared/serialization.js +29 -6
  35. package/lib/types/index.d.ts +126 -6
  36. package/lib/types/rules.d.ts +33 -2
  37. package/package.json +7 -6
  38. package/lib/config/flat-config-helpers.js +0 -128
  39. package/lib/config/rule-validator.js +0 -199
  40. package/lib/shared/types.js +0 -246
@@ -20,6 +20,8 @@ const { upperCaseFirst } = require("../shared/string-utils");
20
20
  module.exports = {
21
21
  meta: {
22
22
  type: "suggestion",
23
+ dialects: ["typescript", "javascript"],
24
+ language: "javascript",
23
25
 
24
26
  docs: {
25
27
  description:
@@ -46,6 +48,11 @@ module.exports = {
46
48
  type: "integer",
47
49
  minimum: 0,
48
50
  },
51
+ countVoidThis: {
52
+ type: "boolean",
53
+ description:
54
+ "Whether to count a `this` declaration when the type is `void`.",
55
+ },
49
56
  },
50
57
  additionalProperties: false,
51
58
  },
@@ -61,12 +68,16 @@ module.exports = {
61
68
  const sourceCode = context.sourceCode;
62
69
  const option = context.options[0];
63
70
  let numParams = 3;
71
+ let countVoidThis = false;
64
72
 
65
- if (
66
- typeof option === "object" &&
67
- (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
68
- ) {
69
- numParams = option.maximum || option.max;
73
+ if (typeof option === "object") {
74
+ if (
75
+ Object.hasOwn(option, "maximum") ||
76
+ Object.hasOwn(option, "max")
77
+ ) {
78
+ numParams = option.maximum || option.max;
79
+ }
80
+ countVoidThis = option.countVoidThis;
70
81
  }
71
82
  if (typeof option === "number") {
72
83
  numParams = option;
@@ -79,7 +90,19 @@ module.exports = {
79
90
  * @private
80
91
  */
81
92
  function checkFunction(node) {
82
- if (node.params.length > numParams) {
93
+ const hasVoidThisParam =
94
+ node.params.length > 0 &&
95
+ node.params[0].type === "Identifier" &&
96
+ node.params[0].name === "this" &&
97
+ node.params[0].typeAnnotation?.typeAnnotation.type ===
98
+ "TSVoidKeyword";
99
+
100
+ const effectiveParamCount =
101
+ hasVoidThisParam && !countVoidThis
102
+ ? node.params.length - 1
103
+ : node.params.length;
104
+
105
+ if (effectiveParamCount > numParams) {
83
106
  context.report({
84
107
  loc: astUtils.getFunctionHeadLoc(node, sourceCode),
85
108
  node,
@@ -88,7 +111,7 @@ module.exports = {
88
111
  name: upperCaseFirst(
89
112
  astUtils.getFunctionNameWithKind(node),
90
113
  ),
91
- count: node.params.length,
114
+ count: effectiveParamCount,
92
115
  max: numParams,
93
116
  },
94
117
  });
@@ -99,6 +122,8 @@ module.exports = {
99
122
  FunctionDeclaration: checkFunction,
100
123
  ArrowFunctionExpression: checkFunction,
101
124
  FunctionExpression: checkFunction,
125
+ TSDeclareFunction: checkFunction,
126
+ TSFunctionType: checkFunction,
102
127
  };
103
128
  },
104
129
  };
@@ -34,6 +34,8 @@ module.exports = {
34
34
  url: "https://eslint.org/docs/latest/rules/no-array-constructor",
35
35
  },
36
36
 
37
+ fixable: "code",
38
+
37
39
  hasSuggestions: true,
38
40
 
39
41
  schema: [],
@@ -49,6 +51,30 @@ module.exports = {
49
51
  create(context) {
50
52
  const sourceCode = context.sourceCode;
51
53
 
54
+ /**
55
+ * Checks if there are comments in Array constructor expressions.
56
+ * @param {ASTNode} node A CallExpression or NewExpression node.
57
+ * @returns {boolean} True if there are comments, false otherwise.
58
+ */
59
+ function hasCommentsInArrayConstructor(node) {
60
+ const firstToken = sourceCode.getFirstToken(node);
61
+ const lastToken = sourceCode.getLastToken(node);
62
+
63
+ let lastRelevantToken = sourceCode.getLastToken(node.callee);
64
+
65
+ while (
66
+ lastRelevantToken !== lastToken &&
67
+ !isOpeningParenToken(lastRelevantToken)
68
+ ) {
69
+ lastRelevantToken = sourceCode.getTokenAfter(lastRelevantToken);
70
+ }
71
+
72
+ return sourceCode.commentsExistBetween(
73
+ firstToken,
74
+ lastRelevantToken,
75
+ );
76
+ }
77
+
52
78
  /**
53
79
  * Gets the text between the calling parentheses of a CallExpression or NewExpression.
54
80
  * @param {ASTNode} node A CallExpression or NewExpression node.
@@ -107,6 +133,17 @@ module.exports = {
107
133
  let fixText;
108
134
  let messageId;
109
135
 
136
+ const nonSpreadCount = node.arguments.reduce(
137
+ (count, arg) =>
138
+ arg.type !== "SpreadElement" ? count + 1 : count,
139
+ 0,
140
+ );
141
+
142
+ const shouldSuggest =
143
+ node.optional ||
144
+ (node.arguments.length > 0 && nonSpreadCount < 2) ||
145
+ hasCommentsInArrayConstructor(node);
146
+
110
147
  /*
111
148
  * Check if the suggested change should include a preceding semicolon or not.
112
149
  * Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
@@ -127,10 +164,23 @@ module.exports = {
127
164
  context.report({
128
165
  node,
129
166
  messageId: "preferLiteral",
167
+ fix(fixer) {
168
+ if (shouldSuggest) {
169
+ return null;
170
+ }
171
+
172
+ return fixer.replaceText(node, fixText);
173
+ },
130
174
  suggest: [
131
175
  {
132
176
  messageId,
133
- fix: fixer => fixer.replaceText(node, fixText),
177
+ fix(fixer) {
178
+ if (shouldSuggest) {
179
+ return fixer.replaceText(node, fixText);
180
+ }
181
+
182
+ return null;
183
+ },
134
184
  },
135
185
  ],
136
186
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Disallow shadowing of NaN, undefined, and Infinity (ES5 section 15.1.1)
2
+ * @fileoverview Disallow shadowing of globalThis, NaN, undefined, and Infinity (ES2020 section 18.1)
3
3
  * @author Michael Ficarra
4
4
  */
5
5
  "use strict";
@@ -32,13 +32,29 @@ module.exports = {
32
32
  meta: {
33
33
  type: "suggestion",
34
34
 
35
+ defaultOptions: [
36
+ {
37
+ reportGlobalThis: false,
38
+ },
39
+ ],
40
+
35
41
  docs: {
36
42
  description: "Disallow identifiers from shadowing restricted names",
37
43
  recommended: true,
38
44
  url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names",
39
45
  },
40
46
 
41
- schema: [],
47
+ schema: [
48
+ {
49
+ type: "object",
50
+ properties: {
51
+ reportGlobalThis: {
52
+ type: "boolean",
53
+ },
54
+ },
55
+ additionalProperties: false,
56
+ },
57
+ ],
42
58
 
43
59
  messages: {
44
60
  shadowingRestrictedName: "Shadowing of global property '{{name}}'.",
@@ -46,6 +62,8 @@ module.exports = {
46
62
  },
47
63
 
48
64
  create(context) {
65
+ const [{ reportGlobalThis }] = context.options;
66
+
49
67
  const RESTRICTED = new Set([
50
68
  "undefined",
51
69
  "NaN",
@@ -53,6 +71,11 @@ module.exports = {
53
71
  "arguments",
54
72
  "eval",
55
73
  ]);
74
+
75
+ if (reportGlobalThis) {
76
+ RESTRICTED.add("globalThis");
77
+ }
78
+
56
79
  const sourceCode = context.sourceCode;
57
80
 
58
81
  // Track reported nodes to avoid duplicate reports. For example, on class declarations.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @fileoverview Rule to flag variables that are never assigned
3
+ * @author Jacob Bandes-Storch <https://github.com/jtbandes>
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ //------------------------------------------------------------------------------
10
+
11
+ /** @type {import('../types').Rule.RuleModule} */
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+ dialects: ["typescript", "javascript"],
16
+ language: "javascript",
17
+
18
+ docs: {
19
+ description:
20
+ "Disallow `let` or `var` variables that are read but never assigned",
21
+ recommended: false,
22
+ url: "https://eslint.org/docs/latest/rules/no-unassigned-vars",
23
+ },
24
+
25
+ schema: [],
26
+ messages: {
27
+ unassigned:
28
+ "'{{name}}' is always 'undefined' because it's never assigned.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const sourceCode = context.sourceCode;
34
+
35
+ return {
36
+ VariableDeclarator(node) {
37
+ /** @type {import('estree').VariableDeclaration} */
38
+ const declaration = node.parent;
39
+ const shouldCheck =
40
+ !node.init &&
41
+ node.id.type === "Identifier" &&
42
+ declaration.kind !== "const" &&
43
+ !declaration.declare;
44
+ if (!shouldCheck) {
45
+ return;
46
+ }
47
+ const [variable] = sourceCode.getDeclaredVariables(node);
48
+ if (!variable) {
49
+ return;
50
+ }
51
+ let hasRead = false;
52
+ for (const reference of variable.references) {
53
+ if (reference.isWrite()) {
54
+ return;
55
+ }
56
+ if (reference.isRead()) {
57
+ hasRead = true;
58
+ }
59
+ }
60
+ if (!hasRead) {
61
+ // Variables that are never read should be flagged by no-unused-vars instead
62
+ return;
63
+ }
64
+ context.report({
65
+ node,
66
+ messageId: "unassigned",
67
+ data: { name: node.id.name },
68
+ });
69
+ },
70
+ };
71
+ },
72
+ };
@@ -55,6 +55,9 @@ module.exports = {
55
55
  enforceForJSX: {
56
56
  type: "boolean",
57
57
  },
58
+ ignoreDirectives: {
59
+ type: "boolean",
60
+ },
58
61
  },
59
62
  additionalProperties: false,
60
63
  },
@@ -66,6 +69,7 @@ module.exports = {
66
69
  allowTernary: false,
67
70
  allowTaggedTemplates: false,
68
71
  enforceForJSX: false,
72
+ ignoreDirectives: false,
69
73
  },
70
74
  ],
71
75
 
@@ -82,6 +86,7 @@ module.exports = {
82
86
  allowTernary,
83
87
  allowTaggedTemplates,
84
88
  enforceForJSX,
89
+ ignoreDirectives,
85
90
  },
86
91
  ] = context.options;
87
92
 
@@ -211,7 +216,8 @@ module.exports = {
211
216
  ExpressionStatement(node) {
212
217
  if (
213
218
  Checker.isDisallowed(node.expression) &&
214
- !isDirective(node)
219
+ !astUtils.isDirective(node) &&
220
+ !(ignoreDirectives && isDirective(node))
215
221
  ) {
216
222
  context.report({ node, messageId: "unusedExpression" });
217
223
  }
@@ -60,6 +60,12 @@ module.exports = {
60
60
  meta: {
61
61
  type: "suggestion",
62
62
 
63
+ defaultOptions: [
64
+ {
65
+ allowRegexCharacters: [],
66
+ },
67
+ ],
68
+
63
69
  docs: {
64
70
  description: "Disallow unnecessary escape characters",
65
71
  recommended: true,
@@ -78,11 +84,26 @@ module.exports = {
78
84
  "Replace the `\\` with `\\\\` to include the actual backslash character.",
79
85
  },
80
86
 
81
- schema: [],
87
+ schema: [
88
+ {
89
+ type: "object",
90
+ properties: {
91
+ allowRegexCharacters: {
92
+ type: "array",
93
+ items: {
94
+ type: "string",
95
+ },
96
+ uniqueItems: true,
97
+ },
98
+ },
99
+ additionalProperties: false,
100
+ },
101
+ ],
82
102
  },
83
103
 
84
104
  create(context) {
85
105
  const sourceCode = context.sourceCode;
106
+ const [{ allowRegexCharacters }] = context.options;
86
107
  const parser = new RegExpParser();
87
108
 
88
109
  /**
@@ -217,7 +238,8 @@ module.exports = {
217
238
 
218
239
  if (
219
240
  escapedChar !==
220
- String.fromCodePoint(characterNode.value)
241
+ String.fromCodePoint(characterNode.value) ||
242
+ allowRegexCharacters.includes(escapedChar)
221
243
  ) {
222
244
  // It's a valid escape.
223
245
  return;
@@ -17,6 +17,12 @@ const {
17
17
  } = require("@eslint-community/eslint-utils");
18
18
  const regexpp = require("@eslint-community/regexpp");
19
19
 
20
+ //------------------------------------------------------------------------------
21
+ // Typedefs
22
+ //------------------------------------------------------------------------------
23
+
24
+ /** @import { SuggestedEdit } from "@eslint/core"; */
25
+
20
26
  //------------------------------------------------------------------------------
21
27
  // Helpers
22
28
  //------------------------------------------------------------------------------
@@ -29,7 +35,7 @@ const parser = new regexpp.RegExpParser();
29
35
  * @param {string} pattern The regular expression pattern to be checked.
30
36
  * @param {string} rawText Source text of the regexNode.
31
37
  * @param {ASTNode} regexNode AST node which contains the regular expression.
32
- * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
38
+ * @returns {Array<SuggestedEdit>} Fixer suggestions for the regex, if statically determinable.
33
39
  */
34
40
  function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
35
41
  switch (regexNode.type) {
@@ -6,7 +6,7 @@
6
6
 
7
7
  const debug = require("debug")("eslint:rules");
8
8
 
9
- /** @typedef {import("../../shared/types").Rule} Rule */
9
+ /** @typedef {import("../../types").Rule.RuleModule} Rule */
10
10
 
11
11
  /**
12
12
  * The `Map` object that loads each rule when it's accessed.
@@ -19,7 +19,7 @@ const debug = require("debug")("eslint:rules");
19
19
  *
20
20
  * rules.get("semi"); // call `() => require("semi")` here.
21
21
  *
22
- * @extends {Map<string, () => Rule>}
22
+ * @extends {Map<string, Rule>}
23
23
  */
24
24
  class LazyLoadingRuleMap extends Map {
25
25
  /**
@@ -17,10 +17,9 @@ const { VFile } = require("../linter/vfile.js");
17
17
  // Types
18
18
  //-----------------------------------------------------------------------------
19
19
 
20
- /** @typedef {import("../shared/types.js").LintMessage} LintMessage */
20
+ /** @typedef {import("../types").Linter.LintMessage} LintMessage */
21
21
  /** @typedef {import("../linter/vfile.js").VFile} VFile */
22
22
  /** @typedef {import("@eslint/core").Language} Language */
23
- /** @typedef {import("@eslint/core").LanguageOptions} LanguageOptions */
24
23
  /** @typedef {import("eslint").Linter.Processor} Processor */
25
24
 
26
25
  //-----------------------------------------------------------------------------
@@ -12,14 +12,16 @@
12
12
  const fs = require("node:fs");
13
13
  const path = require("node:path");
14
14
  const { calculateStatsPerFile } = require("../eslint/eslint-helpers");
15
+ const stringify = require("json-stable-stringify-without-jsonify");
15
16
 
16
17
  //------------------------------------------------------------------------------
17
18
  // Typedefs
18
19
  //------------------------------------------------------------------------------
19
20
 
20
21
  // For VSCode IntelliSense
21
- /** @typedef {import("../shared/types").LintResult} LintResult */
22
- /** @typedef {import("../shared/types").SuppressedViolations} SuppressedViolations */
22
+ /** @typedef {import("../types").Linter.LintMessage} LintMessage */
23
+ /** @typedef {import("../types").ESLint.LintResult} LintResult */
24
+ /** @typedef {Record<string, Record<string, { count: number; }>>} SuppressedViolations */
23
25
 
24
26
  //-----------------------------------------------------------------------------
25
27
  // Exports
@@ -224,7 +226,7 @@ class SuppressionsService {
224
226
  save(suppressions) {
225
227
  return fs.promises.writeFile(
226
228
  this.filePath,
227
- JSON.stringify(suppressions, null, 2),
229
+ stringify(suppressions, { space: 2 }),
228
230
  );
229
231
  }
230
232
 
@@ -27,6 +27,7 @@
27
27
  */
28
28
  const activeFlags = new Map([
29
29
  ["test_only", "Used only for testing."],
30
+ ["test_only_2", "Used only for testing."],
30
31
  [
31
32
  "unstable_config_lookup_from_file",
32
33
  "Look up `eslint.config.js` from the file being linted.",
@@ -26,21 +26,44 @@ function isSerializablePrimitiveOrPlainObject(val) {
26
26
  * Check if a value is serializable.
27
27
  * Functions or objects like RegExp cannot be serialized by JSON.stringify().
28
28
  * Inspired by: https://stackoverflow.com/questions/30579940/reliable-way-to-check-if-objects-is-serializable-in-javascript
29
- * @param {any} val the value
30
- * @returns {boolean} true if the value is serializable
29
+ * @param {any} val The value
30
+ * @param {Set<Object>} seenObjects Objects already seen in this path from the root object.
31
+ * @returns {boolean} `true` if the value is serializable
31
32
  */
32
- function isSerializable(val) {
33
+ function isSerializable(val, seenObjects = new Set()) {
33
34
  if (!isSerializablePrimitiveOrPlainObject(val)) {
34
35
  return false;
35
36
  }
36
- if (typeof val === "object") {
37
+ if (typeof val === "object" && val !== null) {
38
+ if (seenObjects.has(val)) {
39
+ /*
40
+ * Since this is a depth-first traversal, encountering
41
+ * the same object again means there is a circular reference.
42
+ * Objects with circular references are not serializable.
43
+ */
44
+ return false;
45
+ }
37
46
  for (const property in val) {
38
47
  if (Object.hasOwn(val, property)) {
39
48
  if (!isSerializablePrimitiveOrPlainObject(val[property])) {
40
49
  return false;
41
50
  }
42
- if (typeof val[property] === "object") {
43
- if (!isSerializable(val[property])) {
51
+ if (
52
+ typeof val[property] === "object" &&
53
+ val[property] !== null
54
+ ) {
55
+ if (
56
+ /*
57
+ * We're creating a new Set of seen objects because we want to
58
+ * ensure that `val` doesn't appear again in this path, but it can appear
59
+ * in other paths. This allows for resuing objects in the graph, as long as
60
+ * there are no cycles.
61
+ */
62
+ !isSerializable(
63
+ val[property],
64
+ new Set([...seenObjects, val]),
65
+ )
66
+ ) {
44
67
  return false;
45
68
  }
46
69
  }