eslint 9.11.0 → 9.12.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 +1 -1
- package/lib/cli-engine/formatters/stylish.js +2 -2
- package/lib/cli.js +1 -6
- package/lib/config/config-loader.js +694 -0
- package/lib/eslint/eslint-helpers.js +128 -134
- package/lib/eslint/eslint.js +84 -294
- package/lib/rule-tester/rule-tester.js +33 -2
- package/lib/rules/complexity.js +16 -6
- package/lib/shared/flags.js +1 -0
- package/lib/types/rules/best-practices.d.ts +16 -1
- package/lib/types/rules/ecmascript-6.d.ts +4 -0
- package/lib/types/rules/possible-errors.d.ts +22 -2
- package/lib/types/rules/stylistic-issues.d.ts +25 -0
- package/lib/types/rules/variables.d.ts +5 -0
- package/lib/unsupported-api.js +2 -1
- package/package.json +28 -15
package/lib/eslint/eslint.js
CHANGED
@@ -12,7 +12,6 @@
|
|
12
12
|
const fs = require("node:fs/promises");
|
13
13
|
const { existsSync } = require("node:fs");
|
14
14
|
const path = require("node:path");
|
15
|
-
const findUp = require("find-up");
|
16
15
|
const { version } = require("../../package.json");
|
17
16
|
const { Linter } = require("../linter");
|
18
17
|
const { getRuleFromConfig } = require("../config/flat-config-helpers");
|
@@ -39,9 +38,9 @@ const {
|
|
39
38
|
processOptions
|
40
39
|
} = require("./eslint-helpers");
|
41
40
|
const { pathToFileURL } = require("node:url");
|
42
|
-
const { FlatConfigArray } = require("../config/flat-config-array");
|
43
41
|
const LintResultCache = require("../cli-engine/lint-result-cache");
|
44
42
|
const { Retrier } = require("@humanwhocodes/retry");
|
43
|
+
const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
|
45
44
|
|
46
45
|
/*
|
47
46
|
* This is necessary to allow overwriting writeFile for testing purposes.
|
@@ -97,24 +96,8 @@ const { Retrier } = require("@humanwhocodes/retry");
|
|
97
96
|
// Helpers
|
98
97
|
//------------------------------------------------------------------------------
|
99
98
|
|
100
|
-
const FLAT_CONFIG_FILENAMES = [
|
101
|
-
"eslint.config.js",
|
102
|
-
"eslint.config.mjs",
|
103
|
-
"eslint.config.cjs"
|
104
|
-
];
|
105
|
-
const FLAT_CONFIG_FILENAMES_WITH_TS = [
|
106
|
-
...FLAT_CONFIG_FILENAMES,
|
107
|
-
"eslint.config.ts",
|
108
|
-
"eslint.config.mts",
|
109
|
-
"eslint.config.cts"
|
110
|
-
];
|
111
99
|
const debug = require("debug")("eslint:eslint");
|
112
100
|
const privateMembers = new WeakMap();
|
113
|
-
|
114
|
-
/**
|
115
|
-
* @type {Map<string, string>}
|
116
|
-
*/
|
117
|
-
const importedConfigFileModificationTime = new Map();
|
118
101
|
const removedFormatters = new Set([
|
119
102
|
"checkstyle",
|
120
103
|
"codeframe",
|
@@ -196,12 +179,13 @@ const usedDeprecatedRulesCache = new WeakMap();
|
|
196
179
|
*/
|
197
180
|
function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
|
198
181
|
const {
|
199
|
-
|
200
|
-
|
182
|
+
options: { cwd },
|
183
|
+
configLoader
|
201
184
|
} = privateMembers.get(eslint);
|
202
185
|
const filePath = path.isAbsolute(maybeFilePath)
|
203
186
|
? maybeFilePath
|
204
187
|
: getPlaceholderPath(cwd);
|
188
|
+
const configs = configLoader.getCachedConfigArrayForFile(filePath);
|
205
189
|
const config = configs.getConfig(filePath);
|
206
190
|
|
207
191
|
// Most files use the same config, so cache it.
|
@@ -270,269 +254,38 @@ function compareResultsByFilePath(a, b) {
|
|
270
254
|
return 0;
|
271
255
|
}
|
272
256
|
|
273
|
-
/**
|
274
|
-
* Searches from the current working directory up until finding the
|
275
|
-
* given flat config filename.
|
276
|
-
* @param {string} cwd The current working directory to search from.
|
277
|
-
* @param {boolean} hasUnstableTSConfigFlag `true` if the `unstable_ts_config` flag is enabled, `false` if it's not.
|
278
|
-
* @returns {Promise<string|undefined>} The filename if found or `undefined` if not.
|
279
|
-
*/
|
280
|
-
function findFlatConfigFile(cwd, hasUnstableTSConfigFlag) {
|
281
|
-
const filenames = hasUnstableTSConfigFlag ? FLAT_CONFIG_FILENAMES_WITH_TS : FLAT_CONFIG_FILENAMES;
|
282
|
-
|
283
|
-
return findUp(
|
284
|
-
filenames,
|
285
|
-
{ cwd }
|
286
|
-
);
|
287
|
-
}
|
288
|
-
|
289
|
-
/**
|
290
|
-
* Check if the file is a TypeScript file.
|
291
|
-
* @param {string} filePath The file path to check.
|
292
|
-
* @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not.
|
293
|
-
*/
|
294
|
-
function isFileTS(filePath) {
|
295
|
-
const fileExtension = path.extname(filePath);
|
296
|
-
|
297
|
-
return /^\.[mc]?ts$/u.test(fileExtension);
|
298
|
-
}
|
299
|
-
|
300
|
-
/**
|
301
|
-
* Check if ESLint is running in Bun.
|
302
|
-
* @returns {boolean} `true` if the ESLint is running Bun, `false` if it's not.
|
303
|
-
*/
|
304
|
-
function isRunningInBun() {
|
305
|
-
return !!globalThis.Bun;
|
306
|
-
}
|
307
|
-
|
308
|
-
/**
|
309
|
-
* Check if ESLint is running in Deno.
|
310
|
-
* @returns {boolean} `true` if the ESLint is running in Deno, `false` if it's not.
|
311
|
-
*/
|
312
|
-
function isRunningInDeno() {
|
313
|
-
return !!globalThis.Deno;
|
314
|
-
}
|
315
|
-
|
316
|
-
/**
|
317
|
-
* Load the config array from the given filename.
|
318
|
-
* @param {string} filePath The filename to load from.
|
319
|
-
* @param {boolean} hasUnstableTSConfigFlag `true` if the `unstable_ts_config` flag is enabled, `false` if it's not.
|
320
|
-
* @returns {Promise<any>} The config loaded from the config file.
|
321
|
-
*/
|
322
|
-
async function loadFlatConfigFile(filePath, hasUnstableTSConfigFlag) {
|
323
|
-
debug(`Loading config from ${filePath}`);
|
324
|
-
|
325
|
-
const fileURL = pathToFileURL(filePath);
|
326
|
-
|
327
|
-
debug(`Config file URL is ${fileURL}`);
|
328
|
-
|
329
|
-
const mtime = (await fs.stat(filePath)).mtime.getTime().toString();
|
330
|
-
|
331
|
-
/*
|
332
|
-
* Append a query with the config file's modification time (`mtime`) in order
|
333
|
-
* to import the current version of the config file. Without the query, `import()` would
|
334
|
-
* cache the config file module by the pathname only, and then always return
|
335
|
-
* the same version (the one that was actual when the module was imported for the first time).
|
336
|
-
*
|
337
|
-
* This ensures that the config file module is loaded and executed again
|
338
|
-
* if it has been changed since the last time it was imported.
|
339
|
-
* If it hasn't been changed, `import()` will just return the cached version.
|
340
|
-
*
|
341
|
-
* Note that we should not overuse queries (e.g., by appending the current time
|
342
|
-
* to always reload the config file module) as that could cause memory leaks
|
343
|
-
* because entries are never removed from the import cache.
|
344
|
-
*/
|
345
|
-
fileURL.searchParams.append("mtime", mtime);
|
346
|
-
|
347
|
-
/*
|
348
|
-
* With queries, we can bypass the import cache. However, when import-ing a CJS module,
|
349
|
-
* Node.js uses the require infrastructure under the hood. That includes the require cache,
|
350
|
-
* which caches the config file module by its file path (queries have no effect).
|
351
|
-
* Therefore, we also need to clear the require cache before importing the config file module.
|
352
|
-
* In order to get the same behavior with ESM and CJS config files, in particular - to reload
|
353
|
-
* the config file only if it has been changed, we track file modification times and clear
|
354
|
-
* the require cache only if the file has been changed.
|
355
|
-
*/
|
356
|
-
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
|
357
|
-
delete require.cache[filePath];
|
358
|
-
}
|
359
|
-
|
360
|
-
const isTS = isFileTS(filePath) && hasUnstableTSConfigFlag;
|
361
|
-
|
362
|
-
const isBun = isRunningInBun();
|
363
|
-
|
364
|
-
const isDeno = isRunningInDeno();
|
365
|
-
|
366
|
-
if (isTS && !isDeno && !isBun) {
|
367
|
-
|
368
|
-
const createJiti = await import("jiti").then(jitiModule => jitiModule.default, () => {
|
369
|
-
throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.");
|
370
|
-
});
|
371
|
-
|
372
|
-
/*
|
373
|
-
* Disabling `moduleCache` allows us to reload a
|
374
|
-
* config file when the last modified timestamp changes.
|
375
|
-
*/
|
376
|
-
|
377
|
-
const jiti = createJiti(__filename, { moduleCache: false });
|
378
|
-
|
379
|
-
if (typeof jiti?.import !== "function") {
|
380
|
-
throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.");
|
381
|
-
}
|
382
|
-
|
383
|
-
const config = await jiti.import(fileURL.href);
|
384
|
-
|
385
|
-
importedConfigFileModificationTime.set(filePath, mtime);
|
386
|
-
|
387
|
-
return config?.default ?? config;
|
388
|
-
}
|
389
|
-
|
390
|
-
const config = (await import(fileURL.href)).default;
|
391
|
-
|
392
|
-
importedConfigFileModificationTime.set(filePath, mtime);
|
393
|
-
|
394
|
-
return config;
|
395
|
-
}
|
396
257
|
|
397
258
|
/**
|
398
259
|
* Determines which config file to use. This is determined by seeing if an
|
399
260
|
* override config file was passed, and if so, using it; otherwise, as long
|
400
261
|
* as override config file is not explicitly set to `false`, it will search
|
401
262
|
* upwards from the cwd for a file named `eslint.config.js`.
|
263
|
+
*
|
264
|
+
* This function is used primarily by the `--inspect-config` option. For now,
|
265
|
+
* we will maintain the existing behavior, which is to search up from the cwd.
|
402
266
|
* @param {ESLintOptions} options The ESLint instance options.
|
403
|
-
* @param {boolean}
|
404
|
-
* @returns {Promise<{configFilePath:string|undefined;basePath:string
|
267
|
+
* @param {boolean} allowTS `true` if the `unstable_ts_config` flag is enabled, `false` if it's not.
|
268
|
+
* @returns {Promise<{configFilePath:string|undefined;basePath:string}>} Location information for
|
405
269
|
* the config file.
|
406
270
|
*/
|
407
|
-
async function locateConfigFileToUse({ configFile, cwd },
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
configFilePath = path.resolve(cwd, configFile);
|
417
|
-
} else if (configFile !== false) {
|
418
|
-
debug("Searching for eslint.config.js");
|
419
|
-
configFilePath = await findFlatConfigFile(cwd, hasUnstableTSConfigFlag);
|
420
|
-
|
421
|
-
if (configFilePath) {
|
422
|
-
basePath = path.resolve(path.dirname(configFilePath));
|
423
|
-
} else {
|
424
|
-
error = new Error("Could not find config file.");
|
425
|
-
error.messageTemplate = "config-file-missing";
|
426
|
-
}
|
271
|
+
async function locateConfigFileToUse({ configFile, cwd }, allowTS) {
|
272
|
+
|
273
|
+
const configLoader = new ConfigLoader({
|
274
|
+
cwd,
|
275
|
+
allowTS,
|
276
|
+
configFile
|
277
|
+
});
|
278
|
+
|
279
|
+
const configFilePath = await configLoader.findConfigFileForPath(path.join(cwd, "__placeholder__.js"));
|
427
280
|
|
281
|
+
if (!configFilePath) {
|
282
|
+
throw new Error("No ESLint configuration file was found.");
|
428
283
|
}
|
429
284
|
|
430
285
|
return {
|
431
286
|
configFilePath,
|
432
|
-
basePath
|
433
|
-
error
|
287
|
+
basePath: configFile ? cwd : path.dirname(configFilePath)
|
434
288
|
};
|
435
|
-
|
436
|
-
}
|
437
|
-
|
438
|
-
/**
|
439
|
-
* Calculates the config array for this run based on inputs.
|
440
|
-
* @param {ESLint} eslint The instance to create the config array for.
|
441
|
-
* @param {ESLintOptions} options The ESLint instance options.
|
442
|
-
* @returns {Promise<typeof FlatConfigArray>} The config array for `eslint``.
|
443
|
-
*/
|
444
|
-
async function calculateConfigArray(eslint, {
|
445
|
-
cwd,
|
446
|
-
baseConfig,
|
447
|
-
overrideConfig,
|
448
|
-
configFile,
|
449
|
-
ignore: shouldIgnore,
|
450
|
-
ignorePatterns
|
451
|
-
}) {
|
452
|
-
|
453
|
-
// check for cached instance
|
454
|
-
const slots = privateMembers.get(eslint);
|
455
|
-
|
456
|
-
if (slots.configs) {
|
457
|
-
return slots.configs;
|
458
|
-
}
|
459
|
-
|
460
|
-
const hasUnstableTSConfigFlag = eslint.hasFlag("unstable_ts_config");
|
461
|
-
|
462
|
-
const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }, hasUnstableTSConfigFlag);
|
463
|
-
|
464
|
-
// config file is required to calculate config
|
465
|
-
if (error) {
|
466
|
-
throw error;
|
467
|
-
}
|
468
|
-
|
469
|
-
const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore });
|
470
|
-
|
471
|
-
// load config file
|
472
|
-
if (configFilePath) {
|
473
|
-
const fileConfig = await loadFlatConfigFile(configFilePath, hasUnstableTSConfigFlag);
|
474
|
-
|
475
|
-
if (Array.isArray(fileConfig)) {
|
476
|
-
configs.push(...fileConfig);
|
477
|
-
} else {
|
478
|
-
configs.push(fileConfig);
|
479
|
-
}
|
480
|
-
}
|
481
|
-
|
482
|
-
// add in any configured defaults
|
483
|
-
configs.push(...slots.defaultConfigs);
|
484
|
-
|
485
|
-
// append command line ignore patterns
|
486
|
-
if (ignorePatterns && ignorePatterns.length > 0) {
|
487
|
-
|
488
|
-
let relativeIgnorePatterns;
|
489
|
-
|
490
|
-
/*
|
491
|
-
* If the config file basePath is different than the cwd, then
|
492
|
-
* the ignore patterns won't work correctly. Here, we adjust the
|
493
|
-
* ignore pattern to include the correct relative path. Patterns
|
494
|
-
* passed as `ignorePatterns` are relative to the cwd, whereas
|
495
|
-
* the config file basePath can be an ancestor of the cwd.
|
496
|
-
*/
|
497
|
-
if (basePath === cwd) {
|
498
|
-
relativeIgnorePatterns = ignorePatterns;
|
499
|
-
} else {
|
500
|
-
|
501
|
-
// In minimatch patterns, only `/` can be used as path separator
|
502
|
-
const relativeIgnorePath = path.relative(basePath, cwd).replaceAll(path.sep, "/");
|
503
|
-
|
504
|
-
relativeIgnorePatterns = ignorePatterns.map(pattern => {
|
505
|
-
const negated = pattern.startsWith("!");
|
506
|
-
const basePattern = negated ? pattern.slice(1) : pattern;
|
507
|
-
|
508
|
-
return (negated ? "!" : "") +
|
509
|
-
path.posix.join(relativeIgnorePath, basePattern);
|
510
|
-
});
|
511
|
-
}
|
512
|
-
|
513
|
-
/*
|
514
|
-
* Ignore patterns are added to the end of the config array
|
515
|
-
* so they can override default ignores.
|
516
|
-
*/
|
517
|
-
configs.push({
|
518
|
-
ignores: relativeIgnorePatterns
|
519
|
-
});
|
520
|
-
}
|
521
|
-
|
522
|
-
if (overrideConfig) {
|
523
|
-
if (Array.isArray(overrideConfig)) {
|
524
|
-
configs.push(...overrideConfig);
|
525
|
-
} else {
|
526
|
-
configs.push(overrideConfig);
|
527
|
-
}
|
528
|
-
}
|
529
|
-
|
530
|
-
await configs.normalize();
|
531
|
-
|
532
|
-
// cache the config array for this instance
|
533
|
-
slots.configs = configs;
|
534
|
-
|
535
|
-
return configs;
|
536
289
|
}
|
537
290
|
|
538
291
|
/**
|
@@ -678,6 +431,12 @@ class ESLint {
|
|
678
431
|
*/
|
679
432
|
static configType = "flat";
|
680
433
|
|
434
|
+
/**
|
435
|
+
* The loader to use for finding config files.
|
436
|
+
* @type {ConfigLoader|LegacyConfigLoader}
|
437
|
+
*/
|
438
|
+
#configLoader;
|
439
|
+
|
681
440
|
/**
|
682
441
|
* Creates a new instance of the main ESLint API.
|
683
442
|
* @param {ESLintOptions} options The options for this instance.
|
@@ -701,15 +460,34 @@ class ESLint {
|
|
701
460
|
? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy)
|
702
461
|
: null;
|
703
462
|
|
463
|
+
const configLoaderOptions = {
|
464
|
+
cwd: processedOptions.cwd,
|
465
|
+
baseConfig: processedOptions.baseConfig,
|
466
|
+
overrideConfig: processedOptions.overrideConfig,
|
467
|
+
configFile: processedOptions.configFile,
|
468
|
+
ignoreEnabled: processedOptions.ignore,
|
469
|
+
ignorePatterns: processedOptions.ignorePatterns,
|
470
|
+
defaultConfigs,
|
471
|
+
allowTS: processedOptions.flags.includes("unstable_ts_config")
|
472
|
+
};
|
473
|
+
|
474
|
+
this.#configLoader = processedOptions.flags.includes("unstable_config_lookup_from_file")
|
475
|
+
? new ConfigLoader(configLoaderOptions)
|
476
|
+
: new LegacyConfigLoader(configLoaderOptions);
|
477
|
+
|
478
|
+
debug(`Using config loader ${this.#configLoader.constructor.name}`);
|
479
|
+
|
704
480
|
privateMembers.set(this, {
|
705
481
|
options: processedOptions,
|
706
482
|
linter,
|
707
483
|
cacheFilePath,
|
708
484
|
lintResultCache,
|
709
485
|
defaultConfigs,
|
710
|
-
configs: null
|
486
|
+
configs: null,
|
487
|
+
configLoader: this.#configLoader
|
711
488
|
});
|
712
489
|
|
490
|
+
|
713
491
|
/**
|
714
492
|
* If additional plugins are passed in, add that to the default
|
715
493
|
* configs for this instance.
|
@@ -813,20 +591,10 @@ class ESLint {
|
|
813
591
|
|
814
592
|
const resultRules = new Map();
|
815
593
|
const {
|
816
|
-
|
594
|
+
configLoader,
|
817
595
|
options: { cwd }
|
818
596
|
} = privateMembers.get(this);
|
819
597
|
|
820
|
-
/*
|
821
|
-
* We can only accurately return rules meta information for linting results if the
|
822
|
-
* results were created by this instance. Otherwise, the necessary rules data is
|
823
|
-
* not available. So if the config array doesn't already exist, just throw an error
|
824
|
-
* to let the user know we can't do anything here.
|
825
|
-
*/
|
826
|
-
if (!configs) {
|
827
|
-
throw createExtraneousResultsError();
|
828
|
-
}
|
829
|
-
|
830
598
|
for (const result of results) {
|
831
599
|
|
832
600
|
/*
|
@@ -845,6 +613,14 @@ class ESLint {
|
|
845
613
|
* All of the plugin and rule information is contained within the
|
846
614
|
* calculated config for the given file.
|
847
615
|
*/
|
616
|
+
let configs;
|
617
|
+
|
618
|
+
try {
|
619
|
+
configs = configLoader.getCachedConfigArrayForFile(filePath);
|
620
|
+
} catch {
|
621
|
+
throw createExtraneousResultsError();
|
622
|
+
}
|
623
|
+
|
848
624
|
const config = configs.getConfig(filePath);
|
849
625
|
|
850
626
|
if (!config) {
|
@@ -919,7 +695,6 @@ class ESLint {
|
|
919
695
|
|
920
696
|
debug(`Using file patterns: ${normalizedPatterns}`);
|
921
697
|
|
922
|
-
const configs = await calculateConfigArray(this, eslintOptions);
|
923
698
|
const {
|
924
699
|
allowInlineConfig,
|
925
700
|
cache,
|
@@ -955,7 +730,7 @@ class ESLint {
|
|
955
730
|
patterns: normalizedPatterns,
|
956
731
|
cwd,
|
957
732
|
globInputPaths,
|
958
|
-
|
733
|
+
configLoader: this.#configLoader,
|
959
734
|
errorOnUnmatchedPattern
|
960
735
|
});
|
961
736
|
const controller = new AbortController();
|
@@ -972,8 +747,9 @@ class ESLint {
|
|
972
747
|
*/
|
973
748
|
const results = await Promise.all(
|
974
749
|
|
975
|
-
filePaths.map(filePath => {
|
750
|
+
filePaths.map(async filePath => {
|
976
751
|
|
752
|
+
const configs = await this.#configLoader.loadConfigArrayForFile(filePath);
|
977
753
|
const config = configs.getConfig(filePath);
|
978
754
|
|
979
755
|
/*
|
@@ -1111,7 +887,6 @@ class ESLint {
|
|
1111
887
|
linter,
|
1112
888
|
options: eslintOptions
|
1113
889
|
} = privateMembers.get(this);
|
1114
|
-
const configs = await calculateConfigArray(this, eslintOptions);
|
1115
890
|
const {
|
1116
891
|
allowInlineConfig,
|
1117
892
|
cwd,
|
@@ -1125,21 +900,21 @@ class ESLint {
|
|
1125
900
|
const startTime = Date.now();
|
1126
901
|
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
|
1127
902
|
const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js");
|
1128
|
-
const
|
1129
|
-
|
1130
|
-
const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
|
903
|
+
const configs = await this.#configLoader.loadConfigArrayForFile(resolvedFilename);
|
904
|
+
const configStatus = configs?.getConfigStatus(resolvedFilename) ?? "unconfigured";
|
1131
905
|
|
1132
906
|
// Clear the last used config arrays.
|
1133
|
-
if (resolvedFilename &&
|
907
|
+
if (resolvedFilename && configStatus !== "matched") {
|
1134
908
|
const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored;
|
1135
909
|
|
1136
910
|
if (shouldWarnIgnored) {
|
1137
|
-
const configStatus = configs.getConfigStatus(resolvedFilename);
|
1138
|
-
|
1139
911
|
results.push(createIgnoreResult(resolvedFilename, cwd, configStatus));
|
1140
912
|
}
|
1141
913
|
} else {
|
1142
914
|
|
915
|
+
const config = configs.getConfig(resolvedFilename);
|
916
|
+
const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
|
917
|
+
|
1143
918
|
// Do lint.
|
1144
919
|
results.push(verifyText({
|
1145
920
|
text: code,
|
@@ -1271,7 +1046,14 @@ class ESLint {
|
|
1271
1046
|
}
|
1272
1047
|
const options = privateMembers.get(this).options;
|
1273
1048
|
const absolutePath = path.resolve(options.cwd, filePath);
|
1274
|
-
const configs = await
|
1049
|
+
const configs = await this.#configLoader.loadConfigArrayForFile(absolutePath);
|
1050
|
+
|
1051
|
+
if (!configs) {
|
1052
|
+
const error = new Error("Could not find config file.");
|
1053
|
+
|
1054
|
+
error.messageTemplate = "config-file-missing";
|
1055
|
+
throw error;
|
1056
|
+
}
|
1275
1057
|
|
1276
1058
|
return configs.getConfig(absolutePath);
|
1277
1059
|
}
|
@@ -1279,14 +1061,22 @@ class ESLint {
|
|
1279
1061
|
/**
|
1280
1062
|
* Finds the config file being used by this instance based on the options
|
1281
1063
|
* passed to the constructor.
|
1064
|
+
* @param {string} [filePath] The path of the file to find the config file for.
|
1282
1065
|
* @returns {Promise<string|undefined>} The path to the config file being used or
|
1283
1066
|
* `undefined` if no config file is being used.
|
1284
1067
|
*/
|
1285
|
-
|
1068
|
+
findConfigFile(filePath) {
|
1286
1069
|
const options = privateMembers.get(this).options;
|
1287
|
-
const { configFilePath } = await locateConfigFileToUse(options, this.hasFlag("unstable_ts_config"));
|
1288
1070
|
|
1289
|
-
|
1071
|
+
/*
|
1072
|
+
* Because the new config lookup scheme skips the current directory
|
1073
|
+
* and looks into the parent directories, we need to use a placeholder
|
1074
|
+
* directory to ensure the file in cwd is checked.
|
1075
|
+
*/
|
1076
|
+
const fakeCwd = path.join(options.cwd, "__placeholder__");
|
1077
|
+
|
1078
|
+
return this.#configLoader.findConfigFileForPath(filePath ?? fakeCwd)
|
1079
|
+
.catch(() => void 0);
|
1290
1080
|
}
|
1291
1081
|
|
1292
1082
|
/**
|
@@ -47,6 +47,8 @@ const { SourceCode } = require("../languages/js/source-code");
|
|
47
47
|
* @property {string} [name] Name for the test case.
|
48
48
|
* @property {string} code Code for the test case.
|
49
49
|
* @property {any[]} [options] Options for the test case.
|
50
|
+
* @property {Function} [before] Function to execute before testing the case.
|
51
|
+
* @property {Function} [after] Function to execute after testing the case regardless of its result.
|
50
52
|
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
51
53
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
52
54
|
* @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
|
@@ -61,6 +63,8 @@ const { SourceCode } = require("../languages/js/source-code");
|
|
61
63
|
* @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
|
62
64
|
* @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
|
63
65
|
* @property {any[]} [options] Options for the test case.
|
66
|
+
* @property {Function} [before] Function to execute before testing the case.
|
67
|
+
* @property {Function} [after] Function to execute after testing the case regardless of its result.
|
64
68
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
65
69
|
* @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
|
66
70
|
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
@@ -105,6 +109,8 @@ const RuleTesterParameters = [
|
|
105
109
|
"code",
|
106
110
|
"filename",
|
107
111
|
"options",
|
112
|
+
"before",
|
113
|
+
"after",
|
108
114
|
"errors",
|
109
115
|
"output",
|
110
116
|
"only"
|
@@ -621,6 +627,21 @@ class RuleTester {
|
|
621
627
|
...defaultConfig.slice(1)
|
622
628
|
];
|
623
629
|
|
630
|
+
/**
|
631
|
+
* Runs a hook on the given item when it's assigned to the given property
|
632
|
+
* @param {string|Object} item Item to run the hook on
|
633
|
+
* @param {string} prop The property having the hook assigned to
|
634
|
+
* @throws {Error} If the property is not a function or that function throws an error
|
635
|
+
* @returns {void}
|
636
|
+
* @private
|
637
|
+
*/
|
638
|
+
function runHook(item, prop) {
|
639
|
+
if (typeof item === "object" && hasOwnProperty(item, prop)) {
|
640
|
+
assert.strictEqual(typeof item[prop], "function", `Optional test case property '${prop}' must be a function`);
|
641
|
+
item[prop]();
|
642
|
+
}
|
643
|
+
}
|
644
|
+
|
624
645
|
/**
|
625
646
|
* Run the rule for the given item
|
626
647
|
* @param {string|Object} item Item to run the rule against
|
@@ -1258,7 +1279,12 @@ class RuleTester {
|
|
1258
1279
|
this.constructor[valid.only ? "itOnly" : "it"](
|
1259
1280
|
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
|
1260
1281
|
() => {
|
1261
|
-
|
1282
|
+
try {
|
1283
|
+
runHook(valid, "before");
|
1284
|
+
testValidTemplate(valid);
|
1285
|
+
} finally {
|
1286
|
+
runHook(valid, "after");
|
1287
|
+
}
|
1262
1288
|
}
|
1263
1289
|
);
|
1264
1290
|
});
|
@@ -1271,7 +1297,12 @@ class RuleTester {
|
|
1271
1297
|
this.constructor[invalid.only ? "itOnly" : "it"](
|
1272
1298
|
sanitize(invalid.name || invalid.code),
|
1273
1299
|
() => {
|
1274
|
-
|
1300
|
+
try {
|
1301
|
+
runHook(invalid, "before");
|
1302
|
+
testInvalidTemplate(invalid);
|
1303
|
+
} finally {
|
1304
|
+
runHook(invalid, "after");
|
1305
|
+
}
|
1275
1306
|
}
|
1276
1307
|
);
|
1277
1308
|
});
|
package/lib/rules/complexity.js
CHANGED
@@ -45,6 +45,9 @@ module.exports = {
|
|
45
45
|
max: {
|
46
46
|
type: "integer",
|
47
47
|
minimum: 0
|
48
|
+
},
|
49
|
+
variant: {
|
50
|
+
enum: ["classic", "modified"]
|
48
51
|
}
|
49
52
|
},
|
50
53
|
additionalProperties: false
|
@@ -61,16 +64,22 @@ module.exports = {
|
|
61
64
|
create(context) {
|
62
65
|
const option = context.options[0];
|
63
66
|
let THRESHOLD = 20;
|
67
|
+
let VARIANT = "classic";
|
68
|
+
|
69
|
+
if (typeof option === "object") {
|
70
|
+
if (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) {
|
71
|
+
THRESHOLD = option.maximum || option.max;
|
72
|
+
}
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
) {
|
69
|
-
THRESHOLD = option.maximum || option.max;
|
74
|
+
if (Object.hasOwn(option, "variant")) {
|
75
|
+
VARIANT = option.variant;
|
76
|
+
}
|
70
77
|
} else if (typeof option === "number") {
|
71
78
|
THRESHOLD = option;
|
72
79
|
}
|
73
80
|
|
81
|
+
const IS_MODIFIED_COMPLEXITY = VARIANT === "modified";
|
82
|
+
|
74
83
|
//--------------------------------------------------------------------------
|
75
84
|
// Helpers
|
76
85
|
//--------------------------------------------------------------------------
|
@@ -112,7 +121,8 @@ module.exports = {
|
|
112
121
|
AssignmentPattern: increaseComplexity,
|
113
122
|
|
114
123
|
// Avoid `default`
|
115
|
-
"SwitchCase[test]": increaseComplexity,
|
124
|
+
"SwitchCase[test]": () => IS_MODIFIED_COMPLEXITY || increaseComplexity(),
|
125
|
+
SwitchStatement: () => IS_MODIFIED_COMPLEXITY && increaseComplexity(),
|
116
126
|
|
117
127
|
// Logical assignment operators have short-circuiting behavior
|
118
128
|
AssignmentExpression(node) {
|
package/lib/shared/flags.js
CHANGED