eslint 9.33.0 → 9.35.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 (42) hide show
  1. package/README.md +1 -1
  2. package/lib/cli-engine/file-enumerator.js +1 -1
  3. package/lib/cli.js +62 -266
  4. package/lib/config/flat-config-schema.js +1 -1
  5. package/lib/eslint/eslint-helpers.js +426 -6
  6. package/lib/eslint/eslint.js +381 -313
  7. package/lib/eslint/worker.js +164 -0
  8. package/lib/languages/js/source-code/source-code.js +3 -4
  9. package/lib/linter/esquery.js +3 -0
  10. package/lib/linter/interpolate.js +1 -1
  11. package/lib/linter/linter.js +3 -4
  12. package/lib/options.js +23 -9
  13. package/lib/rule-tester/rule-tester.js +3 -0
  14. package/lib/rules/array-callback-return.js +0 -1
  15. package/lib/rules/dot-notation.js +1 -1
  16. package/lib/rules/grouped-accessor-pairs.js +8 -7
  17. package/lib/rules/indent-legacy.js +1 -1
  18. package/lib/rules/index.js +1 -0
  19. package/lib/rules/no-alert.js +1 -1
  20. package/lib/rules/no-empty-function.js +20 -1
  21. package/lib/rules/no-empty-static-block.js +25 -1
  22. package/lib/rules/no-empty.js +37 -0
  23. package/lib/rules/no-eval.js +3 -1
  24. package/lib/rules/no-irregular-whitespace.js +2 -2
  25. package/lib/rules/no-loss-of-precision.js +26 -3
  26. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -0
  27. package/lib/rules/no-octal.js +1 -4
  28. package/lib/rules/no-trailing-spaces.js +2 -1
  29. package/lib/rules/no-useless-escape.js +1 -1
  30. package/lib/rules/prefer-regex-literals.js +1 -1
  31. package/lib/rules/preserve-caught-error.js +509 -0
  32. package/lib/rules/strict.js +2 -1
  33. package/lib/rules/utils/ast-utils.js +1 -1
  34. package/lib/rules/utils/char-source.js +1 -1
  35. package/lib/rules/yoda.js +2 -2
  36. package/lib/services/suppressions-service.js +3 -0
  37. package/lib/services/warning-service.js +13 -0
  38. package/lib/shared/naming.js +1 -1
  39. package/lib/shared/translate-cli-options.js +281 -0
  40. package/lib/types/index.d.ts +7 -0
  41. package/lib/types/rules.d.ts +22 -14
  42. package/package.json +3 -3
@@ -11,11 +11,17 @@
11
11
 
12
12
  const path = require("node:path");
13
13
  const fs = require("node:fs");
14
+ const { isMainThread, threadId } = require("node:worker_threads");
14
15
  const fsp = fs.promises;
15
16
  const isGlob = require("is-glob");
16
17
  const hash = require("../cli-engine/hash");
17
18
  const minimatch = require("minimatch");
18
19
  const globParent = require("glob-parent");
20
+ const { Linter } = require("../linter");
21
+ const { getShorthandName } = require("../shared/naming");
22
+ const LintResultCache = require("../cli-engine/lint-result-cache");
23
+ const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
24
+ const createDebug = require("debug");
19
25
 
20
26
  //-----------------------------------------------------------------------------
21
27
  // Fixup references
@@ -30,11 +36,16 @@ const MINIMATCH_OPTIONS = { dot: true };
30
36
 
31
37
  /**
32
38
  * @import { ESLintOptions } from "./eslint.js";
33
- * @import { ConfigLoader, LegacyConfigLoader } from "../config/config-loader.js";
39
+ * @import { Config as CalculatedConfig } from "../config/config.js";
40
+ * @import { FlatConfigArray } from "../config/flat-config-array.js";
41
+ * @import { WarningService } from "../services/warning-service.js";
42
+ * @import { Retrier } from "@humanwhocodes/retry";
34
43
  */
35
44
 
45
+ /** @typedef {import("../types").Linter.Config} Config */
36
46
  /** @typedef {import("../types").Linter.LintMessage} LintMessage */
37
47
  /** @typedef {import("../types").ESLint.LintResult} LintResult */
48
+ /** @typedef {import("../types").ESLint.Plugin} Plugin */
38
49
 
39
50
  /**
40
51
  * @typedef {Object} GlobSearch
@@ -43,6 +54,18 @@ const MINIMATCH_OPTIONS = { dot: true };
43
54
  * before doing any normalization.
44
55
  */
45
56
 
57
+ //------------------------------------------------------------------------------
58
+ // Internal Helpers
59
+ //------------------------------------------------------------------------------
60
+
61
+ const hrtimeBigint = process.hrtime.bigint;
62
+
63
+ createDebug.formatters.t = timeDiff =>
64
+ `${(timeDiff + 500_000n) / 1_000_000n} ms`;
65
+ const debug = createDebug(
66
+ `eslint:eslint-helpers${isMainThread ? "" : `:thread-${threadId}`}`,
67
+ );
68
+
46
69
  //-----------------------------------------------------------------------------
47
70
  // Errors
48
71
  //-----------------------------------------------------------------------------
@@ -137,6 +160,15 @@ function isEmptyArrayOrArrayOfNonEmptyString(value) {
137
160
  return Array.isArray(value) && value.every(isNonEmptyString);
138
161
  }
139
162
 
163
+ /**
164
+ * Check if a given value is a positive integer.
165
+ * @param {unknown} value The value to check.
166
+ * @returns {boolean} `true` if `value` is a positive integer.
167
+ */
168
+ function isPositiveInteger(value) {
169
+ return Number.isInteger(value) && value > 0;
170
+ }
171
+
140
172
  //-----------------------------------------------------------------------------
141
173
  // File-related Helpers
142
174
  //-----------------------------------------------------------------------------
@@ -537,6 +569,7 @@ async function findFiles({
537
569
  filePaths.map(filePath => fsp.stat(filePath).catch(() => {})),
538
570
  );
539
571
 
572
+ const promises = [];
540
573
  stats.forEach((stat, index) => {
541
574
  const filePath = filePaths[index];
542
575
  const pattern = normalizeToPosix(patterns[index]);
@@ -545,6 +578,7 @@ async function findFiles({
545
578
  // files are added directly to the list
546
579
  if (stat.isFile()) {
547
580
  results.push(filePath);
581
+ promises.push(configLoader.loadConfigArrayForFile(filePath));
548
582
  }
549
583
 
550
584
  // directories need extensions attached
@@ -590,15 +624,28 @@ async function findFiles({
590
624
  }
591
625
 
592
626
  // now we are safe to do the search
593
- const globbyResults = await globMultiSearch({
594
- searches,
595
- configLoader,
596
- errorOnUnmatchedPattern,
597
- });
627
+ promises.push(
628
+ globMultiSearch({
629
+ searches,
630
+ configLoader,
631
+ errorOnUnmatchedPattern,
632
+ }),
633
+ );
634
+ const globbyResults = (await Promise.all(promises)).at(-1);
598
635
 
599
636
  return [...new Set([...results, ...globbyResults])];
600
637
  }
601
638
 
639
+ /**
640
+ * Return the absolute path of a file named `"__placeholder__.js"` in a given directory.
641
+ * This is used as a replacement for a missing file path.
642
+ * @param {string} cwd An absolute directory path.
643
+ * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory.
644
+ */
645
+ function getPlaceholderPath(cwd) {
646
+ return path.join(cwd, "__placeholder__.js");
647
+ }
648
+
602
649
  //-----------------------------------------------------------------------------
603
650
  // Results-related Helpers
604
651
  //-----------------------------------------------------------------------------
@@ -758,6 +805,7 @@ function processOptions({
758
805
  cache = false,
759
806
  cacheLocation = ".eslintcache",
760
807
  cacheStrategy = "metadata",
808
+ concurrency = "off",
761
809
  cwd = process.cwd(),
762
810
  errorOnUnmatchedPattern = true,
763
811
  fix = false,
@@ -853,6 +901,15 @@ function processOptions({
853
901
  if (cacheStrategy !== "metadata" && cacheStrategy !== "content") {
854
902
  errors.push('\'cacheStrategy\' must be any of "metadata", "content".');
855
903
  }
904
+ if (
905
+ concurrency !== "off" &&
906
+ concurrency !== "auto" &&
907
+ !isPositiveInteger(concurrency)
908
+ ) {
909
+ errors.push(
910
+ '\'concurrency\' must be a positive integer, "auto", or "off".',
911
+ );
912
+ }
856
913
  if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
857
914
  errors.push("'cwd' must be an absolute path.");
858
915
  }
@@ -928,6 +985,7 @@ function processOptions({
928
985
  cache,
929
986
  cacheLocation,
930
987
  cacheStrategy,
988
+ concurrency,
931
989
 
932
990
  // when overrideConfigFile is true that means don't do config file lookup
933
991
  configFile: overrideConfigFile === true ? false : overrideConfigFile,
@@ -947,6 +1005,15 @@ function processOptions({
947
1005
  };
948
1006
  }
949
1007
 
1008
+ /**
1009
+ * Loads ESLint constructor options from an options module.
1010
+ * @param {string} optionsURL The URL string of the options module to load.
1011
+ * @returns {Promise<ESLintOptions>} ESLint constructor options.
1012
+ */
1013
+ async function loadOptionsFromModule(optionsURL) {
1014
+ return (await import(optionsURL)).default;
1015
+ }
1016
+
950
1017
  //-----------------------------------------------------------------------------
951
1018
  // Cache-related helpers
952
1019
  //-----------------------------------------------------------------------------
@@ -1022,6 +1089,349 @@ function getCacheFile(cacheFile, cwd, { prefix = ".cache_" } = {}) {
1022
1089
  return resolvedCacheFile;
1023
1090
  }
1024
1091
 
1092
+ /**
1093
+ * Creates a new lint result cache.
1094
+ * @param {ESLintOptions} eslintOptions The processed ESLint options.
1095
+ * @param {string} cacheFilePath The path to the cache file.
1096
+ * @returns {?LintResultCache} A new lint result cache or `null`.
1097
+ */
1098
+ function createLintResultCache({ cache, cacheStrategy }, cacheFilePath) {
1099
+ return cache ? new LintResultCache(cacheFilePath, cacheStrategy) : null;
1100
+ }
1101
+
1102
+ //-----------------------------------------------------------------------------
1103
+ // Lint helpers
1104
+ //-----------------------------------------------------------------------------
1105
+
1106
+ /**
1107
+ * Checks whether a message's rule type should be fixed.
1108
+ * @param {LintMessage} message The message to check.
1109
+ * @param {CalculatedConfig} config The config for the file that generated the message.
1110
+ * @param {string[]} fixTypes An array of fix types to check.
1111
+ * @returns {boolean} Whether the message should be fixed.
1112
+ */
1113
+ function shouldMessageBeFixed(message, config, fixTypes) {
1114
+ if (!message.ruleId) {
1115
+ return fixTypes.has("directive");
1116
+ }
1117
+
1118
+ const rule = message.ruleId && config.getRuleDefinition(message.ruleId);
1119
+
1120
+ return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
1121
+ }
1122
+
1123
+ /**
1124
+ * Creates a fixer function based on the provided fix, fixTypesSet, and config.
1125
+ * @param {Function|boolean} fix The original fix option.
1126
+ * @param {Set<string>} fixTypesSet A set of fix types to filter messages for fixing.
1127
+ * @param {CalculatedConfig} config The config for the file that generated the message.
1128
+ * @returns {Function|boolean} The fixer function or the original fix value.
1129
+ */
1130
+ function getFixerForFixTypes(fix, fixTypesSet, config) {
1131
+ if (!fix || !fixTypesSet) {
1132
+ return fix;
1133
+ }
1134
+
1135
+ const originalFix = typeof fix === "function" ? fix : () => true;
1136
+
1137
+ return message =>
1138
+ shouldMessageBeFixed(message, config, fixTypesSet) &&
1139
+ originalFix(message);
1140
+ }
1141
+
1142
+ /**
1143
+ * Processes a source code using ESLint.
1144
+ * @param {Object} config The config object.
1145
+ * @param {string} config.text The source code to verify.
1146
+ * @param {string} config.cwd The path to the current working directory.
1147
+ * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
1148
+ * @param {FlatConfigArray} config.configs The config.
1149
+ * @param {boolean} config.fix If `true` then it does fix.
1150
+ * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
1151
+ * @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
1152
+ * @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
1153
+ * @param {Linter} config.linter The linter instance to verify.
1154
+ * @returns {LintResult} The result of linting.
1155
+ * @private
1156
+ */
1157
+ function verifyText({
1158
+ text,
1159
+ cwd,
1160
+ filePath: providedFilePath,
1161
+ configs,
1162
+ fix,
1163
+ allowInlineConfig,
1164
+ ruleFilter,
1165
+ stats,
1166
+ linter,
1167
+ }) {
1168
+ const startTime = hrtimeBigint();
1169
+
1170
+ const filePath = providedFilePath || "<text>";
1171
+
1172
+ /*
1173
+ * Verify.
1174
+ * `config.extractConfig(filePath)` requires an absolute path, but `linter`
1175
+ * doesn't know CWD, so it gives `linter` an absolute path always.
1176
+ */
1177
+ const filePathToVerify =
1178
+ filePath === "<text>" ? getPlaceholderPath(cwd) : filePath;
1179
+ const { fixed, messages, output } = linter.verifyAndFix(text, configs, {
1180
+ allowInlineConfig,
1181
+ filename: filePathToVerify,
1182
+ fix,
1183
+ ruleFilter,
1184
+ stats,
1185
+
1186
+ /**
1187
+ * Check if the linter should adopt a given code block or not.
1188
+ * @param {string} blockFilename The virtual filename of a code block.
1189
+ * @returns {boolean} `true` if the linter should adopt the code block.
1190
+ */
1191
+ filterCodeBlock(blockFilename) {
1192
+ return configs.getConfig(blockFilename) !== void 0;
1193
+ },
1194
+ });
1195
+
1196
+ // Tweak and return.
1197
+ const result = {
1198
+ filePath: filePath === "<text>" ? filePath : path.resolve(filePath),
1199
+ messages,
1200
+ suppressedMessages: linter.getSuppressedMessages(),
1201
+ ...calculateStatsPerFile(messages),
1202
+ };
1203
+
1204
+ if (fixed) {
1205
+ result.output = output;
1206
+ }
1207
+
1208
+ if (
1209
+ result.errorCount + result.warningCount > 0 &&
1210
+ typeof result.output === "undefined"
1211
+ ) {
1212
+ result.source = text;
1213
+ }
1214
+
1215
+ if (stats) {
1216
+ result.stats = {
1217
+ times: linter.getTimes(),
1218
+ fixPasses: linter.getFixPassCount(),
1219
+ };
1220
+ }
1221
+
1222
+ const endTime = hrtimeBigint();
1223
+ debug('File "%s" linted in %t', filePath, endTime - startTime);
1224
+
1225
+ return result;
1226
+ }
1227
+
1228
+ /**
1229
+ * Lints a single file.
1230
+ * @param {string} filePath File path to lint.
1231
+ * @param {FlatConfigArray} configs The config array for the file.
1232
+ * @param {ESLintOptions} eslintOptions The processed ESLint options.
1233
+ * @param {Linter} linter The linter instance to use.
1234
+ * @param {?LintResultCache} lintResultCache The result cache or `null`.
1235
+ * @param {?{ duration: bigint; }} readFileCounter Used to keep track of the time spent reading files.
1236
+ * @param {Retrier} [retrier] Used to retry linting on certain errors.
1237
+ * @param {AbortController} [controller] Used to stop linting when an error occurs.
1238
+ * @returns {Promise<LintResult>} The lint result.
1239
+ */
1240
+ async function lintFile(
1241
+ filePath,
1242
+ configs,
1243
+ eslintOptions,
1244
+ linter,
1245
+ lintResultCache,
1246
+ readFileCounter,
1247
+ retrier,
1248
+ controller,
1249
+ ) {
1250
+ const config = configs.getConfig(filePath);
1251
+ const {
1252
+ allowInlineConfig,
1253
+ cwd,
1254
+ fix,
1255
+ fixTypes,
1256
+ ruleFilter,
1257
+ stats,
1258
+ warnIgnored,
1259
+ } = eslintOptions;
1260
+ const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
1261
+
1262
+ /*
1263
+ * If a filename was entered that cannot be matched
1264
+ * to a config, then notify the user.
1265
+ */
1266
+ if (!config) {
1267
+ if (warnIgnored) {
1268
+ const configStatus = configs.getConfigStatus(filePath);
1269
+
1270
+ return createIgnoreResult(filePath, cwd, configStatus);
1271
+ }
1272
+
1273
+ return void 0;
1274
+ }
1275
+
1276
+ // Skip if there is cached result.
1277
+ if (lintResultCache) {
1278
+ const cachedResult = lintResultCache.getCachedLintResults(
1279
+ filePath,
1280
+ config,
1281
+ );
1282
+
1283
+ if (cachedResult) {
1284
+ const hadMessages =
1285
+ cachedResult.messages && cachedResult.messages.length > 0;
1286
+
1287
+ if (hadMessages && fix) {
1288
+ debug(`Reprocessing cached file to allow autofix: ${filePath}`);
1289
+ } else {
1290
+ debug(`Skipping file since it hasn't changed: ${filePath}`);
1291
+ return cachedResult;
1292
+ }
1293
+ }
1294
+ }
1295
+
1296
+ // set up fixer for fixTypes if necessary
1297
+ const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
1298
+
1299
+ /**
1300
+ * Reads the file and lints its content.
1301
+ * @returns {Promise<LintResult>} A lint result.
1302
+ */
1303
+ async function readAndVerifyFile() {
1304
+ const readFileEnterTime = hrtimeBigint();
1305
+ const text = await fsp.readFile(filePath, {
1306
+ encoding: "utf8",
1307
+ signal: controller?.signal,
1308
+ });
1309
+ const readFileExitTime = hrtimeBigint();
1310
+ const readFileDuration = readFileExitTime - readFileEnterTime;
1311
+ debug('File "%s" read in %t', filePath, readFileDuration);
1312
+ if (readFileCounter) {
1313
+ readFileCounter.duration += readFileDuration;
1314
+ }
1315
+
1316
+ // fail immediately if an error occurred in another file
1317
+ controller?.signal.throwIfAborted();
1318
+
1319
+ // do the linting
1320
+ return verifyText({
1321
+ text,
1322
+ filePath,
1323
+ configs,
1324
+ cwd,
1325
+ fix: fixer,
1326
+ allowInlineConfig,
1327
+ ruleFilter,
1328
+ stats,
1329
+ linter,
1330
+ });
1331
+ }
1332
+
1333
+ // Use the retrier if provided, otherwise just call the function.
1334
+ const readAndVerifyFilePromise = retrier
1335
+ ? retrier.retry(readAndVerifyFile, { signal: controller?.signal })
1336
+ : readAndVerifyFile();
1337
+
1338
+ return readAndVerifyFilePromise.catch(error => {
1339
+ controller?.abort(error);
1340
+ throw error;
1341
+ });
1342
+ }
1343
+
1344
+ /**
1345
+ * Retrieves flags from the environment variable ESLINT_FLAGS.
1346
+ * @param {string[]} flags The flags defined via the API.
1347
+ * @returns {string[]} The merged flags to use.
1348
+ */
1349
+ function mergeEnvironmentFlags(flags) {
1350
+ if (!process.env.ESLINT_FLAGS) {
1351
+ return flags;
1352
+ }
1353
+
1354
+ const envFlags = process.env.ESLINT_FLAGS.trim().split(/\s*,\s*/gu);
1355
+ return Array.from(new Set([...envFlags, ...flags]));
1356
+ }
1357
+
1358
+ /**
1359
+ * Creates a new linter instance.
1360
+ * @param {ESLintOptions} eslintOptions The processed ESLint options.
1361
+ * @param {WarningService} warningService The warning service to use.
1362
+ * @returns {Linter} The linter instance.
1363
+ */
1364
+ function createLinter({ cwd, flags }, warningService) {
1365
+ return new Linter({
1366
+ configType: "flat",
1367
+ cwd,
1368
+ flags: mergeEnvironmentFlags(flags),
1369
+ warningService,
1370
+ });
1371
+ }
1372
+
1373
+ /**
1374
+ * Creates default configs with the specified plugins.
1375
+ * @param {Record<string, Plugin> | undefined} optionPlugins The plugins specified in the ESLint options.
1376
+ * @returns {Config[]} The default configs.
1377
+ */
1378
+ function createDefaultConfigs(optionPlugins) {
1379
+ const defaultConfigs = [];
1380
+
1381
+ // Add plugins
1382
+ if (optionPlugins) {
1383
+ const plugins = {};
1384
+
1385
+ for (const [pluginName, plugin] of Object.entries(optionPlugins)) {
1386
+ plugins[getShorthandName(pluginName, "eslint-plugin")] = plugin;
1387
+ }
1388
+
1389
+ defaultConfigs.push({ plugins });
1390
+ }
1391
+
1392
+ return defaultConfigs;
1393
+ }
1394
+
1395
+ /**
1396
+ * Creates a config loader.
1397
+ * @param {ESLintOptions} eslintOptions The processed ESLint options.
1398
+ * @param {Config[]} defaultConfigs The default configs.
1399
+ * @param {Linter} linter The linter instance.
1400
+ * @param {WarningService} warningService The warning service to use.
1401
+ * @returns {ConfigLoader} The config loader.
1402
+ */
1403
+ function createConfigLoader(
1404
+ {
1405
+ cwd,
1406
+ baseConfig,
1407
+ overrideConfig,
1408
+ configFile,
1409
+ ignore: ignoreEnabled,
1410
+ ignorePatterns,
1411
+ },
1412
+ defaultConfigs,
1413
+ linter,
1414
+ warningService,
1415
+ ) {
1416
+ const configLoaderOptions = {
1417
+ cwd,
1418
+ baseConfig,
1419
+ overrideConfig,
1420
+ configFile,
1421
+ ignoreEnabled,
1422
+ ignorePatterns,
1423
+ defaultConfigs,
1424
+ hasUnstableNativeNodeJsTSConfigFlag: linter.hasFlag(
1425
+ "unstable_native_nodejs_ts_config",
1426
+ ),
1427
+ warningService,
1428
+ };
1429
+
1430
+ return linter.hasFlag("v10_config_lookup_from_file")
1431
+ ? new ConfigLoader(configLoaderOptions)
1432
+ : new LegacyConfigLoader(configLoaderOptions);
1433
+ }
1434
+
1025
1435
  //-----------------------------------------------------------------------------
1026
1436
  // Exports
1027
1437
  //-----------------------------------------------------------------------------
@@ -1035,8 +1445,18 @@ module.exports = {
1035
1445
  createIgnoreResult,
1036
1446
  isErrorMessage,
1037
1447
  calculateStatsPerFile,
1448
+ getPlaceholderPath,
1038
1449
 
1039
1450
  processOptions,
1451
+ loadOptionsFromModule,
1040
1452
 
1041
1453
  getCacheFile,
1454
+ createLintResultCache,
1455
+
1456
+ getFixerForFixTypes,
1457
+ verifyText,
1458
+ lintFile,
1459
+ createLinter,
1460
+ createDefaultConfigs,
1461
+ createConfigLoader,
1042
1462
  };