eslint 8.21.0 → 8.23.1

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/bin/eslint.js +2 -4
  2. package/conf/globals.js +6 -1
  3. package/lib/cli-engine/file-enumerator.js +4 -2
  4. package/lib/cli.js +123 -29
  5. package/lib/config/flat-config-array.js +53 -12
  6. package/lib/eslint/eslint-helpers.js +39 -16
  7. package/lib/eslint/flat-eslint.js +29 -17
  8. package/lib/linter/code-path-analysis/code-path-segment.js +2 -2
  9. package/lib/linter/code-path-analysis/code-path-state.js +7 -7
  10. package/lib/linter/code-path-analysis/debug-helpers.js +3 -3
  11. package/lib/linter/code-path-analysis/id-generator.js +2 -2
  12. package/lib/linter/config-comment-parser.js +1 -2
  13. package/lib/linter/timing.js +4 -4
  14. package/lib/options.js +290 -242
  15. package/lib/rule-tester/flat-rule-tester.js +1 -1
  16. package/lib/rule-tester/rule-tester.js +1 -1
  17. package/lib/rules/array-callback-return.js +1 -1
  18. package/lib/rules/global-require.js +2 -1
  19. package/lib/rules/indent-legacy.js +4 -4
  20. package/lib/rules/indent.js +23 -15
  21. package/lib/rules/new-cap.js +2 -2
  22. package/lib/rules/no-extra-boolean-cast.js +1 -1
  23. package/lib/rules/no-extra-parens.js +2 -2
  24. package/lib/rules/no-fallthrough.js +8 -3
  25. package/lib/rules/no-labels.js +1 -1
  26. package/lib/rules/no-lone-blocks.js +1 -1
  27. package/lib/rules/no-useless-computed-key.js +1 -1
  28. package/lib/rules/no-var.js +1 -1
  29. package/lib/rules/no-warning-comments.js +24 -5
  30. package/lib/rules/object-shorthand.js +15 -0
  31. package/lib/rules/padded-blocks.js +1 -1
  32. package/lib/rules/prefer-arrow-callback.js +2 -2
  33. package/lib/rules/prefer-const.js +13 -1
  34. package/lib/rules/prefer-rest-params.js +1 -1
  35. package/lib/rules/require-yield.js +0 -1
  36. package/lib/rules/utils/ast-utils.js +10 -4
  37. package/lib/shared/logging.js +1 -1
  38. package/lib/shared/types.js +1 -1
  39. package/lib/source-code/token-store/cursor.js +1 -1
  40. package/package.json +9 -8
package/bin/eslint.js CHANGED
@@ -9,9 +9,6 @@
9
9
 
10
10
  "use strict";
11
11
 
12
- // to use V8's code cache to speed up instantiation time
13
- require("v8-compile-cache");
14
-
15
12
  // must do this initialization *before* other requires in order to work
16
13
  if (process.argv.includes("--debug")) {
17
14
  require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*");
@@ -137,6 +134,7 @@ ${message}`);
137
134
  // Otherwise, call the CLI.
138
135
  process.exitCode = await require("../lib/cli").execute(
139
136
  process.argv,
140
- process.argv.includes("--stdin") ? await readStdin() : null
137
+ process.argv.includes("--stdin") ? await readStdin() : null,
138
+ true
141
139
  );
142
140
  }()).catch(onFatalError);
package/conf/globals.js CHANGED
@@ -124,6 +124,10 @@ const es2022 = {
124
124
  ...es2021
125
125
  };
126
126
 
127
+ const es2023 = {
128
+ ...es2022
129
+ };
130
+
127
131
 
128
132
  //-----------------------------------------------------------------------------
129
133
  // Exports
@@ -140,5 +144,6 @@ module.exports = {
140
144
  es2019,
141
145
  es2020,
142
146
  es2021,
143
- es2022
147
+ es2022,
148
+ es2023
144
149
  };
@@ -122,7 +122,8 @@ function statSafeSync(filePath) {
122
122
  try {
123
123
  return fs.statSync(filePath);
124
124
  } catch (error) {
125
- /* istanbul ignore next */
125
+
126
+ /* c8 ignore next */
126
127
  if (error.code !== "ENOENT") {
127
128
  throw error;
128
129
  }
@@ -141,7 +142,8 @@ function readdirSafeSync(directoryPath) {
141
142
  try {
142
143
  return fs.readdirSync(directoryPath, { withFileTypes: true });
143
144
  } catch (error) {
144
- /* istanbul ignore next */
145
+
146
+ /* c8 ignore next */
145
147
  if (error.code !== "ENOENT") {
146
148
  throw error;
147
149
  }
package/lib/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
  "use strict";
7
7
 
8
8
  /*
9
- * The CLI object should *not* call process.exit() directly. It should only return
9
+ * NOTE: The CLI object should *not* call process.exit() directly. It should only return
10
10
  * exit codes. This allows other programs to use the CLI object and still control
11
11
  * when the program exits.
12
12
  */
@@ -19,9 +19,14 @@ const fs = require("fs"),
19
19
  path = require("path"),
20
20
  { promisify } = require("util"),
21
21
  { ESLint } = require("./eslint"),
22
- CLIOptions = require("./options"),
22
+ { FlatESLint } = require("./eslint/flat-eslint"),
23
+ createCLIOptions = require("./options"),
23
24
  log = require("./shared/logging"),
24
25
  RuntimeInfo = require("./shared/runtime-info");
26
+ const { Legacy: { naming } } = require("@eslint/eslintrc");
27
+ const { findFlatConfigFile } = require("./eslint/flat-eslint");
28
+ const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch");
29
+ const { ModuleImporter } = require("@humanwhocodes/module-importer");
25
30
 
26
31
  const debug = require("debug")("eslint:cli");
27
32
 
@@ -54,17 +59,20 @@ function quietFixPredicate(message) {
54
59
  }
55
60
 
56
61
  /**
57
- * Translates the CLI options into the options expected by the CLIEngine.
62
+ * Translates the CLI options into the options expected by the ESLint constructor.
58
63
  * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
59
- * @returns {ESLintOptions} The options object for the CLIEngine.
64
+ * @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
65
+ * config to generate.
66
+ * @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
60
67
  * @private
61
68
  */
62
- function translateOptions({
69
+ async function translateOptions({
63
70
  cache,
64
71
  cacheFile,
65
72
  cacheLocation,
66
73
  cacheStrategy,
67
74
  config,
75
+ configLookup,
68
76
  env,
69
77
  errorOnUnmatchedPattern,
70
78
  eslintrc,
@@ -85,19 +93,66 @@ function translateOptions({
85
93
  resolvePluginsRelativeTo,
86
94
  rule,
87
95
  rulesdir
88
- }) {
89
- return {
90
- allowInlineConfig: inlineConfig,
91
- cache,
92
- cacheLocation: cacheLocation || cacheFile,
93
- cacheStrategy,
94
- errorOnUnmatchedPattern,
95
- extensions: ext,
96
- fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
97
- fixTypes: fixType,
98
- ignore,
99
- ignorePath,
100
- overrideConfig: {
96
+ }, configType) {
97
+
98
+ let overrideConfig, overrideConfigFile;
99
+ const importer = new ModuleImporter();
100
+
101
+ if (configType === "flat") {
102
+ overrideConfigFile = (typeof config === "string") ? config : !configLookup;
103
+ if (overrideConfigFile === false) {
104
+ overrideConfigFile = void 0;
105
+ }
106
+
107
+ let globals = {};
108
+
109
+ if (global) {
110
+ globals = global.reduce((obj, name) => {
111
+ if (name.endsWith(":true")) {
112
+ obj[name.slice(0, -5)] = "writable";
113
+ } else {
114
+ obj[name] = "readonly";
115
+ }
116
+ return obj;
117
+ }, globals);
118
+ }
119
+
120
+ overrideConfig = [{
121
+ languageOptions: {
122
+ globals,
123
+ parserOptions: parserOptions || {}
124
+ },
125
+ rules: rule ? rule : {}
126
+ }];
127
+
128
+ if (parser) {
129
+ overrideConfig[0].languageOptions.parser = await importer.import(parser);
130
+ }
131
+
132
+ if (plugin) {
133
+ const plugins = {};
134
+
135
+ for (const pluginName of plugin) {
136
+
137
+ const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
138
+ const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
139
+
140
+ plugins[shortName] = await importer.import(longName);
141
+ }
142
+
143
+ overrideConfig[0].plugins = plugins;
144
+ }
145
+
146
+ if (ignorePattern) {
147
+ overrideConfig.push({
148
+ ignores: ignorePattern.map(gitignoreToMinimatch)
149
+ });
150
+ }
151
+
152
+ } else {
153
+ overrideConfigFile = config;
154
+
155
+ overrideConfig = {
101
156
  env: env && env.reduce((obj, name) => {
102
157
  obj[name] = true;
103
158
  return obj;
@@ -115,13 +170,32 @@ function translateOptions({
115
170
  parserOptions,
116
171
  plugins: plugin,
117
172
  rules: rule
118
- },
119
- overrideConfigFile: config,
120
- reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
121
- resolvePluginsRelativeTo,
122
- rulePaths: rulesdir,
123
- useEslintrc: eslintrc
173
+ };
174
+ }
175
+
176
+ const options = {
177
+ allowInlineConfig: inlineConfig,
178
+ cache,
179
+ cacheLocation: cacheLocation || cacheFile,
180
+ cacheStrategy,
181
+ errorOnUnmatchedPattern,
182
+ fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
183
+ fixTypes: fixType,
184
+ ignore,
185
+ ignorePath,
186
+ overrideConfig,
187
+ overrideConfigFile,
188
+ reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0
124
189
  };
190
+
191
+ if (configType !== "flat") {
192
+ options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
193
+ options.rulePaths = rulesdir;
194
+ options.useEslintrc = eslintrc;
195
+ options.extensions = ext;
196
+ }
197
+
198
+ return options;
125
199
  }
126
200
 
127
201
  /**
@@ -218,19 +292,34 @@ const cli = {
218
292
  * Executes the CLI based on an array of arguments that is passed in.
219
293
  * @param {string|Array|Object} args The arguments to process.
220
294
  * @param {string} [text] The text to lint (used for TTY).
295
+ * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
221
296
  * @returns {Promise<number>} The exit code for the operation.
222
297
  */
223
- async execute(args, text) {
298
+ async execute(args, text, allowFlatConfig) {
224
299
  if (Array.isArray(args)) {
225
300
  debug("CLI args: %o", args.slice(2));
226
301
  }
227
302
 
303
+ /*
304
+ * Before doing anything, we need to see if we are using a
305
+ * flat config file. If so, then we need to change the way command
306
+ * line args are parsed. This is temporary, and when we fully
307
+ * switch to flat config we can remove this logic.
308
+ */
309
+
310
+ const usingFlatConfig = allowFlatConfig && !!(await findFlatConfigFile(process.cwd()));
311
+
312
+ debug("Using flat config?", usingFlatConfig);
313
+
314
+ const CLIOptions = createCLIOptions(usingFlatConfig);
315
+
228
316
  /** @type {ParsedCLIOptions} */
229
317
  let options;
230
318
 
231
319
  try {
232
320
  options = CLIOptions.parse(args);
233
321
  } catch (error) {
322
+ debug("Error parsing CLI options:", error.message);
234
323
  log.error(error.message);
235
324
  return 2;
236
325
  }
@@ -251,6 +340,7 @@ const cli = {
251
340
  log.info(RuntimeInfo.environment());
252
341
  return 0;
253
342
  } catch (err) {
343
+ debug("Error retrieving environment info");
254
344
  log.error(err.message);
255
345
  return 2;
256
346
  }
@@ -266,7 +356,9 @@ const cli = {
266
356
  return 2;
267
357
  }
268
358
 
269
- const engine = new ESLint(translateOptions(options));
359
+ const engine = usingFlatConfig
360
+ ? new FlatESLint(await translateOptions(options, "flat"))
361
+ : new ESLint(await translateOptions(options));
270
362
  const fileConfig =
271
363
  await engine.calculateConfigForFile(options.printConfig);
272
364
 
@@ -289,7 +381,9 @@ const cli = {
289
381
  return 2;
290
382
  }
291
383
 
292
- const engine = new ESLint(translateOptions(options));
384
+ const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint;
385
+
386
+ const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc"));
293
387
  let results;
294
388
 
295
389
  if (useStdin) {
@@ -303,14 +397,14 @@ const cli = {
303
397
 
304
398
  if (options.fix) {
305
399
  debug("Fix mode enabled - applying fixes");
306
- await ESLint.outputFixes(results);
400
+ await ActiveESLint.outputFixes(results);
307
401
  }
308
402
 
309
403
  let resultsToPrint = results;
310
404
 
311
405
  if (options.quiet) {
312
406
  debug("Quiet mode enabled - filtering out warnings");
313
- resultsToPrint = ESLint.getErrorResults(resultsToPrint);
407
+ resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
314
408
  }
315
409
 
316
410
  if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
@@ -139,31 +139,72 @@ class FlatConfigArray extends ConfigArray {
139
139
  [ConfigArraySymbol.finalizeConfig](config) {
140
140
 
141
141
  const { plugins, languageOptions, processor } = config;
142
+ let parserName, processorName;
143
+ let invalidParser = false,
144
+ invalidProcessor = false;
142
145
 
143
146
  // Check parser value
144
- if (languageOptions && languageOptions.parser && typeof languageOptions.parser === "string") {
145
- const { pluginName, objectName: parserName } = splitPluginIdentifier(languageOptions.parser);
147
+ if (languageOptions && languageOptions.parser) {
148
+ if (typeof languageOptions.parser === "string") {
149
+ const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser);
146
150
 
147
- if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[parserName]) {
148
- throw new TypeError(`Key "parser": Could not find "${parserName}" in plugin "${pluginName}".`);
149
- }
151
+ parserName = languageOptions.parser;
152
+
153
+ if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) {
154
+ throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`);
155
+ }
150
156
 
151
- languageOptions.parser = plugins[pluginName].parsers[parserName];
157
+ languageOptions.parser = plugins[pluginName].parsers[localParserName];
158
+ } else {
159
+ invalidParser = true;
160
+ }
152
161
  }
153
162
 
154
163
  // Check processor value
155
- if (processor && typeof processor === "string") {
156
- const { pluginName, objectName: processorName } = splitPluginIdentifier(processor);
164
+ if (processor) {
165
+ if (typeof processor === "string") {
166
+ const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
157
167
 
158
- if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[processorName]) {
159
- throw new TypeError(`Key "processor": Could not find "${processorName}" in plugin "${pluginName}".`);
160
- }
168
+ processorName = processor;
169
+
170
+ if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
171
+ throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
172
+ }
161
173
 
162
- config.processor = plugins[pluginName].processors[processorName];
174
+ config.processor = plugins[pluginName].processors[localProcessorName];
175
+ } else {
176
+ invalidProcessor = true;
177
+ }
163
178
  }
164
179
 
165
180
  ruleValidator.validate(config);
166
181
 
182
+ // apply special logic for serialization into JSON
183
+ /* eslint-disable object-shorthand -- shorthand would change "this" value */
184
+ Object.defineProperty(config, "toJSON", {
185
+ value: function() {
186
+
187
+ if (invalidParser) {
188
+ throw new Error("Caching is not supported when parser is an object.");
189
+ }
190
+
191
+ if (invalidProcessor) {
192
+ throw new Error("Caching is not supported when processor is an object.");
193
+ }
194
+
195
+ return {
196
+ ...this,
197
+ plugins: Object.keys(plugins),
198
+ languageOptions: {
199
+ ...languageOptions,
200
+ parser: parserName
201
+ },
202
+ processor: processorName
203
+ };
204
+ }
205
+ });
206
+ /* eslint-enable object-shorthand -- ok to enable now */
207
+
167
208
  return config;
168
209
  }
169
210
  /* eslint-enable class-methods-use-this -- Desired as instance method */
@@ -15,6 +15,7 @@ const fsp = fs.promises;
15
15
  const isGlob = require("is-glob");
16
16
  const globby = require("globby");
17
17
  const hash = require("../cli-engine/hash");
18
+ const minimatch = require("minimatch");
18
19
 
19
20
  //-----------------------------------------------------------------------------
20
21
  // Errors
@@ -104,6 +105,8 @@ function isGlobPattern(pattern) {
104
105
  * false to not interpret glob patterns.
105
106
  * @param {string} args.cwd The current working directory to find from.
106
107
  * @param {FlatConfigArray} args.configs The configs for the current run.
108
+ * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
109
+ * should throw an error.
107
110
  * @returns {Promise<Array<string>>} The fully resolved file paths.
108
111
  * @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
109
112
  * @throws {NoFilesFoundError} If no files matched the given patterns.
@@ -112,7 +115,8 @@ async function findFiles({
112
115
  patterns,
113
116
  globInputPaths,
114
117
  cwd,
115
- configs
118
+ configs,
119
+ errorOnUnmatchedPattern
116
120
  }) {
117
121
 
118
122
  const results = [];
@@ -123,14 +127,14 @@ async function findFiles({
123
127
  const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
124
128
  const stats = await Promise.all(
125
129
  filePaths.map(
126
- filePath => fsp.stat(filePath).catch(() => {})
130
+ filePath => fsp.stat(filePath).catch(() => { })
127
131
  )
128
132
  );
129
133
 
130
134
  stats.forEach((stat, index) => {
131
135
 
132
136
  const filePath = filePaths[index];
133
- const pattern = patterns[index];
137
+ const pattern = normalizeToPosix(patterns[index]);
134
138
 
135
139
  if (stat) {
136
140
 
@@ -154,6 +158,11 @@ async function findFiles({
154
158
  return false;
155
159
  }
156
160
 
161
+ // patterns starting with ** always apply
162
+ if (filePattern.startsWith("**")) {
163
+ return true;
164
+ }
165
+
157
166
  // patterns ending with * are not used for file search
158
167
  if (filePattern.endsWith("*")) {
159
168
  return false;
@@ -164,11 +173,27 @@ async function findFiles({
164
173
  return false;
165
174
  }
166
175
 
167
- // check if the pattern would be inside the cwd or not
176
+ // check if the pattern would be inside the config base path or not
168
177
  const fullFilePattern = path.join(cwd, filePattern);
169
- const relativeFilePattern = path.relative(configs.basePath, fullFilePattern);
178
+ const patternRelativeToConfigBasePath = path.relative(configs.basePath, fullFilePattern);
179
+
180
+ if (patternRelativeToConfigBasePath.startsWith("..")) {
181
+ return false;
182
+ }
183
+
184
+ // check if the pattern matches
185
+ if (minimatch(filePath, path.dirname(fullFilePattern), { partial: true })) {
186
+ return true;
187
+ }
170
188
 
171
- return !relativeFilePattern.startsWith("..");
189
+ // check if the pattern is inside the directory or not
190
+ const patternRelativeToFilePath = path.relative(filePath, fullFilePattern);
191
+
192
+ if (patternRelativeToFilePath.startsWith("..")) {
193
+ return false;
194
+ }
195
+
196
+ return true;
172
197
  })
173
198
  .map(filePattern => {
174
199
  if (filePattern.startsWith("**")) {
@@ -222,14 +247,16 @@ async function findFiles({
222
247
  }
223
248
 
224
249
  // no files were found
225
- throw new NoFilesFoundError(globbyPattern, globInputPaths);
250
+ if (errorOnUnmatchedPattern) {
251
+ throw new NoFilesFoundError(globbyPattern, globInputPaths);
252
+ }
226
253
  }
227
254
  /* eslint-enable no-unreachable-loop -- Go back to normal. */
228
255
 
229
256
  }
230
257
 
231
258
  // there were patterns that didn't match anything, tell the user
232
- if (missingPatterns.length) {
259
+ if (errorOnUnmatchedPattern && missingPatterns.length) {
233
260
  throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
234
261
  }
235
262
 
@@ -322,6 +349,7 @@ function createIgnoreResult(filePath, baseDir) {
322
349
  message
323
350
  }
324
351
  ],
352
+ suppressedMessages: [],
325
353
  errorCount: 0,
326
354
  warningCount: 1,
327
355
  fatalErrorCount: 0,
@@ -378,7 +406,6 @@ function processOptions({
378
406
  cacheStrategy = "metadata",
379
407
  cwd = process.cwd(),
380
408
  errorOnUnmatchedPattern = true,
381
- extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
382
409
  fix = false,
383
410
  fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
384
411
  globInputPaths = true,
@@ -405,6 +432,9 @@ function processOptions({
405
432
  if (unknownOptionKeys.includes("envs")) {
406
433
  errors.push("'envs' has been removed.");
407
434
  }
435
+ if (unknownOptionKeys.includes("extensions")) {
436
+ errors.push("'extensions' has been removed.");
437
+ }
408
438
  if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
409
439
  errors.push("'resolvePluginsRelativeTo' has been removed.");
410
440
  }
@@ -436,9 +466,6 @@ function processOptions({
436
466
  if (typeof cache !== "boolean") {
437
467
  errors.push("'cache' must be a boolean.");
438
468
  }
439
- if (cache) {
440
- errors.push("'cache' option is not yet supported.");
441
- }
442
469
  if (!isNonEmptyString(cacheLocation)) {
443
470
  errors.push("'cacheLocation' must be a non-empty string.");
444
471
  }
@@ -454,9 +481,6 @@ function processOptions({
454
481
  if (typeof errorOnUnmatchedPattern !== "boolean") {
455
482
  errors.push("'errorOnUnmatchedPattern' must be a boolean.");
456
483
  }
457
- if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
458
- errors.push("'extensions' must be an array of non-empty strings or null.");
459
- }
460
484
  if (typeof fix !== "boolean" && typeof fix !== "function") {
461
485
  errors.push("'fix' must be a boolean or a function.");
462
486
  }
@@ -510,7 +534,6 @@ function processOptions({
510
534
  overrideConfig,
511
535
  cwd,
512
536
  errorOnUnmatchedPattern,
513
- extensions,
514
537
  fix,
515
538
  fixTypes,
516
539
  globInputPaths,