eslint 9.33.0 → 9.35.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 (42) hide show
  1. package/README.md +1 -1
  2. package/lib/cli-engine/file-enumerator.js +1 -1
  3. package/lib/cli.js +62 -266
  4. package/lib/config/flat-config-schema.js +1 -1
  5. package/lib/eslint/eslint-helpers.js +426 -6
  6. package/lib/eslint/eslint.js +381 -313
  7. package/lib/eslint/worker.js +164 -0
  8. package/lib/languages/js/source-code/source-code.js +3 -4
  9. package/lib/linter/esquery.js +3 -0
  10. package/lib/linter/interpolate.js +1 -1
  11. package/lib/linter/linter.js +3 -4
  12. package/lib/options.js +23 -9
  13. package/lib/rule-tester/rule-tester.js +3 -0
  14. package/lib/rules/array-callback-return.js +0 -1
  15. package/lib/rules/dot-notation.js +1 -1
  16. package/lib/rules/grouped-accessor-pairs.js +8 -7
  17. package/lib/rules/indent-legacy.js +1 -1
  18. package/lib/rules/index.js +1 -0
  19. package/lib/rules/no-alert.js +1 -1
  20. package/lib/rules/no-empty-function.js +20 -1
  21. package/lib/rules/no-empty-static-block.js +25 -1
  22. package/lib/rules/no-empty.js +37 -0
  23. package/lib/rules/no-eval.js +3 -1
  24. package/lib/rules/no-irregular-whitespace.js +2 -2
  25. package/lib/rules/no-loss-of-precision.js +26 -3
  26. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -0
  27. package/lib/rules/no-octal.js +1 -4
  28. package/lib/rules/no-trailing-spaces.js +2 -1
  29. package/lib/rules/no-useless-escape.js +1 -1
  30. package/lib/rules/prefer-regex-literals.js +1 -1
  31. package/lib/rules/preserve-caught-error.js +509 -0
  32. package/lib/rules/strict.js +2 -1
  33. package/lib/rules/utils/ast-utils.js +1 -1
  34. package/lib/rules/utils/char-source.js +1 -1
  35. package/lib/rules/yoda.js +2 -2
  36. package/lib/services/suppressions-service.js +3 -0
  37. package/lib/services/warning-service.js +13 -0
  38. package/lib/shared/naming.js +1 -1
  39. package/lib/shared/translate-cli-options.js +281 -0
  40. package/lib/types/index.d.ts +7 -0
  41. package/lib/types/rules.d.ts +22 -14
  42. package/package.json +3 -3
package/README.md CHANGED
@@ -328,7 +328,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
328
328
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
329
329
  <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://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a></p><h3>Silver Sponsors</h3>
330
330
  <p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/e6d15e1/logo.png" alt="Vite" 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" alt="American Express" 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>
331
- <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://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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
331
+ <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://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://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
332
332
  <h3>Technology Sponsors</h3>
333
333
  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.
334
334
  <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>
@@ -51,7 +51,7 @@ const debug = require("debug")("eslint:file-enumerator");
51
51
  //------------------------------------------------------------------------------
52
52
 
53
53
  const minimatchOpts = { dot: true, matchBase: true };
54
- const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;
54
+ const dotfilesPattern = /(?:^\.|[/\\]\.)[^/\\.].*/u;
55
55
  const NONE = 0;
56
56
  const IGNORED_SILENTLY = 1;
57
57
  const IGNORED = 2;
package/lib/cli.js CHANGED
@@ -16,8 +16,9 @@
16
16
  //------------------------------------------------------------------------------
17
17
 
18
18
  const fs = require("node:fs"),
19
+ { mkdir, stat, writeFile } = require("node:fs/promises"),
19
20
  path = require("node:path"),
20
- { promisify } = require("node:util"),
21
+ { pathToFileURL } = require("node:url"),
21
22
  { LegacyESLint } = require("./eslint"),
22
23
  {
23
24
  ESLint,
@@ -27,282 +28,23 @@ const fs = require("node:fs"),
27
28
  createCLIOptions = require("./options"),
28
29
  log = require("./shared/logging"),
29
30
  RuntimeInfo = require("./shared/runtime-info"),
30
- { normalizeSeverityToString } = require("./shared/severity");
31
- const { ModuleImporter } = require("@humanwhocodes/module-importer");
31
+ translateOptions = require("./shared/translate-cli-options");
32
32
  const { getCacheFile } = require("./eslint/eslint-helpers");
33
33
  const { SuppressionsService } = require("./services/suppressions-service");
34
34
  const debug = require("debug")("eslint:cli");
35
- const {
36
- normalizePackageName,
37
- getShorthandName,
38
- } = require("./shared/naming.js");
39
35
 
40
36
  //------------------------------------------------------------------------------
41
37
  // Types
42
38
  //------------------------------------------------------------------------------
43
39
 
44
- /** @import { ESLintOptions } from "./eslint/eslint.js" */
45
-
46
40
  /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
47
- /** @typedef {import("./types").Linter.LintMessage} LintMessage */
48
41
  /** @typedef {import("./types").ESLint.LintResult} LintResult */
49
- /** @typedef {import("./types").ESLint.Plugin} Plugin */
50
42
  /** @typedef {import("./types").ESLint.ResultsMeta} ResultsMeta */
51
43
 
52
44
  //------------------------------------------------------------------------------
53
45
  // Helpers
54
46
  //------------------------------------------------------------------------------
55
47
 
56
- const mkdir = promisify(fs.mkdir);
57
- const stat = promisify(fs.stat);
58
- const writeFile = promisify(fs.writeFile);
59
-
60
- /**
61
- * Loads plugins with the specified names.
62
- * @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
63
- * @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
64
- * @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
65
- */
66
- async function loadPlugins(importer, pluginNames) {
67
- const plugins = {};
68
-
69
- await Promise.all(
70
- pluginNames.map(async pluginName => {
71
- const longName = normalizePackageName(pluginName, "eslint-plugin");
72
- const module = await importer.import(longName);
73
-
74
- if (!("default" in module)) {
75
- throw new Error(
76
- `"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`,
77
- );
78
- }
79
-
80
- const shortName = getShorthandName(pluginName, "eslint-plugin");
81
-
82
- plugins[shortName] = module.default;
83
- }),
84
- );
85
-
86
- return plugins;
87
- }
88
-
89
- /**
90
- * Predicate function for whether or not to apply fixes in quiet mode.
91
- * If a message is a warning, do not apply a fix.
92
- * @param {LintMessage} message The lint result.
93
- * @returns {boolean} True if the lint message is an error (and thus should be
94
- * autofixed), false otherwise.
95
- */
96
- function quietFixPredicate(message) {
97
- return message.severity === 2;
98
- }
99
-
100
- /**
101
- * Predicate function for whether or not to run a rule in quiet mode.
102
- * If a rule is set to warning, do not run it.
103
- * @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
104
- * @returns {boolean} True if the lint rule should run, false otherwise.
105
- */
106
- function quietRuleFilter(rule) {
107
- return rule.severity === 2;
108
- }
109
-
110
- /**
111
- * Translates the CLI options into the options expected by the ESLint constructor.
112
- * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
113
- * @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
114
- * config to generate.
115
- * @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
116
- * @private
117
- */
118
- async function translateOptions(
119
- {
120
- cache,
121
- cacheFile,
122
- cacheLocation,
123
- cacheStrategy,
124
- config,
125
- configLookup,
126
- env,
127
- errorOnUnmatchedPattern,
128
- eslintrc,
129
- ext,
130
- fix,
131
- fixDryRun,
132
- fixType,
133
- flag,
134
- global,
135
- ignore,
136
- ignorePath,
137
- ignorePattern,
138
- inlineConfig,
139
- parser,
140
- parserOptions,
141
- plugin,
142
- quiet,
143
- reportUnusedDisableDirectives,
144
- reportUnusedDisableDirectivesSeverity,
145
- reportUnusedInlineConfigs,
146
- resolvePluginsRelativeTo,
147
- rule,
148
- rulesdir,
149
- stats,
150
- warnIgnored,
151
- passOnNoPatterns,
152
- maxWarnings,
153
- },
154
- configType,
155
- ) {
156
- let overrideConfig, overrideConfigFile;
157
- const importer = new ModuleImporter();
158
-
159
- if (configType === "flat") {
160
- overrideConfigFile =
161
- typeof config === "string" ? config : !configLookup;
162
- if (overrideConfigFile === false) {
163
- overrideConfigFile = void 0;
164
- }
165
-
166
- const languageOptions = {};
167
-
168
- if (global) {
169
- languageOptions.globals = global.reduce((obj, name) => {
170
- if (name.endsWith(":true")) {
171
- obj[name.slice(0, -5)] = "writable";
172
- } else {
173
- obj[name] = "readonly";
174
- }
175
- return obj;
176
- }, {});
177
- }
178
-
179
- if (parserOptions) {
180
- languageOptions.parserOptions = parserOptions;
181
- }
182
-
183
- if (parser) {
184
- languageOptions.parser = await importer.import(parser);
185
- }
186
-
187
- overrideConfig = [
188
- {
189
- ...(Object.keys(languageOptions).length > 0
190
- ? { languageOptions }
191
- : {}),
192
- rules: rule ? rule : {},
193
- },
194
- ];
195
-
196
- if (
197
- reportUnusedDisableDirectives ||
198
- reportUnusedDisableDirectivesSeverity !== void 0
199
- ) {
200
- overrideConfig[0].linterOptions = {
201
- reportUnusedDisableDirectives: reportUnusedDisableDirectives
202
- ? "error"
203
- : normalizeSeverityToString(
204
- reportUnusedDisableDirectivesSeverity,
205
- ),
206
- };
207
- }
208
-
209
- if (reportUnusedInlineConfigs !== void 0) {
210
- overrideConfig[0].linterOptions = {
211
- ...overrideConfig[0].linterOptions,
212
- reportUnusedInlineConfigs: normalizeSeverityToString(
213
- reportUnusedInlineConfigs,
214
- ),
215
- };
216
- }
217
-
218
- if (plugin) {
219
- overrideConfig[0].plugins = await loadPlugins(importer, plugin);
220
- }
221
-
222
- if (ext) {
223
- overrideConfig.push({
224
- files: ext.map(
225
- extension =>
226
- `**/*${extension.startsWith(".") ? "" : "."}${extension}`,
227
- ),
228
- });
229
- }
230
- } else {
231
- overrideConfigFile = config;
232
-
233
- overrideConfig = {
234
- env:
235
- env &&
236
- env.reduce((obj, name) => {
237
- obj[name] = true;
238
- return obj;
239
- }, {}),
240
- globals:
241
- global &&
242
- global.reduce((obj, name) => {
243
- if (name.endsWith(":true")) {
244
- obj[name.slice(0, -5)] = "writable";
245
- } else {
246
- obj[name] = "readonly";
247
- }
248
- return obj;
249
- }, {}),
250
- ignorePatterns: ignorePattern,
251
- parser,
252
- parserOptions,
253
- plugins: plugin,
254
- rules: rule,
255
- };
256
- }
257
-
258
- const options = {
259
- allowInlineConfig: inlineConfig,
260
- cache,
261
- cacheLocation: cacheLocation || cacheFile,
262
- cacheStrategy,
263
- errorOnUnmatchedPattern,
264
- fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
265
- fixTypes: fixType,
266
- ignore,
267
- overrideConfig,
268
- overrideConfigFile,
269
- passOnNoPatterns,
270
- };
271
-
272
- if (configType === "flat") {
273
- options.ignorePatterns = ignorePattern;
274
- options.stats = stats;
275
- options.warnIgnored = warnIgnored;
276
- options.flags = flag;
277
-
278
- /*
279
- * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
280
- * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
281
- */
282
- options.ruleFilter =
283
- quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
284
- } else {
285
- options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
286
- options.rulePaths = rulesdir;
287
- options.useEslintrc = eslintrc;
288
- options.extensions = ext;
289
- options.ignorePath = ignorePath;
290
- if (
291
- reportUnusedDisableDirectives ||
292
- reportUnusedDisableDirectivesSeverity !== void 0
293
- ) {
294
- options.reportUnusedDisableDirectives =
295
- reportUnusedDisableDirectives
296
- ? "error"
297
- : normalizeSeverityToString(
298
- reportUnusedDisableDirectivesSeverity,
299
- );
300
- }
301
- }
302
-
303
- return options;
304
- }
305
-
306
48
  /**
307
49
  * Count error messages.
308
50
  * @param {LintResult[]} results The lint results.
@@ -322,6 +64,26 @@ function countErrors(results) {
322
64
  return { errorCount, fatalErrorCount, warningCount };
323
65
  }
324
66
 
67
+ /**
68
+ * Creates an options module from the provided CLI options and encodes it as a data URL.
69
+ * @param {ParsedCLIOptions} options The CLI options.
70
+ * @returns {URL} The URL of the options module.
71
+ */
72
+ function createOptionsModule(options) {
73
+ const translateOptionsFileURL = new URL(
74
+ "./shared/translate-cli-options.js",
75
+ pathToFileURL(__filename),
76
+ ).href;
77
+ const optionsSrc =
78
+ `import translateOptions from ${JSON.stringify(translateOptionsFileURL)};\n` +
79
+ `export default await translateOptions(${JSON.stringify(options)}, "flat");\n`;
80
+
81
+ // Base64 encoding is typically shorter than URL encoding
82
+ return new URL(
83
+ `data:text/javascript;base64,${Buffer.from(optionsSrc).toString("base64")}`,
84
+ );
85
+ }
86
+
325
87
  /**
326
88
  * Check if a given file path is a directory or not.
327
89
  * @param {string} filePath The path to a file to check.
@@ -385,6 +147,30 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
385
147
  return true;
386
148
  }
387
149
 
150
+ /**
151
+ * Validates the `--concurrency` flag value.
152
+ * @param {string} concurrency The `--concurrency` flag value to validate.
153
+ * @returns {void}
154
+ * @throws {Error} If the `--concurrency` flag value is invalid.
155
+ */
156
+ function validateConcurrency(concurrency) {
157
+ if (
158
+ concurrency === void 0 ||
159
+ concurrency === "auto" ||
160
+ concurrency === "off"
161
+ ) {
162
+ return;
163
+ }
164
+
165
+ const concurrencyValue = Number(concurrency);
166
+
167
+ if (!Number.isInteger(concurrencyValue) || concurrencyValue < 1) {
168
+ throw new Error(
169
+ `Option concurrency: '${concurrency}' is not a positive integer, 'auto' or 'off'.`,
170
+ );
171
+ }
172
+ }
173
+
388
174
  //------------------------------------------------------------------------------
389
175
  // Public Interface
390
176
  //------------------------------------------------------------------------------
@@ -445,6 +231,7 @@ const cli = {
445
231
 
446
232
  try {
447
233
  options = CLIOptions.parse(args);
234
+ validateConcurrency(options.concurrency);
448
235
  } catch (error) {
449
236
  debug("Error parsing CLI options:", error.message);
450
237
 
@@ -614,11 +401,20 @@ const cli = {
614
401
  }
615
402
 
616
403
  const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint;
617
- const eslintOptions = await translateOptions(
618
- options,
619
- usingFlatConfig ? "flat" : "eslintrc",
620
- );
621
- const engine = new ActiveESLint(eslintOptions);
404
+
405
+ /** @type {ESLint|LegacyESLint} */
406
+ let engine;
407
+
408
+ if (options.concurrency && options.concurrency !== "off") {
409
+ const optionsURL = createOptionsModule(options);
410
+ engine = await ESLint.fromOptionsModule(optionsURL);
411
+ } else {
412
+ const eslintOptions = await translateOptions(
413
+ options,
414
+ usingFlatConfig ? "flat" : "eslintrc",
415
+ );
416
+ engine = new ActiveESLint(eslintOptions);
417
+ }
622
418
  let results;
623
419
 
624
420
  if (useStdin) {
@@ -230,7 +230,7 @@ function assertIsRuleSeverity(ruleId, value) {
230
230
  * @throws {TypeError} If the string isn't in the correct format.
231
231
  */
232
232
  function assertIsPluginMemberName(value) {
233
- if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
233
+ if (!/[\w\-@$]+(?:\/[\w\-$]+)+$/iu.test(value)) {
234
234
  throw new TypeError(
235
235
  `Expected string in the form "pluginName/objectName" but found "${value}".`,
236
236
  );