eslint 8.57.0 → 9.2.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 (156) hide show
  1. package/README.md +31 -28
  2. package/bin/eslint.js +4 -3
  3. package/conf/ecma-version.js +16 -0
  4. package/conf/globals.js +1 -0
  5. package/conf/rule-type-list.json +3 -1
  6. package/lib/api.js +7 -11
  7. package/lib/cli-engine/cli-engine.js +14 -3
  8. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  9. package/lib/cli-engine/lint-result-cache.js +2 -2
  10. package/lib/cli.js +115 -36
  11. package/lib/config/default-config.js +3 -0
  12. package/lib/config/flat-config-array.js +110 -24
  13. package/lib/config/flat-config-helpers.js +41 -20
  14. package/lib/config/flat-config-schema.js +1 -7
  15. package/lib/config/rule-validator.js +42 -6
  16. package/lib/eslint/eslint-helpers.js +116 -58
  17. package/lib/eslint/eslint.js +892 -377
  18. package/lib/eslint/index.js +2 -2
  19. package/lib/eslint/legacy-eslint.js +728 -0
  20. package/lib/linter/apply-disable-directives.js +59 -31
  21. package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
  22. package/lib/linter/code-path-analysis/code-path.js +32 -30
  23. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  24. package/lib/linter/config-comment-parser.js +8 -11
  25. package/lib/linter/index.js +1 -3
  26. package/lib/linter/interpolate.js +24 -2
  27. package/lib/linter/linter.js +428 -207
  28. package/lib/linter/report-translator.js +3 -3
  29. package/lib/linter/rules.js +6 -15
  30. package/lib/linter/source-code-fixer.js +1 -1
  31. package/lib/linter/timing.js +16 -8
  32. package/lib/options.js +35 -3
  33. package/lib/rule-tester/index.js +3 -1
  34. package/lib/rule-tester/rule-tester.js +424 -347
  35. package/lib/rules/array-bracket-newline.js +1 -1
  36. package/lib/rules/array-bracket-spacing.js +1 -1
  37. package/lib/rules/block-scoped-var.js +1 -1
  38. package/lib/rules/callback-return.js +2 -2
  39. package/lib/rules/camelcase.js +3 -5
  40. package/lib/rules/capitalized-comments.js +10 -7
  41. package/lib/rules/comma-dangle.js +1 -1
  42. package/lib/rules/comma-style.js +2 -2
  43. package/lib/rules/complexity.js +14 -1
  44. package/lib/rules/constructor-super.js +99 -100
  45. package/lib/rules/default-case.js +1 -1
  46. package/lib/rules/eol-last.js +2 -2
  47. package/lib/rules/function-paren-newline.js +2 -2
  48. package/lib/rules/indent-legacy.js +5 -5
  49. package/lib/rules/indent.js +5 -5
  50. package/lib/rules/index.js +1 -2
  51. package/lib/rules/key-spacing.js +2 -2
  52. package/lib/rules/line-comment-position.js +1 -1
  53. package/lib/rules/lines-around-directive.js +2 -2
  54. package/lib/rules/max-depth.js +1 -1
  55. package/lib/rules/max-len.js +3 -3
  56. package/lib/rules/max-lines.js +3 -3
  57. package/lib/rules/max-nested-callbacks.js +1 -1
  58. package/lib/rules/max-params.js +1 -1
  59. package/lib/rules/max-statements.js +1 -1
  60. package/lib/rules/multiline-comment-style.js +7 -7
  61. package/lib/rules/new-cap.js +1 -1
  62. package/lib/rules/newline-after-var.js +1 -1
  63. package/lib/rules/newline-before-return.js +1 -1
  64. package/lib/rules/no-case-declarations.js +13 -1
  65. package/lib/rules/no-constant-binary-expression.js +7 -8
  66. package/lib/rules/no-constant-condition.js +18 -7
  67. package/lib/rules/no-constructor-return.js +2 -2
  68. package/lib/rules/no-dupe-class-members.js +2 -2
  69. package/lib/rules/no-else-return.js +1 -1
  70. package/lib/rules/no-empty-function.js +2 -2
  71. package/lib/rules/no-empty-static-block.js +1 -1
  72. package/lib/rules/no-extend-native.js +1 -2
  73. package/lib/rules/no-extra-semi.js +1 -1
  74. package/lib/rules/no-fallthrough.js +41 -16
  75. package/lib/rules/no-implicit-coercion.js +66 -24
  76. package/lib/rules/no-inner-declarations.js +23 -2
  77. package/lib/rules/no-invalid-regexp.js +1 -1
  78. package/lib/rules/no-invalid-this.js +1 -1
  79. package/lib/rules/no-lone-blocks.js +3 -3
  80. package/lib/rules/no-loss-of-precision.js +1 -1
  81. package/lib/rules/no-misleading-character-class.js +225 -69
  82. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  83. package/lib/rules/no-multiple-empty-lines.js +1 -1
  84. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  85. package/lib/rules/no-new-symbol.js +8 -1
  86. package/lib/rules/no-restricted-globals.js +1 -1
  87. package/lib/rules/no-restricted-imports.js +186 -40
  88. package/lib/rules/no-restricted-modules.js +2 -2
  89. package/lib/rules/no-return-await.js +1 -1
  90. package/lib/rules/no-sequences.js +1 -0
  91. package/lib/rules/no-this-before-super.js +45 -13
  92. package/lib/rules/no-trailing-spaces.js +2 -3
  93. package/lib/rules/no-unneeded-ternary.js +1 -1
  94. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  95. package/lib/rules/no-unused-private-class-members.js +1 -1
  96. package/lib/rules/no-unused-vars.js +197 -36
  97. package/lib/rules/no-useless-assignment.js +566 -0
  98. package/lib/rules/no-useless-backreference.js +1 -1
  99. package/lib/rules/no-useless-computed-key.js +2 -2
  100. package/lib/rules/no-useless-return.js +7 -2
  101. package/lib/rules/object-curly-spacing.js +3 -3
  102. package/lib/rules/object-property-newline.js +1 -1
  103. package/lib/rules/one-var.js +5 -5
  104. package/lib/rules/padded-blocks.js +7 -7
  105. package/lib/rules/prefer-arrow-callback.js +3 -3
  106. package/lib/rules/prefer-reflect.js +1 -1
  107. package/lib/rules/prefer-regex-literals.js +1 -1
  108. package/lib/rules/prefer-template.js +1 -1
  109. package/lib/rules/radix.js +2 -2
  110. package/lib/rules/semi-style.js +1 -1
  111. package/lib/rules/sort-imports.js +1 -1
  112. package/lib/rules/sort-keys.js +1 -1
  113. package/lib/rules/sort-vars.js +1 -1
  114. package/lib/rules/space-unary-ops.js +1 -1
  115. package/lib/rules/strict.js +1 -1
  116. package/lib/rules/use-isnan.js +101 -7
  117. package/lib/rules/utils/ast-utils.js +16 -7
  118. package/lib/rules/utils/char-source.js +240 -0
  119. package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
  120. package/lib/rules/utils/unicode/index.js +9 -4
  121. package/lib/rules/yield-star-spacing.js +1 -1
  122. package/lib/shared/runtime-info.js +1 -0
  123. package/lib/shared/serialization.js +55 -0
  124. package/lib/shared/stats.js +30 -0
  125. package/lib/shared/string-utils.js +9 -11
  126. package/lib/shared/types.js +35 -1
  127. package/lib/source-code/index.js +3 -1
  128. package/lib/source-code/source-code.js +299 -85
  129. package/lib/source-code/token-store/backward-token-cursor.js +3 -3
  130. package/lib/source-code/token-store/cursors.js +4 -2
  131. package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
  132. package/lib/source-code/token-store/forward-token-cursor.js +3 -3
  133. package/lib/source-code/token-store/index.js +2 -2
  134. package/lib/unsupported-api.js +3 -5
  135. package/messages/no-config-found.js +1 -1
  136. package/messages/plugin-conflict.js +1 -1
  137. package/messages/plugin-invalid.js +1 -1
  138. package/messages/plugin-missing.js +1 -1
  139. package/package.json +32 -29
  140. package/conf/config-schema.js +0 -93
  141. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  142. package/lib/cli-engine/formatters/compact.js +0 -60
  143. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  144. package/lib/cli-engine/formatters/junit.js +0 -82
  145. package/lib/cli-engine/formatters/tap.js +0 -95
  146. package/lib/cli-engine/formatters/unix.js +0 -58
  147. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  148. package/lib/cli-engine/xml-escape.js +0 -34
  149. package/lib/eslint/flat-eslint.js +0 -1155
  150. package/lib/rule-tester/flat-rule-tester.js +0 -1131
  151. package/lib/rules/require-jsdoc.js +0 -122
  152. package/lib/rules/utils/patterns/letters.js +0 -36
  153. package/lib/rules/valid-jsdoc.js +0 -516
  154. package/lib/shared/config-validator.js +0 -347
  155. package/lib/shared/deprecation-warnings.js +0 -58
  156. package/lib/shared/relative-module-resolver.js +0 -50
@@ -13,12 +13,16 @@ const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array"
13
13
  const { flatConfigSchema } = require("./flat-config-schema");
14
14
  const { RuleValidator } = require("./rule-validator");
15
15
  const { defaultConfig } = require("./default-config");
16
- const jsPlugin = require("@eslint/js");
17
16
 
18
17
  //-----------------------------------------------------------------------------
19
18
  // Helpers
20
19
  //-----------------------------------------------------------------------------
21
20
 
21
+ /**
22
+ * Fields that are considered metadata and not part of the config object.
23
+ */
24
+ const META_FIELDS = new Set(["name"]);
25
+
22
26
  const ruleValidator = new RuleValidator();
23
27
 
24
28
  /**
@@ -75,7 +79,53 @@ function getObjectId(object) {
75
79
  return name;
76
80
  }
77
81
 
82
+ /**
83
+ * Wraps a config error with details about where the error occurred.
84
+ * @param {Error} error The original error.
85
+ * @param {number} originalLength The original length of the config array.
86
+ * @param {number} baseLength The length of the base config.
87
+ * @returns {TypeError} The new error with details.
88
+ */
89
+ function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
90
+
91
+ let location = "user-defined";
92
+ let configIndex = error.index;
93
+
94
+ /*
95
+ * A config array is set up in this order:
96
+ * 1. Base config
97
+ * 2. Original configs
98
+ * 3. User-defined configs
99
+ * 4. CLI-defined configs
100
+ *
101
+ * So we need to adjust the index to account for the base config.
102
+ *
103
+ * - If the index is less than the base length, it's in the base config
104
+ * (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
105
+ * - If the index is greater than the base length but less than the original
106
+ * length + base length, it's in the original config. The original config
107
+ * is passed to the `FlatConfigArray` constructor as the first argument.
108
+ * - Otherwise, it's in the user-defined config, which is loaded from the
109
+ * config file and merged with any command-line options.
110
+ */
111
+ if (error.index < baseLength) {
112
+ location = "base";
113
+ } else if (error.index < originalLength + baseLength) {
114
+ location = "original";
115
+ configIndex = error.index - baseLength;
116
+ } else {
117
+ configIndex = error.index - originalLength - baseLength;
118
+ }
119
+
120
+ return new TypeError(
121
+ `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
122
+ { cause: error }
123
+ );
124
+ }
125
+
78
126
  const originalBaseConfig = Symbol("originalBaseConfig");
127
+ const originalLength = Symbol("originalLength");
128
+ const baseLength = Symbol("baseLength");
79
129
 
80
130
  //-----------------------------------------------------------------------------
81
131
  // Exports
@@ -102,12 +152,24 @@ class FlatConfigArray extends ConfigArray {
102
152
  schema: flatConfigSchema
103
153
  });
104
154
 
155
+ /**
156
+ * The original length of the array before any modifications.
157
+ * @type {number}
158
+ */
159
+ this[originalLength] = this.length;
160
+
105
161
  if (baseConfig[Symbol.iterator]) {
106
162
  this.unshift(...baseConfig);
107
163
  } else {
108
164
  this.unshift(baseConfig);
109
165
  }
110
166
 
167
+ /**
168
+ * The length of the array after applying the base config.
169
+ * @type {number}
170
+ */
171
+ this[baseLength] = this.length - this[originalLength];
172
+
111
173
  /**
112
174
  * The base config used to build the config array.
113
175
  * @type {Array<FlatConfig>}
@@ -125,45 +187,69 @@ class FlatConfigArray extends ConfigArray {
125
187
  Object.defineProperty(this, "shouldIgnore", { writable: false });
126
188
  }
127
189
 
128
- /* eslint-disable class-methods-use-this -- Desired as instance method */
129
190
  /**
130
- * Replaces a config with another config to allow us to put strings
131
- * in the config array that will be replaced by objects before
132
- * normalization.
133
- * @param {Object} config The config to preprocess.
134
- * @returns {Object} The preprocessed config.
191
+ * Normalizes the array by calling the superclass method and catching/rethrowing
192
+ * any ConfigError exceptions with additional details.
193
+ * @param {any} [context] The context to use to normalize the array.
194
+ * @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
135
195
  */
136
- [ConfigArraySymbol.preprocessConfig](config) {
137
- if (config === "eslint:recommended") {
196
+ normalize(context) {
197
+ return super.normalize(context)
198
+ .catch(error => {
199
+ if (error.name === "ConfigError") {
200
+ throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
201
+ }
138
202
 
139
- // if we are in a Node.js environment warn the user
140
- if (typeof process !== "undefined" && process.emitWarning) {
141
- process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
142
- }
203
+ throw error;
143
204
 
144
- return jsPlugin.configs.recommended;
145
- }
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Normalizes the array by calling the superclass method and catching/rethrowing
210
+ * any ConfigError exceptions with additional details.
211
+ * @param {any} [context] The context to use to normalize the array.
212
+ * @returns {FlatConfigArray} The current instance.
213
+ * @throws {TypeError} If the config is invalid.
214
+ */
215
+ normalizeSync(context) {
216
+
217
+ try {
146
218
 
147
- if (config === "eslint:all") {
219
+ return super.normalizeSync(context);
148
220
 
149
- // if we are in a Node.js environment warn the user
150
- if (typeof process !== "undefined" && process.emitWarning) {
151
- process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
221
+ } catch (error) {
222
+
223
+ if (error.name === "ConfigError") {
224
+ throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
152
225
  }
153
226
 
154
- return jsPlugin.configs.all;
227
+ throw error;
228
+
155
229
  }
156
230
 
231
+ }
232
+
233
+ /* eslint-disable class-methods-use-this -- Desired as instance method */
234
+ /**
235
+ * Replaces a config with another config to allow us to put strings
236
+ * in the config array that will be replaced by objects before
237
+ * normalization.
238
+ * @param {Object} config The config to preprocess.
239
+ * @returns {Object} The preprocessed config.
240
+ */
241
+ [ConfigArraySymbol.preprocessConfig](config) {
242
+
157
243
  /*
158
- * If `shouldIgnore` is false, we remove any ignore patterns specified
159
- * in the config so long as it's not a default config and it doesn't
160
- * have a `files` entry.
244
+ * If a config object has `ignores` and no other non-meta fields, then it's an object
245
+ * for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
246
+ * so we'll remove its `ignores`.
161
247
  */
162
248
  if (
163
249
  !this.shouldIgnore &&
164
250
  !this[originalBaseConfig].includes(config) &&
165
251
  config.ignores &&
166
- !config.files
252
+ Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
167
253
  ) {
168
254
  /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
169
255
  const { ignores, ...otherKeys } = config;
@@ -5,6 +5,23 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Typedefs
10
+ //------------------------------------------------------------------------------
11
+
12
+ /** @typedef {import("../shared/types").Rule} Rule */
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Private Members
16
+ //------------------------------------------------------------------------------
17
+
18
+ // JSON schema that disallows passing any options
19
+ const noOptionsSchema = Object.freeze({
20
+ type: "array",
21
+ minItems: 0,
22
+ maxItems: 0
23
+ });
24
+
8
25
  //-----------------------------------------------------------------------------
9
26
  // Functions
10
27
  //-----------------------------------------------------------------------------
@@ -52,32 +69,39 @@ function getRuleFromConfig(ruleId, config) {
52
69
  const { pluginName, ruleName } = parseRuleId(ruleId);
53
70
 
54
71
  const plugin = config.plugins && config.plugins[pluginName];
55
- let rule = plugin && plugin.rules && plugin.rules[ruleName];
56
-
57
-
58
- // normalize function rules into objects
59
- if (rule && typeof rule === "function") {
60
- rule = {
61
- create: rule
62
- };
63
- }
72
+ const rule = plugin && plugin.rules && plugin.rules[ruleName];
64
73
 
65
74
  return rule;
66
75
  }
67
76
 
68
77
  /**
69
78
  * Gets a complete options schema for a rule.
70
- * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
71
- * @returns {Object} JSON Schema for the rule's options.
79
+ * @param {Rule} rule A rule object
80
+ * @throws {TypeError} If `meta.schema` is specified but is not an array, object or `false`.
81
+ * @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`.
72
82
  */
73
83
  function getRuleOptionsSchema(rule) {
74
84
 
75
- if (!rule) {
85
+ if (!rule.meta) {
86
+ return { ...noOptionsSchema }; // default if `meta.schema` is not specified
87
+ }
88
+
89
+ const schema = rule.meta.schema;
90
+
91
+ if (typeof schema === "undefined") {
92
+ return { ...noOptionsSchema }; // default if `meta.schema` is not specified
93
+ }
94
+
95
+ // `schema:false` is an allowed explicit opt-out of options validation for the rule
96
+ if (schema === false) {
76
97
  return null;
77
98
  }
78
99
 
79
- const schema = rule.schema || rule.meta && rule.meta.schema;
100
+ if (typeof schema !== "object" || schema === null) {
101
+ throw new TypeError("Rule's `meta.schema` must be an array or object");
102
+ }
80
103
 
104
+ // ESLint-specific array form needs to be converted into a valid JSON Schema definition
81
105
  if (Array.isArray(schema)) {
82
106
  if (schema.length) {
83
107
  return {
@@ -87,16 +111,13 @@ function getRuleOptionsSchema(rule) {
87
111
  maxItems: schema.length
88
112
  };
89
113
  }
90
- return {
91
- type: "array",
92
- minItems: 0,
93
- maxItems: 0
94
- };
95
114
 
115
+ // `schema:[]` is an explicit way to specify that the rule does not accept any options
116
+ return { ...noOptionsSchema };
96
117
  }
97
118
 
98
- // Given a full schema, leave it alone
99
- return schema || null;
119
+ // `schema:<object>` is assumed to be a valid JSON Schema definition
120
+ return schema;
100
121
  }
101
122
 
102
123
 
@@ -9,11 +9,6 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- /*
13
- * Note: This can be removed in ESLint v9 because structuredClone is available globally
14
- * starting in Node.js v17.
15
- */
16
- const structuredClone = require("@ungap/structured-clone").default;
17
12
  const { normalizeSeverityToNumber } = require("../shared/severity");
18
13
 
19
14
  //-----------------------------------------------------------------------------
@@ -593,6 +588,5 @@ const flatConfigSchema = {
593
588
 
594
589
  module.exports = {
595
590
  flatConfigSchema,
596
- assertIsRuleSeverity,
597
- assertIsRuleOptions
591
+ assertIsRuleSeverity
598
592
  };
@@ -66,6 +66,25 @@ function throwRuleNotFoundError({ pluginName, ruleName }, config) {
66
66
  throw new TypeError(errorMessage);
67
67
  }
68
68
 
69
+ /**
70
+ * The error type when a rule has an invalid `meta.schema`.
71
+ */
72
+ class InvalidRuleOptionsSchemaError extends Error {
73
+
74
+ /**
75
+ * Creates a new instance.
76
+ * @param {string} ruleId Id of the rule that has an invalid `meta.schema`.
77
+ * @param {Error} processingError Error caught while processing the `meta.schema`.
78
+ */
79
+ constructor(ruleId, processingError) {
80
+ super(
81
+ `Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`,
82
+ { cause: processingError }
83
+ );
84
+ this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA";
85
+ }
86
+ }
87
+
69
88
  //-----------------------------------------------------------------------------
70
89
  // Exports
71
90
  //-----------------------------------------------------------------------------
@@ -130,10 +149,14 @@ class RuleValidator {
130
149
 
131
150
  // Precompile and cache validator the first time
132
151
  if (!this.validators.has(rule)) {
133
- const schema = getRuleOptionsSchema(rule);
134
-
135
- if (schema) {
136
- this.validators.set(rule, ajv.compile(schema));
152
+ try {
153
+ const schema = getRuleOptionsSchema(rule);
154
+
155
+ if (schema) {
156
+ this.validators.set(rule, ajv.compile(schema));
157
+ }
158
+ } catch (err) {
159
+ throw new InvalidRuleOptionsSchemaError(ruleId, err);
137
160
  }
138
161
  }
139
162
 
@@ -144,9 +167,22 @@ class RuleValidator {
144
167
  validateRule(ruleOptions.slice(1));
145
168
 
146
169
  if (validateRule.errors) {
147
- throw new Error(`Key "rules": Key "${ruleId}": ${
170
+ throw new Error(`Key "rules": Key "${ruleId}":\n${
148
171
  validateRule.errors.map(
149
- error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
172
+ error => {
173
+ if (
174
+ error.keyword === "additionalProperties" &&
175
+ error.schema === false &&
176
+ typeof error.parentSchema?.properties === "object" &&
177
+ typeof error.params?.additionalProperty === "string"
178
+ ) {
179
+ const expectedProperties = Object.keys(error.parentSchema.properties).map(property => `"${property}"`);
180
+
181
+ return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`;
182
+ }
183
+
184
+ return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`;
185
+ }
150
186
  ).join("")
151
187
  }`);
152
188
  }
@@ -15,7 +15,6 @@ const fsp = fs.promises;
15
15
  const isGlob = require("is-glob");
16
16
  const hash = require("../cli-engine/hash");
17
17
  const minimatch = require("minimatch");
18
- const util = require("util");
19
18
  const fswalk = require("@nodelib/fs.walk");
20
19
  const globParent = require("glob-parent");
21
20
  const isPathInside = require("is-path-inside");
@@ -24,7 +23,6 @@ const isPathInside = require("is-path-inside");
24
23
  // Fixup references
25
24
  //-----------------------------------------------------------------------------
26
25
 
27
- const doFsWalk = util.promisify(fswalk.walk);
28
26
  const Minimatch = minimatch.Minimatch;
29
27
  const MINIMATCH_OPTIONS = { dot: true };
30
28
 
@@ -105,20 +103,30 @@ class AllFilesIgnoredError extends Error {
105
103
 
106
104
  /**
107
105
  * Check if a given value is a non-empty string or not.
108
- * @param {any} x The value to check.
109
- * @returns {boolean} `true` if `x` is a non-empty string.
106
+ * @param {any} value The value to check.
107
+ * @returns {boolean} `true` if `value` is a non-empty string.
110
108
  */
111
- function isNonEmptyString(x) {
112
- return typeof x === "string" && x.trim() !== "";
109
+ function isNonEmptyString(value) {
110
+ return typeof value === "string" && value.trim() !== "";
113
111
  }
114
112
 
115
113
  /**
116
114
  * Check if a given value is an array of non-empty strings or not.
117
- * @param {any} x The value to check.
118
- * @returns {boolean} `true` if `x` is an array of non-empty strings.
115
+ * @param {any} value The value to check.
116
+ * @returns {boolean} `true` if `value` is an array of non-empty strings.
117
+ */
118
+ function isArrayOfNonEmptyString(value) {
119
+ return Array.isArray(value) && value.length && value.every(isNonEmptyString);
120
+ }
121
+
122
+ /**
123
+ * Check if a given value is an empty array or an array of non-empty strings.
124
+ * @param {any} value The value to check.
125
+ * @returns {boolean} `true` if `value` is an empty array or an array of non-empty
126
+ * strings.
119
127
  */
120
- function isArrayOfNonEmptyString(x) {
121
- return Array.isArray(x) && x.every(isNonEmptyString);
128
+ function isEmptyArrayOrArrayOfNonEmptyString(value) {
129
+ return Array.isArray(value) && value.every(isNonEmptyString);
122
130
  }
123
131
 
124
132
  //-----------------------------------------------------------------------------
@@ -270,56 +278,92 @@ async function globSearch({
270
278
  */
271
279
  const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
272
280
 
273
- const filePaths = (await doFsWalk(basePath, {
274
-
275
- deepFilter(entry) {
276
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
277
- const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
281
+ const filePaths = (await new Promise((resolve, reject) => {
278
282
 
279
- return matchesPattern && !configs.isDirectoryIgnored(entry.path);
280
- },
281
- entryFilter(entry) {
282
- const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
283
+ let promiseRejected = false;
283
284
 
284
- // entries may be directories or files so filter out directories
285
- if (entry.dirent.isDirectory()) {
285
+ /**
286
+ * Wraps a boolean-returning filter function. The wrapped function will reject the promise if an error occurs.
287
+ * @param {Function} filter A filter function to wrap.
288
+ * @returns {Function} A function similar to the wrapped filter that rejects the promise if an error occurs.
289
+ */
290
+ function wrapFilter(filter) {
291
+ return (...args) => {
292
+
293
+ // No need to run the filter if an error has been thrown.
294
+ if (!promiseRejected) {
295
+ try {
296
+ return filter(...args);
297
+ } catch (error) {
298
+ promiseRejected = true;
299
+ reject(error);
300
+ }
301
+ }
286
302
  return false;
287
- }
303
+ };
304
+ }
288
305
 
289
- /*
290
- * Optimization: We need to track when patterns are left unmatched
291
- * and so we use `unmatchedPatterns` to do that. There is a bit of
292
- * complexity here because the same file can be matched by more than
293
- * one pattern. So, when we start, we actually need to test every
294
- * pattern against every file. Once we know there are no remaining
295
- * unmatched patterns, then we can switch to just looking for the
296
- * first matching pattern for improved speed.
297
- */
298
- const matchesPattern = unmatchedPatterns.size > 0
299
- ? matchers.reduce((previousValue, matcher) => {
300
- const pathMatches = matcher.match(relativePath);
306
+ fswalk.walk(
307
+ basePath,
308
+ {
309
+ deepFilter: wrapFilter(entry => {
310
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
311
+ const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
312
+
313
+ return matchesPattern && !configs.isDirectoryIgnored(entry.path);
314
+ }),
315
+ entryFilter: wrapFilter(entry => {
316
+ const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
317
+
318
+ // entries may be directories or files so filter out directories
319
+ if (entry.dirent.isDirectory()) {
320
+ return false;
321
+ }
301
322
 
302
323
  /*
303
- * We updated the unmatched patterns set only if the path
304
- * matches and the file isn't ignored. If the file is
305
- * ignored, that means there wasn't a match for the
306
- * pattern so it should not be removed.
307
- *
308
- * Performance note: isFileIgnored() aggressively caches
309
- * results so there is no performance penalty for calling
310
- * it twice with the same argument.
324
+ * Optimization: We need to track when patterns are left unmatched
325
+ * and so we use `unmatchedPatterns` to do that. There is a bit of
326
+ * complexity here because the same file can be matched by more than
327
+ * one pattern. So, when we start, we actually need to test every
328
+ * pattern against every file. Once we know there are no remaining
329
+ * unmatched patterns, then we can switch to just looking for the
330
+ * first matching pattern for improved speed.
311
331
  */
312
- if (pathMatches && !configs.isFileIgnored(entry.path)) {
313
- unmatchedPatterns.delete(matcher.pattern);
314
- }
315
-
316
- return pathMatches || previousValue;
317
- }, false)
318
- : matchers.some(matcher => matcher.match(relativePath));
319
-
320
- return matchesPattern && !configs.isFileIgnored(entry.path);
321
- }
322
-
332
+ const matchesPattern = unmatchedPatterns.size > 0
333
+ ? matchers.reduce((previousValue, matcher) => {
334
+ const pathMatches = matcher.match(relativePath);
335
+
336
+ /*
337
+ * We updated the unmatched patterns set only if the path
338
+ * matches and the file isn't ignored. If the file is
339
+ * ignored, that means there wasn't a match for the
340
+ * pattern so it should not be removed.
341
+ *
342
+ * Performance note: isFileIgnored() aggressively caches
343
+ * results so there is no performance penalty for calling
344
+ * it twice with the same argument.
345
+ */
346
+ if (pathMatches && !configs.isFileIgnored(entry.path)) {
347
+ unmatchedPatterns.delete(matcher.pattern);
348
+ }
349
+
350
+ return pathMatches || previousValue;
351
+ }, false)
352
+ : matchers.some(matcher => matcher.match(relativePath));
353
+
354
+ return matchesPattern && !configs.isFileIgnored(entry.path);
355
+ })
356
+ },
357
+ (error, entries) => {
358
+
359
+ // If the promise is already rejected, calling `resolve` or `reject` will do nothing.
360
+ if (error) {
361
+ reject(error);
362
+ } else {
363
+ resolve(entries);
364
+ }
365
+ }
366
+ );
323
367
  })).map(entry => entry.path);
324
368
 
325
369
  // now check to see if we have any unmatched patterns
@@ -655,9 +699,9 @@ class ESLintInvalidOptionsError extends Error {
655
699
 
656
700
  /**
657
701
  * Validates and normalizes options for the wrapped CLIEngine instance.
658
- * @param {FlatESLintOptions} options The options to process.
702
+ * @param {ESLintOptions} options The options to process.
659
703
  * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
660
- * @returns {FlatESLintOptions} The normalized options.
704
+ * @returns {ESLintOptions} The normalized options.
661
705
  */
662
706
  function processOptions({
663
707
  allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
@@ -675,7 +719,10 @@ function processOptions({
675
719
  overrideConfig = null,
676
720
  overrideConfigFile = null,
677
721
  plugins = {},
722
+ stats = false,
678
723
  warnIgnored = true,
724
+ passOnNoPatterns = false,
725
+ ruleFilter = () => true,
679
726
  ...unknownOptions
680
727
  }) {
681
728
  const errors = [];
@@ -759,7 +806,7 @@ function processOptions({
759
806
  if (typeof ignore !== "boolean") {
760
807
  errors.push("'ignore' must be a boolean.");
761
808
  }
762
- if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
809
+ if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
763
810
  errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
764
811
  }
765
812
  if (typeof overrideConfig !== "object") {
@@ -768,6 +815,9 @@ function processOptions({
768
815
  if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
769
816
  errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
770
817
  }
818
+ if (typeof passOnNoPatterns !== "boolean") {
819
+ errors.push("'passOnNoPatterns' must be a boolean.");
820
+ }
771
821
  if (typeof plugins !== "object") {
772
822
  errors.push("'plugins' must be an object or null.");
773
823
  } else if (plugins !== null && Object.keys(plugins).includes("")) {
@@ -776,9 +826,15 @@ function processOptions({
776
826
  if (Array.isArray(plugins)) {
777
827
  errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
778
828
  }
829
+ if (typeof stats !== "boolean") {
830
+ errors.push("'stats' must be a boolean.");
831
+ }
779
832
  if (typeof warnIgnored !== "boolean") {
780
833
  errors.push("'warnIgnored' must be a boolean.");
781
834
  }
835
+ if (typeof ruleFilter !== "function") {
836
+ errors.push("'ruleFilter' must be a function.");
837
+ }
782
838
  if (errors.length > 0) {
783
839
  throw new ESLintInvalidOptionsError(errors);
784
840
  }
@@ -800,7 +856,10 @@ function processOptions({
800
856
  globInputPaths,
801
857
  ignore,
802
858
  ignorePatterns,
803
- warnIgnored
859
+ stats,
860
+ passOnNoPatterns,
861
+ warnIgnored,
862
+ ruleFilter
804
863
  };
805
864
  }
806
865
 
@@ -887,7 +946,6 @@ function getCacheFile(cacheFile, cwd) {
887
946
  //-----------------------------------------------------------------------------
888
947
 
889
948
  module.exports = {
890
- isGlobPattern,
891
949
  findFiles,
892
950
 
893
951
  isNonEmptyString,