eslint 8.7.0 → 8.10.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
@@ -48,19 +48,19 @@ Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.
48
48
  You can install ESLint using npm:
49
49
 
50
50
  ```sh
51
- $ npm install eslint --save-dev
51
+ npm install eslint --save-dev
52
52
  ```
53
53
 
54
54
  You should then set up a configuration file:
55
55
 
56
56
  ```sh
57
- $ npm init @eslint/config
57
+ npm init @eslint/config
58
58
  ```
59
59
 
60
60
  After that, you can run ESLint on any file or directory like this:
61
61
 
62
62
  ```sh
63
- $ ./node_modules/.bin/eslint yourfile.js
63
+ ./node_modules/.bin/eslint yourfile.js
64
64
  ```
65
65
 
66
66
  ## <a name="configuration"></a>Configuration
@@ -129,7 +129,7 @@ ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2
129
129
 
130
130
  ESLint's parser only officially supports the latest final ECMAScript standard. We will make changes to core rules in order to avoid crashes on stage 3 ECMAScript syntax proposals (as long as they are implemented using the correct experimental ESTree syntax). We may make changes to core rules to better work with language extensions (such as JSX, Flow, and TypeScript) on a case-by-case basis.
131
131
 
132
- In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use the [babel-eslint](https://github.com/babel/babel-eslint) parser and [eslint-plugin-babel](https://github.com/babel/eslint-plugin-babel) to use any option available in Babel.
132
+ In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) and [@babel/eslint-plugin](https://www.npmjs.com/package/@babel/eslint-plugin) to use any option available in Babel.
133
133
 
134
134
  Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature.
135
135
 
@@ -292,9 +292,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
292
292
  <!--sponsorsstart-->
293
293
  <h3>Platinum Sponsors</h3>
294
294
  <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>
295
- <p><a href="https://contra.com"><img src="https://images.opencollective.com/contra1/c70f93f/logo.png" alt="Contra" height="96"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.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> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
295
+ <p><a href="https://contra.com"><img src="https://images.opencollective.com/contra1/c70f93f/logo.png" alt="Contra" height="96"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.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> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
296
296
  <p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
297
- <p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" 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://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" 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://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
297
+ <p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" 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://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" 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://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
298
298
  <!--sponsorsend-->
299
299
 
300
300
  ## <a name="technology-sponsors"></a>Technology Sponsors
@@ -51,6 +51,7 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
51
51
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
52
52
  /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
53
53
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
54
+ /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
54
55
  /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
55
56
  /** @typedef {import("../shared/types").Plugin} Plugin */
56
57
  /** @typedef {import("../shared/types").RuleConf} RuleConf */
@@ -91,6 +92,7 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
91
92
  * @typedef {Object} LintResult
92
93
  * @property {string} filePath The path to the file that was linted.
93
94
  * @property {LintMessage[]} messages All of the messages for the result.
95
+ * @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
94
96
  * @property {number} errorCount Number of errors for the result.
95
97
  * @property {number} fatalErrorCount Number of fatal errors for the result.
96
98
  * @property {number} warningCount Number of warnings for the result.
@@ -263,6 +265,7 @@ function verifyText({
263
265
  const result = {
264
266
  filePath,
265
267
  messages,
268
+ suppressedMessages: linter.getSuppressedMessages(),
266
269
  ...calculateStatsPerFile(messages)
267
270
  };
268
271
 
@@ -309,6 +312,7 @@ function createIgnoreResult(filePath, baseDir) {
309
312
  message
310
313
  }
311
314
  ],
315
+ suppressedMessages: [],
312
316
  errorCount: 0,
313
317
  fatalErrorCount: 0,
314
318
  warningCount: 1,
@@ -612,8 +616,8 @@ class CLIEngine {
612
616
  useEslintrc: options.useEslintrc,
613
617
  builtInRules,
614
618
  loadRules,
615
- eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
616
- eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
619
+ getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"),
620
+ getEslintAllConfig: () => require("../../conf/eslint-all.js")
617
621
  });
618
622
  const fileEnumerator = new FileEnumerator({
619
623
  configArrayFactory,
@@ -683,11 +687,13 @@ class CLIEngine {
683
687
 
684
688
  results.forEach(result => {
685
689
  const filteredMessages = result.messages.filter(isErrorMessage);
690
+ const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage);
686
691
 
687
692
  if (filteredMessages.length > 0) {
688
693
  filtered.push({
689
694
  ...result,
690
695
  messages: filteredMessages,
696
+ suppressedMessages: filteredSuppressedMessages,
691
697
  errorCount: filteredMessages.length,
692
698
  warningCount: 0,
693
699
  fixableErrorCount: result.fixableErrorCount,
@@ -215,8 +215,8 @@ class FileEnumerator {
215
215
  cwd = process.cwd(),
216
216
  configArrayFactory = new CascadingConfigArrayFactory({
217
217
  cwd,
218
- eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
219
- eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
218
+ getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"),
219
+ getEslintAllConfig: () => require("../../conf/eslint-all.js")
220
220
  }),
221
221
  extensions = null,
222
222
  globInputPaths = true,
@@ -14,7 +14,6 @@ const { flatConfigSchema } = require("./flat-config-schema");
14
14
  const { RuleValidator } = require("./rule-validator");
15
15
  const { defaultConfig } = require("./default-config");
16
16
  const recommendedConfig = require("../../conf/eslint-recommended");
17
- const allConfig = require("../../conf/eslint-all");
18
17
 
19
18
  //-----------------------------------------------------------------------------
20
19
  // Helpers
@@ -58,7 +57,11 @@ class FlatConfigArray extends ConfigArray {
58
57
  schema: flatConfigSchema
59
58
  });
60
59
 
61
- this.unshift(...baseConfig);
60
+ if (baseConfig[Symbol.iterator]) {
61
+ this.unshift(...baseConfig);
62
+ } else {
63
+ this.unshift(baseConfig);
64
+ }
62
65
  }
63
66
 
64
67
  /* eslint-disable class-methods-use-this -- Desired as instance method */
@@ -75,7 +78,13 @@ class FlatConfigArray extends ConfigArray {
75
78
  }
76
79
 
77
80
  if (config === "eslint:all") {
78
- return allConfig;
81
+
82
+ /*
83
+ * Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules
84
+ * when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones,
85
+ * so requiring `eslint-all.js` module loads all rule modules as a consequence.
86
+ */
87
+ return require("../../conf/eslint-all");
79
88
  }
80
89
 
81
90
  return config;
@@ -57,11 +57,47 @@ function getRuleFromConfig(ruleId, config) {
57
57
  return rule;
58
58
  }
59
59
 
60
+ /**
61
+ * Gets a complete options schema for a rule.
62
+ * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
63
+ * @returns {Object} JSON Schema for the rule's options.
64
+ */
65
+ function getRuleOptionsSchema(rule) {
66
+
67
+ if (!rule) {
68
+ return null;
69
+ }
70
+
71
+ const schema = rule.schema || rule.meta && rule.meta.schema;
72
+
73
+ if (Array.isArray(schema)) {
74
+ if (schema.length) {
75
+ return {
76
+ type: "array",
77
+ items: schema,
78
+ minItems: 0,
79
+ maxItems: schema.length
80
+ };
81
+ }
82
+ return {
83
+ type: "array",
84
+ minItems: 0,
85
+ maxItems: 0
86
+ };
87
+
88
+ }
89
+
90
+ // Given a full schema, leave it alone
91
+ return schema || null;
92
+ }
93
+
94
+
60
95
  //-----------------------------------------------------------------------------
61
96
  // Exports
62
97
  //-----------------------------------------------------------------------------
63
98
 
64
99
  module.exports = {
65
100
  parseRuleId,
66
- getRuleFromConfig
101
+ getRuleFromConfig,
102
+ getRuleOptionsSchema
67
103
  };
@@ -10,7 +10,11 @@
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
12
  const ajv = require("../shared/ajv")();
13
- const { parseRuleId, getRuleFromConfig } = require("./flat-config-helpers");
13
+ const {
14
+ parseRuleId,
15
+ getRuleFromConfig,
16
+ getRuleOptionsSchema
17
+ } = require("./flat-config-helpers");
14
18
  const ruleReplacements = require("../../conf/replacements.json");
15
19
 
16
20
  //-----------------------------------------------------------------------------
@@ -61,40 +65,6 @@ function throwRuleNotFoundError({ pluginName, ruleName }, config) {
61
65
  throw new TypeError(errorMessage);
62
66
  }
63
67
 
64
- /**
65
- * Gets a complete options schema for a rule.
66
- * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
67
- * @returns {Object} JSON Schema for the rule's options.
68
- */
69
- function getRuleOptionsSchema(rule) {
70
-
71
- if (!rule) {
72
- return null;
73
- }
74
-
75
- const schema = rule.schema || rule.meta && rule.meta.schema;
76
-
77
- if (Array.isArray(schema)) {
78
- if (schema.length) {
79
- return {
80
- type: "array",
81
- items: schema,
82
- minItems: 0,
83
- maxItems: schema.length
84
- };
85
- }
86
- return {
87
- type: "array",
88
- minItems: 0,
89
- maxItems: 0
90
- };
91
-
92
- }
93
-
94
- // Given a full schema, leave it alone
95
- return schema || null;
96
- }
97
-
98
68
  //-----------------------------------------------------------------------------
99
69
  // Exports
100
70
  //-----------------------------------------------------------------------------
@@ -32,6 +32,7 @@ const { version } = require("../../package.json");
32
32
  /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
33
33
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
34
34
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
35
+ /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
35
36
  /** @typedef {import("../shared/types").Plugin} Plugin */
36
37
  /** @typedef {import("../shared/types").Rule} Rule */
37
38
 
@@ -78,6 +79,7 @@ const { version } = require("../../package.json");
78
79
  * @typedef {Object} LintResult
79
80
  * @property {string} filePath The path to the file that was linted.
80
81
  * @property {LintMessage[]} messages All of the messages for the result.
82
+ * @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
81
83
  * @property {number} errorCount Number of errors for the result.
82
84
  * @property {number} fatalErrorCount Number of fatal errors for the result.
83
85
  * @property {number} warningCount Number of warnings for the result.
@@ -526,6 +528,9 @@ class ESLint {
526
528
  for (const { ruleId } of result.messages) {
527
529
  resultRuleIds.add(ruleId);
528
530
  }
531
+ for (const { ruleId } of result.suppressedMessages) {
532
+ resultRuleIds.add(ruleId);
533
+ }
529
534
  }
530
535
 
531
536
  // create a map of all rules in the results
@@ -197,62 +197,52 @@ function processUnusedDisableDirectives(allDirectives) {
197
197
  * for the exported function, except that `reportUnusedDisableDirectives` is not supported
198
198
  * (this function always reports unused disable directives).
199
199
  * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
200
- * of filtered problems and unused eslint-disable directives
200
+ * of problems (including suppressed ones) and unused eslint-disable directives
201
201
  */
202
202
  function applyDirectives(options) {
203
203
  const problems = [];
204
- let nextDirectiveIndex = 0;
205
- let currentGlobalDisableDirective = null;
206
- const disabledRuleMap = new Map();
207
-
208
- // enabledRules is only used when there is a current global disable directive.
209
- const enabledRules = new Set();
210
204
  const usedDisableDirectives = new Set();
211
205
 
212
206
  for (const problem of options.problems) {
207
+ let disableDirectivesForProblem = [];
208
+ let nextDirectiveIndex = 0;
209
+
213
210
  while (
214
211
  nextDirectiveIndex < options.directives.length &&
215
212
  compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
216
213
  ) {
217
214
  const directive = options.directives[nextDirectiveIndex++];
218
215
 
219
- switch (directive.type) {
220
- case "disable":
221
- if (directive.ruleId === null) {
222
- currentGlobalDisableDirective = directive;
223
- disabledRuleMap.clear();
224
- enabledRules.clear();
225
- } else if (currentGlobalDisableDirective) {
226
- enabledRules.delete(directive.ruleId);
227
- disabledRuleMap.set(directive.ruleId, directive);
228
- } else {
229
- disabledRuleMap.set(directive.ruleId, directive);
230
- }
231
- break;
232
-
233
- case "enable":
234
- if (directive.ruleId === null) {
235
- currentGlobalDisableDirective = null;
236
- disabledRuleMap.clear();
237
- } else if (currentGlobalDisableDirective) {
238
- enabledRules.add(directive.ruleId);
239
- disabledRuleMap.delete(directive.ruleId);
240
- } else {
241
- disabledRuleMap.delete(directive.ruleId);
242
- }
243
- break;
244
-
245
- // no default
216
+ if (directive.ruleId === null || directive.ruleId === problem.ruleId) {
217
+ switch (directive.type) {
218
+ case "disable":
219
+ disableDirectivesForProblem.push(directive);
220
+ break;
221
+
222
+ case "enable":
223
+ disableDirectivesForProblem = [];
224
+ break;
225
+
226
+ // no default
227
+ }
246
228
  }
247
229
  }
248
230
 
249
- if (disabledRuleMap.has(problem.ruleId)) {
250
- usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId));
251
- } else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) {
252
- usedDisableDirectives.add(currentGlobalDisableDirective);
253
- } else {
254
- problems.push(problem);
231
+ if (disableDirectivesForProblem.length > 0) {
232
+ const suppressions = disableDirectivesForProblem.map(directive => ({
233
+ kind: "directive",
234
+ justification: directive.unprocessedDirective.justification
235
+ }));
236
+
237
+ if (problem.suppressions) {
238
+ problem.suppressions = problem.suppressions.concat(suppressions);
239
+ } else {
240
+ problem.suppressions = suppressions;
241
+ usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]);
242
+ }
255
243
  }
244
+
245
+ problems.push(problem);
256
246
  }
257
247
 
258
248
  const unusedDisableDirectivesToReport = options.directives
@@ -282,13 +272,14 @@ function applyDirectives(options) {
282
272
 
283
273
  /**
284
274
  * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
285
- * of reported problems, determines which problems should be reported.
275
+ * of reported problems, adds the suppression information to the problems.
286
276
  * @param {Object} options Information about directives and problems
287
277
  * @param {{
288
278
  * type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
289
279
  * ruleId: (string|null),
290
280
  * line: number,
291
- * column: number
281
+ * column: number,
282
+ * justification: string
292
283
  * }} options.directives Directive comments found in the file, with one-based columns.
293
284
  * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
294
285
  * comment for two different rules is represented as two directives).
@@ -296,8 +287,8 @@ function applyDirectives(options) {
296
287
  * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
297
288
  * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
298
289
  * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
299
- * @returns {{ruleId: (string|null), line: number, column: number}[]}
300
- * A list of reported problems that were not disabled by the directive comments.
290
+ * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
291
+ * An object with a list of reported problems, the suppressed of which contain the suppression information.
301
292
  */
302
293
  module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => {
303
294
  const blockDirectives = directives