eslint 9.4.0 → 9.5.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 (32) hide show
  1. package/README.md +2 -2
  2. package/lib/api.js +1 -1
  3. package/lib/config/default-config.js +5 -0
  4. package/lib/config/flat-config-array.js +71 -8
  5. package/lib/config/flat-config-schema.js +46 -62
  6. package/lib/eslint/eslint-helpers.js +27 -17
  7. package/lib/eslint/eslint.js +14 -10
  8. package/lib/languages/js/index.js +247 -0
  9. package/lib/{source-code → languages/js/source-code}/source-code.js +38 -18
  10. package/lib/languages/js/validate-language-options.js +181 -0
  11. package/lib/linter/apply-disable-directives.js +8 -3
  12. package/lib/linter/linter.js +122 -121
  13. package/lib/linter/report-translator.js +14 -7
  14. package/lib/linter/vfile.js +104 -0
  15. package/lib/rule-tester/rule-tester.js +5 -2
  16. package/lib/rules/no-sparse-arrays.js +26 -3
  17. package/messages/all-matched-files-ignored.js +21 -0
  18. package/package.json +11 -11
  19. /package/lib/{source-code → languages/js/source-code}/index.js +0 -0
  20. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-comment-cursor.js +0 -0
  21. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-cursor.js +0 -0
  22. /package/lib/{source-code → languages/js/source-code}/token-store/cursor.js +0 -0
  23. /package/lib/{source-code → languages/js/source-code}/token-store/cursors.js +0 -0
  24. /package/lib/{source-code → languages/js/source-code}/token-store/decorative-cursor.js +0 -0
  25. /package/lib/{source-code → languages/js/source-code}/token-store/filter-cursor.js +0 -0
  26. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-comment-cursor.js +0 -0
  27. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-cursor.js +0 -0
  28. /package/lib/{source-code → languages/js/source-code}/token-store/index.js +0 -0
  29. /package/lib/{source-code → languages/js/source-code}/token-store/limit-cursor.js +0 -0
  30. /package/lib/{source-code → languages/js/source-code}/token-store/padded-token-cursor.js +0 -0
  31. /package/lib/{source-code → languages/js/source-code}/token-store/skip-cursor.js +0 -0
  32. /package/lib/{source-code → languages/js/source-code}/token-store/utils.js +0 -0
package/README.md CHANGED
@@ -289,13 +289,13 @@ Percy Ma
289
289
 
290
290
  ## Sponsors
291
291
 
292
- The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website.
292
+ The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate) to get your logo on our README and website.
293
293
 
294
294
  <!-- NOTE: This section is autogenerated. Do not manually edit.-->
295
295
  <!--sponsorsstart-->
296
296
  <h3>Platinum Sponsors</h3>
297
297
  <p><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>
298
- <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>
298
+ <p><a href="#"><img src="https://images.opencollective.com/guest-bf377e88/avatar.png" alt="Eli Schleifer" height="96"></a> <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>
299
299
  <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/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>
300
300
  <p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" 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.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> <a href="https://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
301
301
  <!--sponsorsend-->
package/lib/api.js CHANGED
@@ -13,7 +13,7 @@ const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint");
13
13
  const { LegacyESLint } = require("./eslint/legacy-eslint");
14
14
  const { Linter } = require("./linter");
15
15
  const { RuleTester } = require("./rule-tester");
16
- const { SourceCode } = require("./source-code");
16
+ const { SourceCode } = require("./languages/js/source-code");
17
17
 
18
18
  //-----------------------------------------------------------------------------
19
19
  // Functions
@@ -20,6 +20,10 @@ exports.defaultConfig = [
20
20
  plugins: {
21
21
  "@": {
22
22
 
23
+ languages: {
24
+ js: require("../languages/js")
25
+ },
26
+
23
27
  /*
24
28
  * Because we try to delay loading rules until absolutely
25
29
  * necessary, a proxy allows us to hook into the lazy-loading
@@ -37,6 +41,7 @@ exports.defaultConfig = [
37
41
  })
38
42
  }
39
43
  },
44
+ language: "@/js",
40
45
  languageOptions: {
41
46
  sourceType: "module",
42
47
  ecmaVersion: "latest",
@@ -10,7 +10,7 @@
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
12
  const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
13
- const { flatConfigSchema } = require("./flat-config-schema");
13
+ const { flatConfigSchema, hasMethod } = require("./flat-config-schema");
14
14
  const { RuleValidator } = require("./rule-validator");
15
15
  const { defaultConfig } = require("./default-config");
16
16
 
@@ -123,6 +123,43 @@ function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
123
123
  );
124
124
  }
125
125
 
126
+ /**
127
+ * Converts a languageOptions object to a JSON representation.
128
+ * @param {Record<string, any>} languageOptions The options to create a JSON
129
+ * representation of.
130
+ * @param {string} objectKey The key of the object being converted.
131
+ * @returns {Record<string, any>} The JSON representation of the languageOptions.
132
+ * @throws {TypeError} If a function is found in the languageOptions.
133
+ */
134
+ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
135
+
136
+ const result = {};
137
+
138
+ for (const [key, value] of Object.entries(languageOptions)) {
139
+ if (value) {
140
+ if (typeof value === "object") {
141
+ const name = getObjectId(value);
142
+
143
+ if (name && hasMethod(value)) {
144
+ result[key] = name;
145
+ } else {
146
+ result[key] = languageOptionsToJSON(value, key);
147
+ }
148
+ continue;
149
+ }
150
+
151
+ if (typeof value === "function") {
152
+ throw new TypeError(`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`);
153
+ }
154
+
155
+ }
156
+
157
+ result[key] = value;
158
+ }
159
+
160
+ return result;
161
+ }
162
+
126
163
  const originalBaseConfig = Symbol("originalBaseConfig");
127
164
  const originalLength = Symbol("originalLength");
128
165
  const baseLength = Symbol("baseLength");
@@ -269,10 +306,11 @@ class FlatConfigArray extends ConfigArray {
269
306
  */
270
307
  [ConfigArraySymbol.finalizeConfig](config) {
271
308
 
272
- const { plugins, languageOptions, processor } = config;
273
- let parserName, processorName;
309
+ const { plugins, language, languageOptions, processor } = config;
310
+ let parserName, processorName, languageName;
274
311
  let invalidParser = false,
275
- invalidProcessor = false;
312
+ invalidProcessor = false,
313
+ invalidLanguage = false;
276
314
 
277
315
  // Check parser value
278
316
  if (languageOptions && languageOptions.parser) {
@@ -290,6 +328,29 @@ class FlatConfigArray extends ConfigArray {
290
328
  }
291
329
  }
292
330
 
331
+ // Check language value
332
+ if (language) {
333
+ if (typeof language === "string") {
334
+ const { pluginName, objectName: localLanguageName } = splitPluginIdentifier(language);
335
+
336
+ languageName = language;
337
+
338
+ if (!plugins || !plugins[pluginName] || !plugins[pluginName].languages || !plugins[pluginName].languages[localLanguageName]) {
339
+ throw new TypeError(`Key "language": Could not find "${localLanguageName}" in plugin "${pluginName}".`);
340
+ }
341
+
342
+ config.language = plugins[pluginName].languages[localLanguageName];
343
+ } else {
344
+ invalidLanguage = true;
345
+ }
346
+
347
+ try {
348
+ config.language.validateLanguageOptions(config.languageOptions);
349
+ } catch (error) {
350
+ throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
351
+ }
352
+ }
353
+
293
354
  // Check processor value
294
355
  if (processor) {
295
356
  if (typeof processor === "string") {
@@ -329,6 +390,10 @@ class FlatConfigArray extends ConfigArray {
329
390
  throw new Error("Could not serialize processor object (missing 'meta' object).");
330
391
  }
331
392
 
393
+ if (invalidLanguage) {
394
+ throw new Error("Caching is not supported when language is an object.");
395
+ }
396
+
332
397
  return {
333
398
  ...this,
334
399
  plugins: Object.entries(plugins).map(([namespace, plugin]) => {
@@ -341,10 +406,8 @@ class FlatConfigArray extends ConfigArray {
341
406
 
342
407
  return `${namespace}:${pluginId}`;
343
408
  }),
344
- languageOptions: {
345
- ...languageOptions,
346
- parser: parserName
347
- },
409
+ language: languageName,
410
+ languageOptions: languageOptionsToJSON(languageOptions),
348
411
  processor: processorName
349
412
  };
350
413
  }
@@ -33,12 +33,6 @@ const ruleSeverities = new Map([
33
33
  [2, 2], ["error", 2]
34
34
  ]);
35
35
 
36
- const globalVariablesValues = new Set([
37
- true, "true", "writable", "writeable",
38
- false, "false", "readonly", "readable", null,
39
- "off"
40
- ]);
41
-
42
36
  /**
43
37
  * Check if a value is a non-null object.
44
38
  * @param {any} value The value to check.
@@ -143,6 +137,23 @@ function normalizeRuleOptions(ruleOptions) {
143
137
  return structuredClone(finalOptions);
144
138
  }
145
139
 
140
+ /**
141
+ * Determines if an object has any methods.
142
+ * @param {Object} object The object to check.
143
+ * @returns {boolean} `true` if the object has any methods.
144
+ */
145
+ function hasMethod(object) {
146
+
147
+ for (const key of Object.keys(object)) {
148
+
149
+ if (typeof object[key] === "function") {
150
+ return true;
151
+ }
152
+ }
153
+
154
+ return false;
155
+ }
156
+
146
157
  //-----------------------------------------------------------------------------
147
158
  // Assertions
148
159
  //-----------------------------------------------------------------------------
@@ -301,47 +312,48 @@ const deepObjectAssignSchema = {
301
312
  validate: "object"
302
313
  };
303
314
 
315
+
304
316
  //-----------------------------------------------------------------------------
305
317
  // High-Level Schemas
306
318
  //-----------------------------------------------------------------------------
307
319
 
308
320
  /** @type {ObjectPropertySchema} */
309
- const globalsSchema = {
310
- merge: "assign",
311
- validate(value) {
321
+ const languageOptionsSchema = {
322
+ merge(first = {}, second = {}) {
312
323
 
313
- assertIsObject(value);
324
+ const result = deepMerge(first, second);
314
325
 
315
- for (const key of Object.keys(value)) {
326
+ for (const [key, value] of Object.entries(result)) {
316
327
 
317
- // avoid hairy edge case
318
- if (key === "__proto__") {
319
- continue;
320
- }
328
+ /*
329
+ * Special case: Because the `parser` property is an object, it should
330
+ * not be deep merged. Instead, it should be replaced if it exists in
331
+ * the second object. To make this more generic, we just check for
332
+ * objects with methods and replace them if they exist in the second
333
+ * object.
334
+ */
335
+ if (isNonArrayObject(value)) {
336
+ if (hasMethod(value)) {
337
+ result[key] = second[key] ?? first[key];
338
+ continue;
339
+ }
321
340
 
322
- if (key !== key.trim()) {
323
- throw new TypeError(`Global "${key}" has leading or trailing whitespace.`);
341
+ // for other objects, make sure we aren't reusing the same object
342
+ result[key] = { ...result[key] };
343
+ continue;
324
344
  }
325
345
 
326
- if (!globalVariablesValues.has(value[key])) {
327
- throw new TypeError(`Key "${key}": Expected "readonly", "writable", or "off".`);
328
- }
329
346
  }
330
- }
347
+
348
+ return result;
349
+ },
350
+ validate: "object"
331
351
  };
332
352
 
333
353
  /** @type {ObjectPropertySchema} */
334
- const parserSchema = {
354
+ const languageSchema = {
335
355
  merge: "replace",
336
- validate(value) {
337
-
338
- if (!value || typeof value !== "object" ||
339
- (typeof value.parse !== "function" && typeof value.parseForESLint !== "function")
340
- ) {
341
- throw new TypeError("Expected object with parse() or parseForESLint() method.");
342
- }
343
-
344
- }
356
+ validate: assertIsPluginMemberName
345
357
  };
346
358
 
347
359
  /** @type {ObjectPropertySchema} */
@@ -501,28 +513,6 @@ const rulesSchema = {
501
513
  }
502
514
  };
503
515
 
504
- /** @type {ObjectPropertySchema} */
505
- const ecmaVersionSchema = {
506
- merge: "replace",
507
- validate(value) {
508
- if (typeof value === "number" || value === "latest") {
509
- return;
510
- }
511
-
512
- throw new TypeError("Expected a number or \"latest\".");
513
- }
514
- };
515
-
516
- /** @type {ObjectPropertySchema} */
517
- const sourceTypeSchema = {
518
- merge: "replace",
519
- validate(value) {
520
- if (typeof value !== "string" || !/^(?:script|module|commonjs)$/u.test(value)) {
521
- throw new TypeError("Expected \"script\", \"module\", or \"commonjs\".");
522
- }
523
- }
524
- };
525
-
526
516
  /**
527
517
  * Creates a schema that always throws an error. Useful for warning
528
518
  * about eslintrc-style keys.
@@ -568,15 +558,8 @@ const flatConfigSchema = {
568
558
  reportUnusedDisableDirectives: disableDirectiveSeveritySchema
569
559
  }
570
560
  },
571
- languageOptions: {
572
- schema: {
573
- ecmaVersion: ecmaVersionSchema,
574
- sourceType: sourceTypeSchema,
575
- globals: globalsSchema,
576
- parser: parserSchema,
577
- parserOptions: deepObjectAssignSchema
578
- }
579
- },
561
+ language: languageSchema,
562
+ languageOptions: languageOptionsSchema,
580
563
  processor: processorSchema,
581
564
  plugins: pluginsSchema,
582
565
  rules: rulesSchema
@@ -588,5 +571,6 @@ const flatConfigSchema = {
588
571
 
589
572
  module.exports = {
590
573
  flatConfigSchema,
574
+ hasMethod,
591
575
  assertIsRuleSeverity
592
576
  };
@@ -91,7 +91,7 @@ class AllFilesIgnoredError extends Error {
91
91
  */
92
92
  constructor(pattern) {
93
93
  super(`All files matched by '${pattern}' are ignored.`);
94
- this.messageTemplate = "all-files-ignored";
94
+ this.messageTemplate = "all-matched-files-ignored";
95
95
  this.messageData = { pattern };
96
96
  }
97
97
  }
@@ -494,7 +494,7 @@ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
494
494
 
495
495
  }
496
496
 
497
- return [...new Set(filePaths)];
497
+ return filePaths;
498
498
 
499
499
  }
500
500
 
@@ -543,10 +543,7 @@ async function findFiles({
543
543
 
544
544
  // files are added directly to the list
545
545
  if (stat.isFile()) {
546
- results.push({
547
- filePath,
548
- ignored: !configs.getConfig(filePath)
549
- });
546
+ results.push(filePath);
550
547
  }
551
548
 
552
549
  // directories need extensions attached
@@ -604,11 +601,10 @@ async function findFiles({
604
601
  });
605
602
 
606
603
  return [
607
- ...results,
608
- ...globbyResults.map(filePath => ({
609
- filePath: path.resolve(filePath),
610
- ignored: false
611
- }))
604
+ ...new Set([
605
+ ...results,
606
+ ...globbyResults.map(filePath => path.resolve(filePath))
607
+ ])
612
608
  ];
613
609
  }
614
610
 
@@ -630,17 +626,31 @@ function isErrorMessage(message) {
630
626
  * Returns result with warning by ignore settings
631
627
  * @param {string} filePath File path of checked code
632
628
  * @param {string} baseDir Absolute path of base directory
629
+ * @param {"ignored"|"external"|"unconfigured"} configStatus A status that determines why the file is ignored
633
630
  * @returns {LintResult} Result with single warning
634
631
  * @private
635
632
  */
636
- function createIgnoreResult(filePath, baseDir) {
633
+ function createIgnoreResult(filePath, baseDir, configStatus) {
637
634
  let message;
638
- const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
639
635
 
640
- if (isInNodeModules) {
641
- message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
642
- } else {
643
- message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
636
+ switch (configStatus) {
637
+ case "external":
638
+ message = "File ignored because outside of base path.";
639
+ break;
640
+ case "unconfigured":
641
+ message = "File ignored because no matching configuration was supplied.";
642
+ break;
643
+ default:
644
+ {
645
+ const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
646
+
647
+ if (isInNodeModules) {
648
+ message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
649
+ } else {
650
+ message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
651
+ }
652
+ }
653
+ break;
644
654
  }
645
655
 
646
656
  return {
@@ -68,7 +68,7 @@ const { Retrier } = require("@humanwhocodes/retry");
68
68
  * The options with which to configure the ESLint instance.
69
69
  * @typedef {Object} ESLintOptions
70
70
  * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
71
- * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
71
+ * @property {ConfigData|Array<ConfigData>} [baseConfig] Base config, extended by all configs used with this instance
72
72
  * @property {boolean} [cache] Enable result caching.
73
73
  * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
74
74
  * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
@@ -79,7 +79,7 @@ const { Retrier } = require("@humanwhocodes/retry");
79
79
  * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
80
80
  * @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
81
81
  * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`.
82
- * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
82
+ * @property {ConfigData|Array<ConfigData>} [overrideConfig] Override config, overrides all configs used with this instance
83
83
  * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
84
84
  * doesn't do any config file lookup when `true`; considered to be a config filename
85
85
  * when a string.
@@ -865,22 +865,24 @@ class ESLint {
865
865
  */
866
866
  const results = await Promise.all(
867
867
 
868
- filePaths.map(({ filePath, ignored }) => {
868
+ filePaths.map(filePath => {
869
+
870
+ const config = configs.getConfig(filePath);
869
871
 
870
872
  /*
871
- * If a filename was entered that matches an ignore
872
- * pattern, then notify the user.
873
+ * If a filename was entered that cannot be matched
874
+ * to a config, then notify the user.
873
875
  */
874
- if (ignored) {
876
+ if (!config) {
875
877
  if (warnIgnored) {
876
- return createIgnoreResult(filePath, cwd);
878
+ const configStatus = configs.getConfigStatus(filePath);
879
+
880
+ return createIgnoreResult(filePath, cwd, configStatus);
877
881
  }
878
882
 
879
883
  return void 0;
880
884
  }
881
885
 
882
- const config = configs.getConfig(filePath);
883
-
884
886
  // Skip if there is cached result.
885
887
  if (lintResultCache) {
886
888
  const cachedResult =
@@ -1029,7 +1031,9 @@ class ESLint {
1029
1031
  const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored;
1030
1032
 
1031
1033
  if (shouldWarnIgnored) {
1032
- results.push(createIgnoreResult(resolvedFilename, cwd));
1034
+ const configStatus = configs.getConfigStatus(resolvedFilename);
1035
+
1036
+ results.push(createIgnoreResult(resolvedFilename, cwd, configStatus));
1033
1037
  }
1034
1038
  } else {
1035
1039