eslint 8.40.0 → 8.42.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/README.md CHANGED
@@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
284
284
  <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>
285
285
  <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>
286
286
  <p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" 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></p><h3>Bronze Sponsors</h3>
287
- <p><a href="https://paydaysay.com/"><img src="https://images.opencollective.com/payday-say-organization/9cd2467/logo.png" alt="PayDay Say" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" 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: free icons, photos, illustrations, and music" 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://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
287
+ <p><a href="#"><img src="https://images.opencollective.com/king-billy-slots1/c30c2aa/avatar.png" alt="King Billy Slots" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" 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: free icons, photos, illustrations, and music" 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://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
288
288
  <!--sponsorsend-->
289
289
 
290
290
  ## Technology Sponsors
@@ -308,9 +308,11 @@ function createIgnoreResult(filePath, baseDir) {
308
308
  filePath: path.resolve(filePath),
309
309
  messages: [
310
310
  {
311
+ ruleId: null,
311
312
  fatal: false,
312
313
  severity: 1,
313
- message
314
+ message,
315
+ nodeType: null
314
316
  }
315
317
  ],
316
318
  suppressedMessages: [],
package/lib/cli.js CHANGED
@@ -19,12 +19,11 @@ const fs = require("fs"),
19
19
  path = require("path"),
20
20
  { promisify } = require("util"),
21
21
  { ESLint } = require("./eslint"),
22
- { FlatESLint } = require("./eslint/flat-eslint"),
22
+ { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"),
23
23
  createCLIOptions = require("./options"),
24
24
  log = require("./shared/logging"),
25
25
  RuntimeInfo = require("./shared/runtime-info");
26
26
  const { Legacy: { naming } } = require("@eslint/eslintrc");
27
- const { findFlatConfigFile } = require("./eslint/flat-eslint");
28
27
  const { ModuleImporter } = require("@humanwhocodes/module-importer");
29
28
 
30
29
  const debug = require("debug")("eslint:cli");
@@ -275,31 +274,6 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
275
274
  return true;
276
275
  }
277
276
 
278
- /**
279
- * Returns whether flat config should be used.
280
- * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
281
- * @returns {Promise<boolean>} Where flat config should be used.
282
- */
283
- async function shouldUseFlatConfig(allowFlatConfig) {
284
- if (!allowFlatConfig) {
285
- return false;
286
- }
287
-
288
- switch (process.env.ESLINT_USE_FLAT_CONFIG) {
289
- case "true":
290
- return true;
291
- case "false":
292
- return false;
293
- default:
294
-
295
- /*
296
- * If neither explicitly enabled nor disabled, then use the presence
297
- * of a flat config file to determine enablement.
298
- */
299
- return !!(await findFlatConfigFile(process.cwd()));
300
- }
301
- }
302
-
303
277
  //------------------------------------------------------------------------------
304
278
  // Public Interface
305
279
  //------------------------------------------------------------------------------
@@ -329,7 +303,7 @@ const cli = {
329
303
  * switch to flat config we can remove this logic.
330
304
  */
331
305
 
332
- const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig);
306
+ const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig();
333
307
 
334
308
  debug("Using flat config?", usingFlatConfig);
335
309
 
@@ -48,7 +48,7 @@ exports.defaultConfig = [
48
48
  // default ignores are listed here
49
49
  {
50
50
  ignores: [
51
- "**/node_modules/*",
51
+ "**/node_modules/",
52
52
  ".git/"
53
53
  ]
54
54
  },
@@ -591,14 +591,10 @@ function isErrorMessage(message) {
591
591
  */
592
592
  function createIgnoreResult(filePath, baseDir) {
593
593
  let message;
594
- const isHidden = filePath.split(path.sep)
595
- .find(segment => /^\./u.test(segment));
596
- const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
597
-
598
- if (isHidden) {
599
- message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
600
- } else if (isInNodeModules) {
601
- message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
594
+ const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
595
+
596
+ if (isInNodeModules) {
597
+ message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.";
602
598
  } else {
603
599
  message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
604
600
  }
@@ -607,9 +603,11 @@ function createIgnoreResult(filePath, baseDir) {
607
603
  filePath: path.resolve(filePath),
608
604
  messages: [
609
605
  {
606
+ ruleId: null,
610
607
  fatal: false,
611
608
  severity: 1,
612
- message
609
+ message,
610
+ nodeType: null
613
611
  }
614
612
  ],
615
613
  suppressedMessages: [],
@@ -758,6 +756,9 @@ function processOptions({
758
756
  if (typeof ignore !== "boolean") {
759
757
  errors.push("'ignore' must be a boolean.");
760
758
  }
759
+ if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
760
+ errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
761
+ }
761
762
  if (typeof overrideConfig !== "object") {
762
763
  errors.push("'overrideConfig' must be an object or null.");
763
764
  }
@@ -55,6 +55,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
55
55
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
56
56
  /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
57
57
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
58
+ /** @typedef {import("../shared/types").LintResult} LintResult */
58
59
  /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
59
60
  /** @typedef {import("../shared/types").Plugin} Plugin */
60
61
  /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
@@ -76,7 +77,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
76
77
  * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
77
78
  * @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.
78
79
  * @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
79
- * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores.
80
+ * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`.
80
81
  * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
81
82
  * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
82
83
  * doesn't do any config file lookup when `true`; considered to be a config filename
@@ -261,7 +262,7 @@ function compareResultsByFilePath(a, b) {
261
262
  * Searches from the current working directory up until finding the
262
263
  * given flat config filename.
263
264
  * @param {string} cwd The current working directory to search from.
264
- * @returns {Promise<string|null>} The filename if found or `null` if not.
265
+ * @returns {Promise<string|undefined>} The filename if found or `undefined` if not.
265
266
  */
266
267
  function findFlatConfigFile(cwd) {
267
268
  return findUp(
@@ -326,7 +327,7 @@ async function loadFlatConfigFile(filePath) {
326
327
  * as override config file is not explicitly set to `false`, it will search
327
328
  * upwards from the cwd for a file named `eslint.config.js`.
328
329
  * @param {import("./eslint").ESLintOptions} options The ESLint instance options.
329
- * @returns {{configFilePath:string,basePath:string,error:boolean}} Location information for
330
+ * @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for
330
331
  * the config file.
331
332
  */
332
333
  async function locateConfigFileToUse({ configFile, cwd }) {
@@ -334,7 +335,7 @@ async function locateConfigFileToUse({ configFile, cwd }) {
334
335
  // determine where to load config file from
335
336
  let configFilePath;
336
337
  let basePath = cwd;
337
- let error = false;
338
+ let error = null;
338
339
 
339
340
  if (typeof configFile === "string") {
340
341
  debug(`Override config file path is ${configFile}`);
@@ -346,7 +347,7 @@ async function locateConfigFileToUse({ configFile, cwd }) {
346
347
  if (configFilePath) {
347
348
  basePath = path.resolve(path.dirname(configFilePath));
348
349
  } else {
349
- error = true;
350
+ error = new Error("Could not find config file.");
350
351
  }
351
352
 
352
353
  }
@@ -385,7 +386,7 @@ async function calculateConfigArray(eslint, {
385
386
 
386
387
  // config file is required to calculate config
387
388
  if (error) {
388
- throw new Error("Could not find config file.");
389
+ throw error;
389
390
  }
390
391
 
391
392
  const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore });
@@ -404,45 +405,39 @@ async function calculateConfigArray(eslint, {
404
405
  // add in any configured defaults
405
406
  configs.push(...slots.defaultConfigs);
406
407
 
407
- let allIgnorePatterns = [];
408
-
409
408
  // append command line ignore patterns
410
- if (ignorePatterns) {
411
- if (typeof ignorePatterns === "string") {
412
- allIgnorePatterns.push(ignorePatterns);
413
- } else {
414
- allIgnorePatterns.push(...ignorePatterns);
415
- }
416
- }
409
+ if (ignorePatterns && ignorePatterns.length > 0) {
417
410
 
418
- /*
419
- * If the config file basePath is different than the cwd, then
420
- * the ignore patterns won't work correctly. Here, we adjust the
421
- * ignore pattern to include the correct relative path. Patterns
422
- * loaded from ignore files are always relative to the cwd, whereas
423
- * the config file basePath can be an ancestor of the cwd.
424
- */
425
- if (basePath !== cwd && allIgnorePatterns.length) {
411
+ let relativeIgnorePatterns;
426
412
 
427
- const relativeIgnorePath = path.relative(basePath, cwd);
413
+ /*
414
+ * If the config file basePath is different than the cwd, then
415
+ * the ignore patterns won't work correctly. Here, we adjust the
416
+ * ignore pattern to include the correct relative path. Patterns
417
+ * passed as `ignorePatterns` are relative to the cwd, whereas
418
+ * the config file basePath can be an ancestor of the cwd.
419
+ */
420
+ if (basePath === cwd) {
421
+ relativeIgnorePatterns = ignorePatterns;
422
+ } else {
428
423
 
429
- allIgnorePatterns = allIgnorePatterns.map(pattern => {
430
- const negated = pattern.startsWith("!");
431
- const basePattern = negated ? pattern.slice(1) : pattern;
424
+ const relativeIgnorePath = path.relative(basePath, cwd);
432
425
 
433
- return (negated ? "!" : "") +
434
- path.posix.join(relativeIgnorePath, basePattern);
435
- });
436
- }
426
+ relativeIgnorePatterns = ignorePatterns.map(pattern => {
427
+ const negated = pattern.startsWith("!");
428
+ const basePattern = negated ? pattern.slice(1) : pattern;
437
429
 
438
- if (allIgnorePatterns.length) {
430
+ return (negated ? "!" : "") +
431
+ path.posix.join(relativeIgnorePath, basePattern);
432
+ });
433
+ }
439
434
 
440
435
  /*
441
436
  * Ignore patterns are added to the end of the config array
442
437
  * so they can override default ignores.
443
438
  */
444
439
  configs.push({
445
- ignores: allIgnorePatterns
440
+ ignores: relativeIgnorePatterns
446
441
  });
447
442
  }
448
443
 
@@ -1212,11 +1207,31 @@ class FlatESLint {
1212
1207
  }
1213
1208
  }
1214
1209
 
1210
+ /**
1211
+ * Returns whether flat config should be used.
1212
+ * @returns {Promise<boolean>} Whether flat config should be used.
1213
+ */
1214
+ async function shouldUseFlatConfig() {
1215
+ switch (process.env.ESLINT_USE_FLAT_CONFIG) {
1216
+ case "true":
1217
+ return true;
1218
+ case "false":
1219
+ return false;
1220
+ default:
1221
+
1222
+ /*
1223
+ * If neither explicitly enabled nor disabled, then use the presence
1224
+ * of a flat config file to determine enablement.
1225
+ */
1226
+ return !!(await findFlatConfigFile(process.cwd()));
1227
+ }
1228
+ }
1229
+
1215
1230
  //------------------------------------------------------------------------------
1216
1231
  // Public Interface
1217
1232
  //------------------------------------------------------------------------------
1218
1233
 
1219
1234
  module.exports = {
1220
1235
  FlatESLint,
1221
- findFlatConfigFile
1236
+ shouldUseFlatConfig
1222
1237
  };
@@ -5,6 +5,16 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Typedefs
10
+ //------------------------------------------------------------------------------
11
+
12
+ /** @typedef {import("../shared/types").LintMessage} LintMessage */
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Module Definition
16
+ //------------------------------------------------------------------------------
17
+
8
18
  const escapeRegExp = require("escape-string-regexp");
9
19
 
10
20
  /**
@@ -196,7 +206,7 @@ function processUnusedDisableDirectives(allDirectives) {
196
206
  * @param {Object} options options for applying directives. This is the same as the options
197
207
  * for the exported function, except that `reportUnusedDisableDirectives` is not supported
198
208
  * (this function always reports unused disable directives).
199
- * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
209
+ * @returns {{problems: LintMessage[], unusedDisableDirectives: LintMessage[]}} An object with a list
200
210
  * of problems (including suppressed ones) and unused eslint-disable directives
201
211
  */
202
212
  function applyDirectives(options) {
@@ -109,7 +109,7 @@ module.exports = {
109
109
  text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
110
110
  }
111
111
  if (codePath.thrownSegments.length > 0) {
112
- text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize];\n";
112
+ text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n";
113
113
  }
114
114
 
115
115
  const traceMap = Object.create(null);
@@ -19,6 +19,12 @@ const levn = require("levn"),
19
19
 
20
20
  const debug = require("debug")("eslint:config-comment-parser");
21
21
 
22
+ //------------------------------------------------------------------------------
23
+ // Typedefs
24
+ //------------------------------------------------------------------------------
25
+
26
+ /** @typedef {import("../shared/types").LintMessage} LintMessage */
27
+
22
28
  //------------------------------------------------------------------------------
23
29
  // Public Interface
24
30
  //------------------------------------------------------------------------------
@@ -61,7 +67,7 @@ module.exports = class ConfigCommentParser {
61
67
  * Parses a JSON-like config.
62
68
  * @param {string} string The string to parse.
63
69
  * @param {Object} location Start line and column of comments for potential error message.
64
- * @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object
70
+ * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object
65
71
  */
66
72
  parseJsonConfig(string, location) {
67
73
  debug("Parsing JSON config");
@@ -109,7 +115,8 @@ module.exports = class ConfigCommentParser {
109
115
  severity: 2,
110
116
  message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
111
117
  line: location.start.line,
112
- column: location.start.column + 1
118
+ column: location.start.column + 1,
119
+ nodeType: null
113
120
  }
114
121
  };
115
122
 
@@ -364,7 +364,7 @@ function extractDirectiveComment(value) {
364
364
  * @param {ASTNode} ast The top node of the AST.
365
365
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
366
366
  * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
367
- * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
367
+ * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}}
368
368
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
369
369
  */
370
370
  function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
@@ -775,7 +775,7 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
775
775
  * @param {string} text The text to parse.
776
776
  * @param {LanguageOptions} languageOptions Options to pass to the parser
777
777
  * @param {string} filePath The path to the file being parsed.
778
- * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
778
+ * @returns {{success: false, error: LintMessage}|{success: true, sourceCode: SourceCode}}
779
779
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
780
780
  * @private
781
781
  */
@@ -851,7 +851,8 @@ function parse(text, languageOptions, filePath) {
851
851
  severity: 2,
852
852
  message,
853
853
  line: ex.lineNumber,
854
- column: ex.column
854
+ column: ex.column,
855
+ nodeType: null
855
856
  }
856
857
  };
857
858
  }
@@ -921,7 +922,7 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
921
922
  * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
922
923
  * @param {string | undefined} cwd cwd of the cli
923
924
  * @param {string} physicalFilename The full path of the file on disk without any code block information
924
- * @returns {Problem[]} An array of reported problems
925
+ * @returns {LintMessage[]} An array of reported problems
925
926
  */
926
927
  function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
927
928
  const emitter = createEmitter();
@@ -1253,7 +1254,8 @@ class Linter {
1253
1254
  severity: 2,
1254
1255
  message: `Configured parser '${config.parser}' was not found.`,
1255
1256
  line: 0,
1256
- column: 0
1257
+ column: 0,
1258
+ nodeType: null
1257
1259
  }];
1258
1260
  }
1259
1261
  parserName = config.parser;
@@ -1464,7 +1466,8 @@ class Linter {
1464
1466
  severity: 2,
1465
1467
  message,
1466
1468
  line: ex.lineNumber,
1467
- column: ex.column
1469
+ column: ex.column,
1470
+ nodeType: null
1468
1471
  }
1469
1472
  ];
1470
1473
  }
@@ -1729,7 +1732,8 @@ class Linter {
1729
1732
  severity: 1,
1730
1733
  message: `No matching configuration found for ${filename}.`,
1731
1734
  line: 0,
1732
- column: 0
1735
+ column: 0,
1736
+ nodeType: null
1733
1737
  }
1734
1738
  ];
1735
1739
  }
@@ -1794,7 +1798,8 @@ class Linter {
1794
1798
  severity: 2,
1795
1799
  message,
1796
1800
  line: ex.lineNumber,
1797
- column: ex.column
1801
+ column: ex.column,
1802
+ nodeType: null
1798
1803
  }
1799
1804
  ];
1800
1805
  }
@@ -1840,7 +1845,7 @@ class Linter {
1840
1845
  /**
1841
1846
  * Given a list of reported problems, distinguish problems between normal messages and suppressed messages.
1842
1847
  * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages.
1843
- * @param {Problem[]} problems A list of reported problems.
1848
+ * @param {Array<LintMessage|SuppressedLintMessage>} problems A list of reported problems.
1844
1849
  * @returns {LintMessage[]} A list of LintMessage.
1845
1850
  */
1846
1851
  _distinguishSuppressedMessages(problems) {
@@ -17,6 +17,8 @@ const interpolate = require("./interpolate");
17
17
  // Typedefs
18
18
  //------------------------------------------------------------------------------
19
19
 
20
+ /** @typedef {import("../shared/types").LintMessage} LintMessage */
21
+
20
22
  /**
21
23
  * An error message description
22
24
  * @typedef {Object} MessageDescriptor
@@ -29,23 +31,6 @@ const interpolate = require("./interpolate");
29
31
  * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes.
30
32
  */
31
33
 
32
- /**
33
- * Information about the report
34
- * @typedef {Object} ReportInfo
35
- * @property {string} ruleId The rule ID
36
- * @property {(0|1|2)} severity Severity of the error
37
- * @property {(string|undefined)} message The message
38
- * @property {(string|undefined)} [messageId] The message ID
39
- * @property {number} line The line number
40
- * @property {number} column The column number
41
- * @property {(number|undefined)} [endLine] The ending line number
42
- * @property {(number|undefined)} [endColumn] The ending column number
43
- * @property {(string|null)} nodeType Type of node
44
- * @property {string} source Source text
45
- * @property {({text: string, range: (number[]|null)}|null)} [fix] The fix object
46
- * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] Suggestion info
47
- */
48
-
49
34
  //------------------------------------------------------------------------------
50
35
  // Module Definition
51
36
  //------------------------------------------------------------------------------
@@ -239,7 +224,7 @@ function mapSuggestions(descriptor, sourceCode, messages) {
239
224
  * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
240
225
  * @param {{text: string, range: (number[]|null)}} options.fix The fix object
241
226
  * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects
242
- * @returns {function(...args): ReportInfo} Function that returns information about the report
227
+ * @returns {LintMessage} Information about the report
243
228
  */
244
229
  function createProblem(options) {
245
230
  const problem = {
@@ -314,7 +299,7 @@ function validateSuggestions(suggest, messages) {
314
299
  * problem for the Node.js API.
315
300
  * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem
316
301
  * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted
317
- * @returns {function(...args): ReportInfo} Function that returns information about the report
302
+ * @returns {function(...args): LintMessage} Function that returns information about the report
318
303
  */
319
304
 
320
305
  module.exports = function createReportTranslator(metadata) {
@@ -12,8 +12,6 @@
12
12
  // Requirements
13
13
  //------------------------------------------------------------------------------
14
14
 
15
- const { OrderedMap } = require("js-sdsl");
16
-
17
15
  const astUtils = require("./utils/ast-utils");
18
16
 
19
17
  //------------------------------------------------------------------------------
@@ -125,43 +123,48 @@ const KNOWN_NODES = new Set([
125
123
 
126
124
 
127
125
  /**
128
- * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique.
129
- * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation
126
+ * A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique.
127
+ * This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation
130
128
  * can easily be swapped out.
131
129
  */
132
- class BinarySearchTree {
130
+ class IndexMap {
133
131
 
134
132
  /**
135
- * Creates an empty tree
133
+ * Creates an empty map
134
+ * @param {number} maxKey The maximum key
136
135
  */
137
- constructor() {
138
- this._orderedMap = new OrderedMap();
139
- this._orderedMapEnd = this._orderedMap.end();
136
+ constructor(maxKey) {
137
+
138
+ // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance.
139
+ this._values = Array(maxKey + 1);
140
140
  }
141
141
 
142
142
  /**
143
- * Inserts an entry into the tree.
143
+ * Inserts an entry into the map.
144
144
  * @param {number} key The entry's key
145
145
  * @param {any} value The entry's value
146
146
  * @returns {void}
147
147
  */
148
148
  insert(key, value) {
149
- this._orderedMap.setElement(key, value);
149
+ this._values[key] = value;
150
150
  }
151
151
 
152
152
  /**
153
- * Finds the entry with the largest key less than or equal to the provided key
153
+ * Finds the value of the entry with the largest key less than or equal to the provided key
154
154
  * @param {number} key The provided key
155
- * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
155
+ * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists.
156
156
  */
157
- findLe(key) {
158
- const iterator = this._orderedMap.reverseLowerBound(key);
157
+ findLastNotAfter(key) {
158
+ const values = this._values;
159
159
 
160
- if (iterator.equals(this._orderedMapEnd)) {
161
- return {};
162
- }
160
+ for (let index = key; index >= 0; index--) {
161
+ const value = values[index];
163
162
 
164
- return { key: iterator.pointer[0], value: iterator.pointer[1] };
163
+ if (value) {
164
+ return value;
165
+ }
166
+ }
167
+ return void 0;
165
168
  }
166
169
 
167
170
  /**
@@ -171,26 +174,7 @@ class BinarySearchTree {
171
174
  * @returns {void}
172
175
  */
173
176
  deleteRange(start, end) {
174
-
175
- // Exit without traversing the tree if the range has zero size.
176
- if (start === end) {
177
- return;
178
- }
179
- const iterator = this._orderedMap.lowerBound(start);
180
-
181
- if (iterator.equals(this._orderedMapEnd)) {
182
- return;
183
- }
184
-
185
- if (end > this._orderedMap.back()[0]) {
186
- while (!iterator.equals(this._orderedMapEnd)) {
187
- this._orderedMap.eraseElementByIterator(iterator);
188
- }
189
- } else {
190
- while (iterator.pointer[0] < end) {
191
- this._orderedMap.eraseElementByIterator(iterator);
192
- }
193
- }
177
+ this._values.fill(void 0, start, end);
194
178
  }
195
179
  }
196
180
 
@@ -252,14 +236,15 @@ class OffsetStorage {
252
236
  * @param {TokenInfo} tokenInfo a TokenInfo instance
253
237
  * @param {number} indentSize The desired size of each indentation level
254
238
  * @param {string} indentType The indentation character
239
+ * @param {number} maxIndex The maximum end index of any token
255
240
  */
256
- constructor(tokenInfo, indentSize, indentType) {
241
+ constructor(tokenInfo, indentSize, indentType, maxIndex) {
257
242
  this._tokenInfo = tokenInfo;
258
243
  this._indentSize = indentSize;
259
244
  this._indentType = indentType;
260
245
 
261
- this._tree = new BinarySearchTree();
262
- this._tree.insert(0, { offset: 0, from: null, force: false });
246
+ this._indexMap = new IndexMap(maxIndex);
247
+ this._indexMap.insert(0, { offset: 0, from: null, force: false });
263
248
 
264
249
  this._lockedFirstTokens = new WeakMap();
265
250
  this._desiredIndentCache = new WeakMap();
@@ -267,7 +252,7 @@ class OffsetStorage {
267
252
  }
268
253
 
269
254
  _getOffsetDescriptor(token) {
270
- return this._tree.findLe(token.range[0]).value;
255
+ return this._indexMap.findLastNotAfter(token.range[0]);
271
256
  }
272
257
 
273
258
  /**
@@ -388,37 +373,36 @@ class OffsetStorage {
388
373
  * * key: 820, value: { offset: 1, from: bazToken }
389
374
  *
390
375
  * To find the offset descriptor for any given token, one needs to find the node with the largest key
391
- * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary
392
- * search tree indexed by key.
376
+ * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key.
393
377
  */
394
378
 
395
379
  const descriptorToInsert = { offset, from: fromToken, force };
396
380
 
397
- const descriptorAfterRange = this._tree.findLe(range[1]).value;
381
+ const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]);
398
382
 
399
383
  const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1];
400
384
  const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken);
401
385
 
402
- // First, remove any existing nodes in the range from the tree.
403
- this._tree.deleteRange(range[0] + 1, range[1]);
386
+ // First, remove any existing nodes in the range from the map.
387
+ this._indexMap.deleteRange(range[0] + 1, range[1]);
404
388
 
405
- // Insert a new node into the tree for this range
406
- this._tree.insert(range[0], descriptorToInsert);
389
+ // Insert a new node into the map for this range
390
+ this._indexMap.insert(range[0], descriptorToInsert);
407
391
 
408
392
  /*
409
393
  * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
410
394
  * even if it's in the current range.
411
395
  */
412
396
  if (fromTokenIsInRange) {
413
- this._tree.insert(fromToken.range[0], fromTokenDescriptor);
414
- this._tree.insert(fromToken.range[1], descriptorToInsert);
397
+ this._indexMap.insert(fromToken.range[0], fromTokenDescriptor);
398
+ this._indexMap.insert(fromToken.range[1], descriptorToInsert);
415
399
  }
416
400
 
417
401
  /*
418
402
  * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
419
403
  * tokens the same as it was before.
420
404
  */
421
- this._tree.insert(range[1], descriptorAfterRange);
405
+ this._indexMap.insert(range[1], descriptorAfterRange);
422
406
  }
423
407
 
424
408
  /**
@@ -705,7 +689,7 @@ module.exports = {
705
689
 
706
690
  const sourceCode = context.sourceCode;
707
691
  const tokenInfo = new TokenInfo(sourceCode);
708
- const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t");
692
+ const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t", sourceCode.text.length);
709
693
  const parameterParens = new WeakSet();
710
694
 
711
695
  /**
@@ -100,12 +100,12 @@ module.exports = {
100
100
  }
101
101
 
102
102
  /**
103
- * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
103
+ * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
104
104
  * @param {ASTNode} node to check for matching errors.
105
105
  * @returns {void}
106
106
  * @private
107
107
  */
108
- function removeInvalidNodeErrorsInIdentifierOrLiteral(node) {
108
+ function removeInvalidNodeErrorsInLiteral(node) {
109
109
  const shouldCheckStrings = skipStrings && (typeof node.value === "string");
110
110
  const shouldCheckRegExps = skipRegExps && Boolean(node.regex);
111
111
 
@@ -237,8 +237,7 @@ module.exports = {
237
237
  checkForIrregularLineTerminators(node);
238
238
  };
239
239
 
240
- nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
241
- nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
240
+ nodes.Literal = removeInvalidNodeErrorsInLiteral;
242
241
  nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
243
242
  nodes["Program:exit"] = function() {
244
243
  if (skipComments) {
@@ -82,7 +82,6 @@ module.exports = {
82
82
 
83
83
  create(context) {
84
84
  const segmentInfoMap = new WeakMap();
85
- const usedUnreachableSegments = new WeakSet();
86
85
  const sourceCode = context.sourceCode;
87
86
  let scopeInfo = null;
88
87
 
@@ -152,24 +151,44 @@ module.exports = {
152
151
  * This behavior would simulate code paths for the case that the return
153
152
  * statement does not exist.
154
153
  * @param {CodePathSegment} segment The segment to get return statements.
154
+ * @param {Set<CodePathSegment>} usedUnreachableSegments A set of segments that have already been traversed in this call.
155
155
  * @returns {void}
156
156
  */
157
- function markReturnStatementsOnSegmentAsUsed(segment) {
157
+ function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) {
158
158
  if (!segment.reachable) {
159
159
  usedUnreachableSegments.add(segment);
160
160
  segment.allPrevSegments
161
161
  .filter(isReturned)
162
162
  .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
163
- .forEach(markReturnStatementsOnSegmentAsUsed);
163
+ .forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments));
164
164
  return;
165
165
  }
166
166
 
167
167
  const info = segmentInfoMap.get(segment);
168
168
 
169
- for (const node of info.uselessReturns) {
169
+ info.uselessReturns = info.uselessReturns.filter(node => {
170
+ if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) {
171
+ const returnInitialRange = node.range[0];
172
+ const returnFinalRange = node.range[1];
173
+
174
+ const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => {
175
+ const blockInitialRange = tryBlockStatement.range[0];
176
+ const blockFinalRange = tryBlockStatement.range[1];
177
+
178
+ return (
179
+ returnInitialRange >= blockInitialRange &&
180
+ returnFinalRange <= blockFinalRange
181
+ );
182
+ });
183
+
184
+ if (areBlocksInRange) {
185
+ return true;
186
+ }
187
+ }
188
+
170
189
  remove(scopeInfo.uselessReturns, node);
171
- }
172
- info.uselessReturns = [];
190
+ return false;
191
+ });
173
192
  }
174
193
 
175
194
  /**
@@ -188,7 +207,7 @@ module.exports = {
188
207
  scopeInfo
189
208
  .codePath
190
209
  .currentSegments
191
- .forEach(markReturnStatementsOnSegmentAsUsed);
210
+ .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
192
211
  }
193
212
 
194
213
  //----------------------------------------------------------------------
@@ -202,6 +221,7 @@ module.exports = {
202
221
  scopeInfo = {
203
222
  upper: scopeInfo,
204
223
  uselessReturns: [],
224
+ traversedTryBlockStatements: [],
205
225
  codePath
206
226
  };
207
227
  },
@@ -275,6 +295,14 @@ module.exports = {
275
295
  scopeInfo.uselessReturns.push(node);
276
296
  },
277
297
 
298
+ "TryStatement > BlockStatement.block:exit"(node) {
299
+ scopeInfo.traversedTryBlockStatements.push(node);
300
+ },
301
+
302
+ "TryStatement:exit"() {
303
+ scopeInfo.traversedTryBlockStatements.pop();
304
+ },
305
+
278
306
  /*
279
307
  * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
280
308
  * Removes return statements of the current segments from the useless return statement list.
@@ -9,7 +9,7 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const GraphemeSplitter = require("grapheme-splitter");
12
+ const Graphemer = require("graphemer").default;
13
13
 
14
14
  //------------------------------------------------------------------------------
15
15
  // Helpers
@@ -18,7 +18,7 @@ const GraphemeSplitter = require("grapheme-splitter");
18
18
  // eslint-disable-next-line no-control-regex -- intentionally including control characters
19
19
  const ASCII_REGEX = /^[\u0000-\u007f]*$/u;
20
20
 
21
- /** @type {GraphemeSplitter | undefined} */
21
+ /** @type {Graphemer | undefined} */
22
22
  let splitter;
23
23
 
24
24
  //------------------------------------------------------------------------------
@@ -48,7 +48,7 @@ function getGraphemeCount(value) {
48
48
  }
49
49
 
50
50
  if (!splitter) {
51
- splitter = new GraphemeSplitter();
51
+ splitter = new Graphemer();
52
52
  }
53
53
 
54
54
  return splitter.countGraphemes(value);
@@ -96,10 +96,12 @@ module.exports = {};
96
96
  * @property {number|undefined} column The 1-based column number.
97
97
  * @property {number} [endColumn] The 1-based column number of the end location.
98
98
  * @property {number} [endLine] The 1-based line number of the end location.
99
- * @property {boolean} fatal If `true` then this is a fatal error.
99
+ * @property {boolean} [fatal] If `true` then this is a fatal error.
100
100
  * @property {{range:[number,number], text:string}} [fix] Information for autofix.
101
101
  * @property {number|undefined} line The 1-based line number.
102
102
  * @property {string} message The error message.
103
+ * @property {string} [messageId] The ID of the message in the rule's meta.
104
+ * @property {(string|null)} nodeType Type of node
103
105
  * @property {string|null} ruleId The ID of the rule which makes this message.
104
106
  * @property {0|1|2} severity The severity of this message.
105
107
  * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
@@ -110,10 +112,12 @@ module.exports = {};
110
112
  * @property {number|undefined} column The 1-based column number.
111
113
  * @property {number} [endColumn] The 1-based column number of the end location.
112
114
  * @property {number} [endLine] The 1-based line number of the end location.
113
- * @property {boolean} fatal If `true` then this is a fatal error.
115
+ * @property {boolean} [fatal] If `true` then this is a fatal error.
114
116
  * @property {{range:[number,number], text:string}} [fix] Information for autofix.
115
117
  * @property {number|undefined} line The 1-based line number.
116
118
  * @property {string} message The error message.
119
+ * @property {string} [messageId] The ID of the message in the rule's meta.
120
+ * @property {(string|null)} nodeType Type of node
117
121
  * @property {string|null} ruleId The ID of the rule which makes this message.
118
122
  * @property {0|1|2} severity The severity of this message.
119
123
  * @property {Array<{kind: string, justification: string}>} suppressions The suppression info.
@@ -12,7 +12,7 @@
12
12
  //-----------------------------------------------------------------------------
13
13
 
14
14
  const { FileEnumerator } = require("./cli-engine/file-enumerator");
15
- const { FlatESLint } = require("./eslint/flat-eslint");
15
+ const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint");
16
16
  const FlatRuleTester = require("./rule-tester/flat-rule-tester");
17
17
 
18
18
  //-----------------------------------------------------------------------------
@@ -22,6 +22,7 @@ const FlatRuleTester = require("./rule-tester/flat-rule-tester");
22
22
  module.exports = {
23
23
  builtinRules: require("./rules"),
24
24
  FlatESLint,
25
+ shouldUseFlatConfig,
25
26
  FlatRuleTester,
26
27
  FileEnumerator
27
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.40.0",
3
+ "version": "8.42.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -63,8 +63,8 @@
63
63
  "@eslint-community/eslint-utils": "^4.2.0",
64
64
  "@eslint-community/regexpp": "^4.4.0",
65
65
  "@eslint/eslintrc": "^2.0.3",
66
- "@eslint/js": "8.40.0",
67
- "@humanwhocodes/config-array": "^0.11.8",
66
+ "@eslint/js": "8.42.0",
67
+ "@humanwhocodes/config-array": "^0.11.10",
68
68
  "@humanwhocodes/module-importer": "^1.0.1",
69
69
  "@nodelib/fs.walk": "^1.2.8",
70
70
  "ajv": "^6.10.0",
@@ -83,13 +83,12 @@
83
83
  "find-up": "^5.0.0",
84
84
  "glob-parent": "^6.0.2",
85
85
  "globals": "^13.19.0",
86
- "grapheme-splitter": "^1.0.4",
86
+ "graphemer": "^1.4.0",
87
87
  "ignore": "^5.2.0",
88
88
  "import-fresh": "^3.0.0",
89
89
  "imurmurhash": "^0.1.4",
90
90
  "is-glob": "^4.0.0",
91
91
  "is-path-inside": "^3.0.3",
92
- "js-sdsl": "^4.1.4",
93
92
  "js-yaml": "^4.1.0",
94
93
  "json-stable-stringify-without-jsonify": "^1.0.1",
95
94
  "levn": "^0.4.1",