eslint 8.56.0 → 9.0.0-alpha.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 (50) hide show
  1. package/README.md +2 -2
  2. package/conf/rule-type-list.json +3 -1
  3. package/lib/api.js +1 -1
  4. package/lib/cli-engine/cli-engine.js +13 -2
  5. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  6. package/lib/cli.js +32 -9
  7. package/lib/config/default-config.js +3 -0
  8. package/lib/config/flat-config-array.js +0 -20
  9. package/lib/config/flat-config-helpers.js +41 -20
  10. package/lib/config/flat-config-schema.js +35 -25
  11. package/lib/config/rule-validator.js +27 -4
  12. package/lib/eslint/eslint-helpers.js +32 -12
  13. package/lib/eslint/eslint.js +856 -373
  14. package/lib/eslint/index.js +2 -2
  15. package/lib/eslint/legacy-eslint.js +722 -0
  16. package/lib/linter/apply-disable-directives.js +33 -5
  17. package/lib/linter/config-comment-parser.js +1 -1
  18. package/lib/linter/linter.js +91 -96
  19. package/lib/linter/rules.js +6 -15
  20. package/lib/options.js +9 -1
  21. package/lib/rule-tester/rule-tester.js +240 -272
  22. package/lib/rules/index.js +0 -2
  23. package/lib/rules/no-constant-binary-expression.js +1 -1
  24. package/lib/rules/no-constructor-return.js +1 -1
  25. package/lib/rules/no-empty-static-block.js +1 -1
  26. package/lib/rules/no-extra-semi.js +1 -1
  27. package/lib/rules/no-implicit-coercion.js +17 -1
  28. package/lib/rules/no-inner-declarations.js +1 -1
  29. package/lib/rules/no-invalid-regexp.js +1 -1
  30. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  31. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  32. package/lib/rules/no-new-symbol.js +8 -1
  33. package/lib/rules/no-sequences.js +1 -0
  34. package/lib/rules/no-unused-private-class-members.js +1 -1
  35. package/lib/shared/config-validator.js +44 -11
  36. package/lib/shared/types.js +1 -1
  37. package/lib/source-code/source-code.js +1 -79
  38. package/lib/unsupported-api.js +3 -5
  39. package/package.json +9 -11
  40. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  41. package/lib/cli-engine/formatters/compact.js +0 -60
  42. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  43. package/lib/cli-engine/formatters/junit.js +0 -82
  44. package/lib/cli-engine/formatters/tap.js +0 -95
  45. package/lib/cli-engine/formatters/unix.js +0 -58
  46. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  47. package/lib/eslint/flat-eslint.js +0 -1142
  48. package/lib/rule-tester/flat-rule-tester.js +0 -1122
  49. package/lib/rules/require-jsdoc.js +0 -122
  50. package/lib/rules/valid-jsdoc.js +0 -516
package/README.md CHANGED
@@ -43,7 +43,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
43
43
 
44
44
  ## Installation and Usage
45
45
 
46
- Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
46
+ Prerequisites: [Node.js](https://nodejs.org/) (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
47
47
 
48
48
  You can install and configure ESLint using this command:
49
49
 
@@ -293,7 +293,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
293
293
  <h3>Platinum Sponsors</h3>
294
294
  <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>
295
295
  <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>
296
- <p><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> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
296
+ <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/logo.png" alt="JetBrains" 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> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
297
297
  <p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" 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" 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://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://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
298
298
  <!--sponsorsend-->
299
299
 
@@ -23,6 +23,8 @@
23
23
  { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] },
24
24
  { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] },
25
25
  { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] },
26
- { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }
26
+ { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] },
27
+ { "removed": "valid-jsdoc", "replacedBy": [] },
28
+ { "removed": "require-jsdoc", "replacedBy": [] }
27
29
  ]
28
30
  }
package/lib/api.js CHANGED
@@ -9,7 +9,7 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- const { ESLint } = require("./eslint");
12
+ const { ESLint } = require("./eslint/eslint");
13
13
  const { Linter } = require("./linter");
14
14
  const { RuleTester } = require("./rule-tester");
15
15
  const { SourceCode } = require("./source-code");
@@ -41,6 +41,17 @@ const hash = require("./hash");
41
41
  const LintResultCache = require("./lint-result-cache");
42
42
 
43
43
  const debug = require("debug")("eslint:cli-engine");
44
+ const removedFormatters = new Set([
45
+ "checkstyle",
46
+ "codeframe",
47
+ "compact",
48
+ "jslint-xml",
49
+ "junit",
50
+ "table",
51
+ "tap",
52
+ "unix",
53
+ "visualstudio"
54
+ ]);
44
55
  const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
45
56
 
46
57
  //------------------------------------------------------------------------------
@@ -639,7 +650,7 @@ class CLIEngine {
639
650
  });
640
651
  const lintResultCache =
641
652
  options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null;
642
- const linter = new Linter({ cwd: options.cwd });
653
+ const linter = new Linter({ cwd: options.cwd, configType: "eslintrc" });
643
654
 
644
655
  /** @type {ConfigArray[]} */
645
656
  const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()];
@@ -1047,7 +1058,7 @@ class CLIEngine {
1047
1058
  try {
1048
1059
  return require(formatterPath);
1049
1060
  } catch (ex) {
1050
- if (format === "table" || format === "codeframe") {
1061
+ if (removedFormatters.has(format)) {
1051
1062
  ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``;
1052
1063
  } else {
1053
1064
  ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
@@ -1,20 +1,8 @@
1
1
  [
2
- {
3
- "name": "checkstyle",
4
- "description": "Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format."
5
- },
6
- {
7
- "name": "compact",
8
- "description": "Human-readable output format. Mimics the default output of JSHint."
9
- },
10
2
  {
11
3
  "name": "html",
12
4
  "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser."
13
5
  },
14
- {
15
- "name": "jslint-xml",
16
- "description": "Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/)."
17
- },
18
6
  {
19
7
  "name": "json-with-metadata",
20
8
  "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
@@ -23,24 +11,8 @@
23
11
  "name": "json",
24
12
  "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
25
13
  },
26
- {
27
- "name": "junit",
28
- "description": "Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/)."
29
- },
30
14
  {
31
15
  "name": "stylish",
32
16
  "description": "Human-readable output format. This is the default formatter."
33
- },
34
- {
35
- "name": "tap",
36
- "description": "Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format."
37
- },
38
- {
39
- "name": "unix",
40
- "description": "Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html)."
41
- },
42
- {
43
- "name": "visualstudio",
44
- "description": "Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code."
45
17
  }
46
- ]
18
+ ]
package/lib/cli.js CHANGED
@@ -18,8 +18,8 @@
18
18
  const fs = require("fs"),
19
19
  path = require("path"),
20
20
  { promisify } = require("util"),
21
- { ESLint } = require("./eslint"),
22
- { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"),
21
+ { LegacyESLint } = require("./eslint"),
22
+ { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"),
23
23
  createCLIOptions = require("./options"),
24
24
  log = require("./shared/logging"),
25
25
  RuntimeInfo = require("./shared/runtime-info"),
@@ -58,6 +58,16 @@ function quietFixPredicate(message) {
58
58
  return message.severity === 2;
59
59
  }
60
60
 
61
+ /**
62
+ * Predicate function for whether or not to run a rule in quiet mode.
63
+ * If a rule is set to warning, do not run it.
64
+ * @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
65
+ * @returns {boolean} True if the lint rule should run, false otherwise.
66
+ */
67
+ function quietRuleFilter(rule) {
68
+ return rule.severity === 2;
69
+ }
70
+
61
71
  /**
62
72
  * Translates the CLI options into the options expected by the ESLint constructor.
63
73
  * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
@@ -94,7 +104,9 @@ async function translateOptions({
94
104
  resolvePluginsRelativeTo,
95
105
  rule,
96
106
  rulesdir,
97
- warnIgnored
107
+ warnIgnored,
108
+ passOnNoPatterns,
109
+ maxWarnings
98
110
  }, configType) {
99
111
 
100
112
  let overrideConfig, overrideConfigFile;
@@ -187,12 +199,19 @@ async function translateOptions({
187
199
  fixTypes: fixType,
188
200
  ignore,
189
201
  overrideConfig,
190
- overrideConfigFile
202
+ overrideConfigFile,
203
+ passOnNoPatterns
191
204
  };
192
205
 
193
206
  if (configType === "flat") {
194
207
  options.ignorePatterns = ignorePattern;
195
208
  options.warnIgnored = warnIgnored;
209
+
210
+ /*
211
+ * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
212
+ * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
213
+ */
214
+ options.ruleFilter = quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
196
215
  } else {
197
216
  options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
198
217
  options.rulePaths = rulesdir;
@@ -304,10 +323,10 @@ const cli = {
304
323
  * Executes the CLI based on an array of arguments that is passed in.
305
324
  * @param {string|Array|Object} args The arguments to process.
306
325
  * @param {string} [text] The text to lint (used for TTY).
307
- * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
326
+ * @param {boolean} [allowFlatConfig=true] Whether or not to allow flat config.
308
327
  * @returns {Promise<number>} The exit code for the operation.
309
328
  */
310
- async execute(args, text, allowFlatConfig) {
329
+ async execute(args, text, allowFlatConfig = true) {
311
330
  if (Array.isArray(args)) {
312
331
  debug("CLI args: %o", args.slice(2));
313
332
  }
@@ -323,6 +342,10 @@ const cli = {
323
342
 
324
343
  debug("Using flat config?", usingFlatConfig);
325
344
 
345
+ if (allowFlatConfig && !usingFlatConfig) {
346
+ process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details.", "ESLintRCWarning");
347
+ }
348
+
326
349
  const CLIOptions = createCLIOptions(usingFlatConfig);
327
350
 
328
351
  /** @type {ParsedCLIOptions} */
@@ -376,8 +399,8 @@ const cli = {
376
399
  }
377
400
 
378
401
  const engine = usingFlatConfig
379
- ? new FlatESLint(await translateOptions(options, "flat"))
380
- : new ESLint(await translateOptions(options));
402
+ ? new ESLint(await translateOptions(options, "flat"))
403
+ : new LegacyESLint(await translateOptions(options));
381
404
  const fileConfig =
382
405
  await engine.calculateConfigForFile(options.printConfig);
383
406
 
@@ -405,7 +428,7 @@ const cli = {
405
428
  return 2;
406
429
  }
407
430
 
408
- const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint;
431
+ const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint;
409
432
 
410
433
  const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc"));
411
434
  let results;
@@ -42,6 +42,9 @@ exports.defaultConfig = [
42
42
  ecmaVersion: "latest",
43
43
  parser: require("espree"),
44
44
  parserOptions: {}
45
+ },
46
+ linterOptions: {
47
+ reportUnusedDisableDirectives: 1
45
48
  }
46
49
  },
47
50
 
@@ -13,7 +13,6 @@ const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array"
13
13
  const { flatConfigSchema } = require("./flat-config-schema");
14
14
  const { RuleValidator } = require("./rule-validator");
15
15
  const { defaultConfig } = require("./default-config");
16
- const jsPlugin = require("@eslint/js");
17
16
 
18
17
  //-----------------------------------------------------------------------------
19
18
  // Helpers
@@ -134,25 +133,6 @@ class FlatConfigArray extends ConfigArray {
134
133
  * @returns {Object} The preprocessed config.
135
134
  */
136
135
  [ConfigArraySymbol.preprocessConfig](config) {
137
- if (config === "eslint:recommended") {
138
-
139
- // if we are in a Node.js environment warn the user
140
- if (typeof process !== "undefined" && process.emitWarning) {
141
- process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
142
- }
143
-
144
- return jsPlugin.configs.recommended;
145
- }
146
-
147
- if (config === "eslint:all") {
148
-
149
- // if we are in a Node.js environment warn the user
150
- if (typeof process !== "undefined" && process.emitWarning) {
151
- process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
152
- }
153
-
154
- return jsPlugin.configs.all;
155
- }
156
136
 
157
137
  /*
158
138
  * If `shouldIgnore` is false, we remove any ignore patterns specified
@@ -5,6 +5,23 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Typedefs
10
+ //------------------------------------------------------------------------------
11
+
12
+ /** @typedef {import("../shared/types").Rule} Rule */
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Private Members
16
+ //------------------------------------------------------------------------------
17
+
18
+ // JSON schema that disallows passing any options
19
+ const noOptionsSchema = Object.freeze({
20
+ type: "array",
21
+ minItems: 0,
22
+ maxItems: 0
23
+ });
24
+
8
25
  //-----------------------------------------------------------------------------
9
26
  // Functions
10
27
  //-----------------------------------------------------------------------------
@@ -52,32 +69,39 @@ function getRuleFromConfig(ruleId, config) {
52
69
  const { pluginName, ruleName } = parseRuleId(ruleId);
53
70
 
54
71
  const plugin = config.plugins && config.plugins[pluginName];
55
- let rule = plugin && plugin.rules && plugin.rules[ruleName];
56
-
57
-
58
- // normalize function rules into objects
59
- if (rule && typeof rule === "function") {
60
- rule = {
61
- create: rule
62
- };
63
- }
72
+ const rule = plugin && plugin.rules && plugin.rules[ruleName];
64
73
 
65
74
  return rule;
66
75
  }
67
76
 
68
77
  /**
69
78
  * Gets a complete options schema for a rule.
70
- * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
71
- * @returns {Object} JSON Schema for the rule's options.
79
+ * @param {Rule} rule A rule object
80
+ * @throws {TypeError} If `meta.schema` is specified but is not an array, object or `false`.
81
+ * @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`.
72
82
  */
73
83
  function getRuleOptionsSchema(rule) {
74
84
 
75
- if (!rule) {
85
+ if (!rule.meta) {
86
+ return { ...noOptionsSchema }; // default if `meta.schema` is not specified
87
+ }
88
+
89
+ const schema = rule.meta.schema;
90
+
91
+ if (typeof schema === "undefined") {
92
+ return { ...noOptionsSchema }; // default if `meta.schema` is not specified
93
+ }
94
+
95
+ // `schema:false` is an allowed explicit opt-out of options validation for the rule
96
+ if (schema === false) {
76
97
  return null;
77
98
  }
78
99
 
79
- const schema = rule.schema || rule.meta && rule.meta.schema;
100
+ if (typeof schema !== "object" || schema === null) {
101
+ throw new TypeError("Rule's `meta.schema` must be an array or object");
102
+ }
80
103
 
104
+ // ESLint-specific array form needs to be converted into a valid JSON Schema definition
81
105
  if (Array.isArray(schema)) {
82
106
  if (schema.length) {
83
107
  return {
@@ -87,16 +111,13 @@ function getRuleOptionsSchema(rule) {
87
111
  maxItems: schema.length
88
112
  };
89
113
  }
90
- return {
91
- type: "array",
92
- minItems: 0,
93
- maxItems: 0
94
- };
95
114
 
115
+ // `schema:[]` is an explicit way to specify that the rule does not accept any options
116
+ return { ...noOptionsSchema };
96
117
  }
97
118
 
98
- // Given a full schema, leave it alone
99
- return schema || null;
119
+ // `schema:<object>` is assumed to be a valid JSON Schema definition
120
+ return schema;
100
121
  }
101
122
 
102
123
 
@@ -9,11 +9,6 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- /*
13
- * Note: This can be removed in ESLint v9 because structuredClone is available globally
14
- * starting in Node.js v17.
15
- */
16
- const structuredClone = require("@ungap/structured-clone").default;
17
12
  const { normalizeSeverityToNumber } = require("../shared/severity");
18
13
 
19
14
  //-----------------------------------------------------------------------------
@@ -53,6 +48,15 @@ function isNonNullObject(value) {
53
48
  return typeof value === "object" && value !== null;
54
49
  }
55
50
 
51
+ /**
52
+ * Check if a value is a non-null non-array object.
53
+ * @param {any} value The value to check.
54
+ * @returns {boolean} `true` if the value is a non-null non-array object.
55
+ */
56
+ function isNonArrayObject(value) {
57
+ return isNonNullObject(value) && !Array.isArray(value);
58
+ }
59
+
56
60
  /**
57
61
  * Check if a value is undefined.
58
62
  * @param {any} value The value to check.
@@ -63,19 +67,27 @@ function isUndefined(value) {
63
67
  }
64
68
 
65
69
  /**
66
- * Deeply merges two objects.
70
+ * Deeply merges two non-array objects.
67
71
  * @param {Object} first The base object.
68
72
  * @param {Object} second The overrides object.
73
+ * @param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
69
74
  * @returns {Object} An object with properties from both first and second.
70
75
  */
71
- function deepMerge(first = {}, second = {}) {
76
+ function deepMerge(first, second, mergeMap = new Map()) {
72
77
 
73
- /*
74
- * If the second value is an array, just return it. We don't merge
75
- * arrays because order matters and we can't know the correct order.
76
- */
77
- if (Array.isArray(second)) {
78
- return second;
78
+ let secondMergeMap = mergeMap.get(first);
79
+
80
+ if (secondMergeMap) {
81
+ const result = secondMergeMap.get(second);
82
+
83
+ if (result) {
84
+
85
+ // If this combination of first and second arguments has been already visited, return the previously created result.
86
+ return result;
87
+ }
88
+ } else {
89
+ secondMergeMap = new Map();
90
+ mergeMap.set(first, secondMergeMap);
79
91
  }
80
92
 
81
93
  /*
@@ -89,27 +101,25 @@ function deepMerge(first = {}, second = {}) {
89
101
  ...second
90
102
  };
91
103
 
104
+ delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__"
105
+
106
+ // Store the pending result for this combination of first and second arguments.
107
+ secondMergeMap.set(second, result);
108
+
92
109
  for (const key of Object.keys(second)) {
93
110
 
94
111
  // avoid hairy edge case
95
- if (key === "__proto__") {
112
+ if (key === "__proto__" || !Object.prototype.propertyIsEnumerable.call(first, key)) {
96
113
  continue;
97
114
  }
98
115
 
99
116
  const firstValue = first[key];
100
117
  const secondValue = second[key];
101
118
 
102
- if (isNonNullObject(firstValue)) {
103
- result[key] = deepMerge(firstValue, secondValue);
104
- } else if (isUndefined(firstValue)) {
105
- if (isNonNullObject(secondValue)) {
106
- result[key] = deepMerge(
107
- Array.isArray(secondValue) ? [] : {},
108
- secondValue
109
- );
110
- } else if (!isUndefined(secondValue)) {
111
- result[key] = secondValue;
112
- }
119
+ if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
120
+ result[key] = deepMerge(firstValue, secondValue, mergeMap);
121
+ } else if (isUndefined(secondValue)) {
122
+ result[key] = firstValue;
113
123
  }
114
124
  }
115
125
 
@@ -66,6 +66,25 @@ function throwRuleNotFoundError({ pluginName, ruleName }, config) {
66
66
  throw new TypeError(errorMessage);
67
67
  }
68
68
 
69
+ /**
70
+ * The error type when a rule has an invalid `meta.schema`.
71
+ */
72
+ class InvalidRuleOptionsSchemaError extends Error {
73
+
74
+ /**
75
+ * Creates a new instance.
76
+ * @param {string} ruleId Id of the rule that has an invalid `meta.schema`.
77
+ * @param {Error} processingError Error caught while processing the `meta.schema`.
78
+ */
79
+ constructor(ruleId, processingError) {
80
+ super(
81
+ `Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`,
82
+ { cause: processingError }
83
+ );
84
+ this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA";
85
+ }
86
+ }
87
+
69
88
  //-----------------------------------------------------------------------------
70
89
  // Exports
71
90
  //-----------------------------------------------------------------------------
@@ -130,10 +149,14 @@ class RuleValidator {
130
149
 
131
150
  // Precompile and cache validator the first time
132
151
  if (!this.validators.has(rule)) {
133
- const schema = getRuleOptionsSchema(rule);
134
-
135
- if (schema) {
136
- this.validators.set(rule, ajv.compile(schema));
152
+ try {
153
+ const schema = getRuleOptionsSchema(rule);
154
+
155
+ if (schema) {
156
+ this.validators.set(rule, ajv.compile(schema));
157
+ }
158
+ } catch (err) {
159
+ throw new InvalidRuleOptionsSchemaError(ruleId, err);
137
160
  }
138
161
  }
139
162
 
@@ -105,20 +105,30 @@ class AllFilesIgnoredError extends Error {
105
105
 
106
106
  /**
107
107
  * Check if a given value is a non-empty string or not.
108
- * @param {any} x The value to check.
109
- * @returns {boolean} `true` if `x` is a non-empty string.
108
+ * @param {any} value The value to check.
109
+ * @returns {boolean} `true` if `value` is a non-empty string.
110
110
  */
111
- function isNonEmptyString(x) {
112
- return typeof x === "string" && x.trim() !== "";
111
+ function isNonEmptyString(value) {
112
+ return typeof value === "string" && value.trim() !== "";
113
113
  }
114
114
 
115
115
  /**
116
116
  * Check if a given value is an array of non-empty strings or not.
117
- * @param {any} x The value to check.
118
- * @returns {boolean} `true` if `x` is an array of non-empty strings.
117
+ * @param {any} value The value to check.
118
+ * @returns {boolean} `true` if `value` is an array of non-empty strings.
119
+ */
120
+ function isArrayOfNonEmptyString(value) {
121
+ return Array.isArray(value) && value.length && value.every(isNonEmptyString);
122
+ }
123
+
124
+ /**
125
+ * Check if a given value is an empty array or an array of non-empty strings.
126
+ * @param {any} value The value to check.
127
+ * @returns {boolean} `true` if `value` is an empty array or an array of non-empty
128
+ * strings.
119
129
  */
120
- function isArrayOfNonEmptyString(x) {
121
- return Array.isArray(x) && x.every(isNonEmptyString);
130
+ function isEmptyArrayOrArrayOfNonEmptyString(value) {
131
+ return Array.isArray(value) && value.every(isNonEmptyString);
122
132
  }
123
133
 
124
134
  //-----------------------------------------------------------------------------
@@ -655,9 +665,9 @@ class ESLintInvalidOptionsError extends Error {
655
665
 
656
666
  /**
657
667
  * Validates and normalizes options for the wrapped CLIEngine instance.
658
- * @param {FlatESLintOptions} options The options to process.
668
+ * @param {ESLintOptions} options The options to process.
659
669
  * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
660
- * @returns {FlatESLintOptions} The normalized options.
670
+ * @returns {ESLintOptions} The normalized options.
661
671
  */
662
672
  function processOptions({
663
673
  allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
@@ -676,6 +686,8 @@ function processOptions({
676
686
  overrideConfigFile = null,
677
687
  plugins = {},
678
688
  warnIgnored = true,
689
+ passOnNoPatterns = false,
690
+ ruleFilter = () => true,
679
691
  ...unknownOptions
680
692
  }) {
681
693
  const errors = [];
@@ -759,7 +771,7 @@ function processOptions({
759
771
  if (typeof ignore !== "boolean") {
760
772
  errors.push("'ignore' must be a boolean.");
761
773
  }
762
- if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
774
+ if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
763
775
  errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
764
776
  }
765
777
  if (typeof overrideConfig !== "object") {
@@ -768,6 +780,9 @@ function processOptions({
768
780
  if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
769
781
  errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
770
782
  }
783
+ if (typeof passOnNoPatterns !== "boolean") {
784
+ errors.push("'passOnNoPatterns' must be a boolean.");
785
+ }
771
786
  if (typeof plugins !== "object") {
772
787
  errors.push("'plugins' must be an object or null.");
773
788
  } else if (plugins !== null && Object.keys(plugins).includes("")) {
@@ -779,6 +794,9 @@ function processOptions({
779
794
  if (typeof warnIgnored !== "boolean") {
780
795
  errors.push("'warnIgnored' must be a boolean.");
781
796
  }
797
+ if (typeof ruleFilter !== "function") {
798
+ errors.push("'ruleFilter' must be a function.");
799
+ }
782
800
  if (errors.length > 0) {
783
801
  throw new ESLintInvalidOptionsError(errors);
784
802
  }
@@ -800,7 +818,9 @@ function processOptions({
800
818
  globInputPaths,
801
819
  ignore,
802
820
  ignorePatterns,
803
- warnIgnored
821
+ passOnNoPatterns,
822
+ warnIgnored,
823
+ ruleFilter
804
824
  };
805
825
  }
806
826