eslint 8.20.0 → 8.23.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/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
  };
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)) {
@@ -15,7 +15,6 @@ const Rules = require("../rules");
15
15
  // Helpers
16
16
  //-----------------------------------------------------------------------------
17
17
 
18
-
19
18
  exports.defaultConfig = [
20
19
  {
21
20
  plugins: {
@@ -41,21 +40,31 @@ exports.defaultConfig = [
41
40
  })
42
41
  }
43
42
  },
44
- ignores: [
45
- "**/node_modules/**",
46
- ".git/**"
47
- ],
48
43
  languageOptions: {
49
- ecmaVersion: "latest",
50
44
  sourceType: "module",
45
+ ecmaVersion: "latest",
51
46
  parser: "@/espree",
52
47
  parserOptions: {}
53
48
  }
54
49
  },
50
+
51
+ // default ignores are listed here
52
+ {
53
+ ignores: [
54
+ "**/node_modules/**",
55
+ ".git/**"
56
+ ]
57
+ },
58
+
59
+ // intentionally empty config to ensure these files are globbed by default
60
+ {
61
+ files: ["**/*.js", "**/*.mjs"]
62
+ },
55
63
  {
56
64
  files: ["**/*.cjs"],
57
65
  languageOptions: {
58
- sourceType: "commonjs"
66
+ sourceType: "commonjs",
67
+ ecmaVersion: "latest"
59
68
  }
60
69
  }
61
70
  ];
@@ -36,6 +36,8 @@ function splitPluginIdentifier(identifier) {
36
36
  };
37
37
  }
38
38
 
39
+ const originalBaseConfig = Symbol("originalBaseConfig");
40
+
39
41
  //-----------------------------------------------------------------------------
40
42
  // Exports
41
43
  //-----------------------------------------------------------------------------
@@ -48,10 +50,14 @@ class FlatConfigArray extends ConfigArray {
48
50
  /**
49
51
  * Creates a new instance.
50
52
  * @param {*[]} configs An array of configuration information.
51
- * @param {{basePath: string, baseConfig: FlatConfig}} options The options
53
+ * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
52
54
  * to use for the config array instance.
53
55
  */
54
- constructor(configs, { basePath, baseConfig = defaultConfig } = {}) {
56
+ constructor(configs, {
57
+ basePath,
58
+ shouldIgnore = true,
59
+ baseConfig = defaultConfig
60
+ } = {}) {
55
61
  super(configs, {
56
62
  basePath,
57
63
  schema: flatConfigSchema
@@ -62,6 +68,22 @@ class FlatConfigArray extends ConfigArray {
62
68
  } else {
63
69
  this.unshift(baseConfig);
64
70
  }
71
+
72
+ /**
73
+ * The baes config used to build the config array.
74
+ * @type {Array<FlatConfig>}
75
+ */
76
+ this[originalBaseConfig] = baseConfig;
77
+ Object.defineProperty(this, originalBaseConfig, { writable: false });
78
+
79
+ /**
80
+ * Determines if `ignores` fields should be honored.
81
+ * If true, then all `ignores` fields are honored.
82
+ * if false, then only `ignores` fields in the baseConfig are honored.
83
+ * @type {boolean}
84
+ */
85
+ this.shouldIgnore = shouldIgnore;
86
+ Object.defineProperty(this, "shouldIgnore", { writable: false });
65
87
  }
66
88
 
67
89
  /* eslint-disable class-methods-use-this -- Desired as instance method */
@@ -87,6 +109,23 @@ class FlatConfigArray extends ConfigArray {
87
109
  return require("../../conf/eslint-all");
88
110
  }
89
111
 
112
+ /*
113
+ * If `shouldIgnore` is false, we remove any ignore patterns specified
114
+ * in the config so long as it's not a default config and it doesn't
115
+ * have a `files` entry.
116
+ */
117
+ if (
118
+ !this.shouldIgnore &&
119
+ !this[originalBaseConfig].includes(config) &&
120
+ config.ignores &&
121
+ !config.files
122
+ ) {
123
+ /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
124
+ const { ignores, ...otherKeys } = config;
125
+
126
+ return otherKeys;
127
+ }
128
+
90
129
  return config;
91
130
  }
92
131
 
@@ -100,31 +139,72 @@ class FlatConfigArray extends ConfigArray {
100
139
  [ConfigArraySymbol.finalizeConfig](config) {
101
140
 
102
141
  const { plugins, languageOptions, processor } = config;
142
+ let parserName, processorName;
143
+ let invalidParser = false,
144
+ invalidProcessor = false;
103
145
 
104
146
  // Check parser value
105
- if (languageOptions && languageOptions.parser && typeof languageOptions.parser === "string") {
106
- 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);
107
150
 
108
- if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[parserName]) {
109
- throw new TypeError(`Key "parser": Could not find "${parserName}" in plugin "${pluginName}".`);
110
- }
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
+ }
111
156
 
112
- languageOptions.parser = plugins[pluginName].parsers[parserName];
157
+ languageOptions.parser = plugins[pluginName].parsers[localParserName];
158
+ } else {
159
+ invalidParser = true;
160
+ }
113
161
  }
114
162
 
115
163
  // Check processor value
116
- if (processor && typeof processor === "string") {
117
- const { pluginName, objectName: processorName } = splitPluginIdentifier(processor);
164
+ if (processor) {
165
+ if (typeof processor === "string") {
166
+ const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
118
167
 
119
- if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[processorName]) {
120
- throw new TypeError(`Key "processor": Could not find "${processorName}" in plugin "${pluginName}".`);
121
- }
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
+ }
122
173
 
123
- config.processor = plugins[pluginName].processors[processorName];
174
+ config.processor = plugins[pluginName].processors[localProcessorName];
175
+ } else {
176
+ invalidProcessor = true;
177
+ }
124
178
  }
125
179
 
126
180
  ruleValidator.validate(config);
127
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
+
128
208
  return config;
129
209
  }
130
210
  /* eslint-enable class-methods-use-this -- Desired as instance method */
@@ -20,7 +20,14 @@ function parseRuleId(ruleId) {
20
20
 
21
21
  // distinguish between core rules and plugin rules
22
22
  if (ruleId.includes("/")) {
23
- pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
23
+
24
+ // mimic scoped npm packages
25
+ if (ruleId.startsWith("@")) {
26
+ pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
27
+ } else {
28
+ pluginName = ruleId.slice(0, ruleId.indexOf("/"));
29
+ }
30
+
24
31
  ruleName = ruleId.slice(pluginName.length + 1);
25
32
  } else {
26
33
  pluginName = "@";
@@ -47,6 +54,7 @@ function getRuleFromConfig(ruleId, config) {
47
54
  const plugin = config.plugins && config.plugins[pluginName];
48
55
  let rule = plugin && plugin.rules && plugin.rules[ruleName];
49
56
 
57
+
50
58
  // normalize function rules into objects
51
59
  if (rule && typeof rule === "function") {
52
60
  rule = {