eslint 9.27.0 → 9.29.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/conf/ecma-version.js +1 -1
- package/conf/globals.js +10 -0
- package/lib/cli.js +20 -23
- package/lib/config/config-loader.js +32 -21
- package/lib/config/config.js +34 -11
- package/lib/eslint/eslint.js +18 -21
- package/lib/languages/js/source-code/source-code.js +104 -27
- package/lib/linter/apply-disable-directives.js +2 -4
- package/lib/linter/code-path-analysis/code-path-analyzer.js +8 -9
- package/lib/linter/linter.js +30 -61
- package/lib/linter/source-code-traverser.js +327 -0
- package/lib/linter/source-code-visitor.js +81 -0
- package/lib/options.js +7 -0
- package/lib/rules/class-methods-use-this.js +7 -0
- package/lib/rules/func-style.js +57 -7
- package/lib/rules/no-implicit-globals.js +31 -15
- package/lib/rules/no-magic-numbers.js +98 -5
- package/lib/rules/no-promise-executor-return.js +4 -35
- package/lib/rules/no-restricted-globals.js +35 -2
- package/lib/rules/no-restricted-properties.js +24 -10
- package/lib/rules/no-setter-return.js +13 -48
- package/lib/rules/no-shadow.js +262 -6
- package/lib/rules/no-unassigned-vars.js +14 -6
- package/lib/rules/no-use-before-define.js +99 -1
- package/lib/rules/no-var.js +14 -2
- package/lib/rules/prefer-arrow-callback.js +9 -0
- package/lib/rules/prefer-regex-literals.js +1 -18
- package/lib/services/suppressions-service.js +8 -0
- package/lib/services/warning-service.js +85 -0
- package/lib/shared/naming.js +109 -0
- package/lib/shared/relative-module-resolver.js +28 -0
- package/lib/types/index.d.ts +18 -7
- package/lib/types/rules.d.ts +52 -2
- package/package.json +12 -10
- package/lib/linter/node-event-generator.js +0 -256
- package/lib/linter/safe-emitter.js +0 -52
package/README.md
CHANGED
@@ -329,7 +329,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
|
329
329
|
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
|
330
330
|
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a></p><h3>Silver Sponsors</h3>
|
331
331
|
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/e6d15e1/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301" alt="American Express" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
|
332
|
-
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nolebase.ayaka.io"><img src="https://avatars.githubusercontent.com/u/11081491" alt="Neko" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
|
332
|
+
<p><a href="https://sentry.io"><img src="https://github.com/getsentry.png" alt="Sentry" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nolebase.ayaka.io"><img src="https://avatars.githubusercontent.com/u/11081491" alt="Neko" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
|
333
333
|
<h3>Technology Sponsors</h3>
|
334
334
|
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
|
335
335
|
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
|
package/conf/ecma-version.js
CHANGED
package/conf/globals.js
CHANGED
@@ -135,6 +135,15 @@ const es2024 = {
|
|
135
135
|
|
136
136
|
const es2025 = {
|
137
137
|
...es2024,
|
138
|
+
Float16Array: false,
|
139
|
+
Iterator: false,
|
140
|
+
};
|
141
|
+
|
142
|
+
const es2026 = {
|
143
|
+
...es2025,
|
144
|
+
AsyncDisposableStack: false,
|
145
|
+
DisposableStack: false,
|
146
|
+
SuppressedError: false,
|
138
147
|
};
|
139
148
|
|
140
149
|
//-----------------------------------------------------------------------------
|
@@ -156,4 +165,5 @@ module.exports = {
|
|
156
165
|
es2023,
|
157
166
|
es2024,
|
158
167
|
es2025,
|
168
|
+
es2026,
|
159
169
|
};
|
package/lib/cli.js
CHANGED
@@ -28,13 +28,14 @@ const fs = require("node:fs"),
|
|
28
28
|
log = require("./shared/logging"),
|
29
29
|
RuntimeInfo = require("./shared/runtime-info"),
|
30
30
|
{ normalizeSeverityToString } = require("./shared/severity");
|
31
|
-
const {
|
32
|
-
Legacy: { naming },
|
33
|
-
} = require("@eslint/eslintrc");
|
34
31
|
const { ModuleImporter } = require("@humanwhocodes/module-importer");
|
35
32
|
const { getCacheFile } = require("./eslint/eslint-helpers");
|
36
33
|
const { SuppressionsService } = require("./services/suppressions-service");
|
37
34
|
const debug = require("debug")("eslint:cli");
|
35
|
+
const {
|
36
|
+
normalizePackageName,
|
37
|
+
getShorthandName,
|
38
|
+
} = require("./shared/naming.js");
|
38
39
|
|
39
40
|
//------------------------------------------------------------------------------
|
40
41
|
// Types
|
@@ -67,10 +68,7 @@ async function loadPlugins(importer, pluginNames) {
|
|
67
68
|
|
68
69
|
await Promise.all(
|
69
70
|
pluginNames.map(async pluginName => {
|
70
|
-
const longName =
|
71
|
-
pluginName,
|
72
|
-
"eslint-plugin",
|
73
|
-
);
|
71
|
+
const longName = normalizePackageName(pluginName, "eslint-plugin");
|
74
72
|
const module = await importer.import(longName);
|
75
73
|
|
76
74
|
if (!("default" in module)) {
|
@@ -79,10 +77,7 @@ async function loadPlugins(importer, pluginNames) {
|
|
79
77
|
);
|
80
78
|
}
|
81
79
|
|
82
|
-
const shortName =
|
83
|
-
pluginName,
|
84
|
-
"eslint-plugin",
|
85
|
-
);
|
80
|
+
const shortName = getShorthandName(pluginName, "eslint-plugin");
|
86
81
|
|
87
82
|
plugins[shortName] = module.default;
|
88
83
|
}),
|
@@ -439,10 +434,8 @@ const cli = {
|
|
439
434
|
debug("Using flat config?", usingFlatConfig);
|
440
435
|
|
441
436
|
if (allowFlatConfig && !usingFlatConfig) {
|
442
|
-
|
443
|
-
|
444
|
-
"ESLintRCWarning",
|
445
|
-
);
|
437
|
+
const { WarningService } = require("./services/warning-service");
|
438
|
+
new WarningService().emitESLintRCWarning();
|
446
439
|
}
|
447
440
|
|
448
441
|
const CLIOptions = createCLIOptions(usingFlatConfig);
|
@@ -736,17 +729,21 @@ const cli = {
|
|
736
729
|
);
|
737
730
|
}
|
738
731
|
|
739
|
-
|
740
|
-
|
732
|
+
if (!options.passOnUnprunedSuppressions) {
|
733
|
+
const unusedSuppressionsCount =
|
734
|
+
Object.keys(unusedSuppressions).length;
|
741
735
|
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
736
|
+
if (unusedSuppressionsCount > 0) {
|
737
|
+
log.error(
|
738
|
+
"There are suppressions left that do not occur anymore. Consider re-running the command with `--prune-suppressions`.",
|
739
|
+
);
|
740
|
+
debug(JSON.stringify(unusedSuppressions, null, 2));
|
741
|
+
|
742
|
+
return 2;
|
743
|
+
}
|
747
744
|
}
|
748
745
|
|
749
|
-
if (shouldExitForFatalErrors
|
746
|
+
if (shouldExitForFatalErrors) {
|
750
747
|
return 2;
|
751
748
|
}
|
752
749
|
|
@@ -15,6 +15,7 @@ const findUp = require("find-up");
|
|
15
15
|
const { pathToFileURL } = require("node:url");
|
16
16
|
const debug = require("debug")("eslint:config-loader");
|
17
17
|
const { FlatConfigArray } = require("./flat-config-array");
|
18
|
+
const { WarningService } = require("../services/warning-service");
|
18
19
|
|
19
20
|
//-----------------------------------------------------------------------------
|
20
21
|
// Types
|
@@ -32,6 +33,7 @@ const { FlatConfigArray } = require("./flat-config-array");
|
|
32
33
|
* @property {Array<string>} [ignorePatterns] The ignore patterns to use.
|
33
34
|
* @property {Config|Array<Config>} [overrideConfig] The override config to use.
|
34
35
|
* @property {boolean} [hasUnstableNativeNodeJsTSConfigFlag] The flag to indicate whether the `unstable_native_nodejs_ts_config` flag is enabled.
|
36
|
+
* @property {WarningService} [warningService] The warning service to use.
|
35
37
|
*/
|
36
38
|
|
37
39
|
//------------------------------------------------------------------------------
|
@@ -137,12 +139,13 @@ function isNativeTypeScriptSupportEnabled() {
|
|
137
139
|
* @since 9.24.0
|
138
140
|
*/
|
139
141
|
async function loadTypeScriptConfigFileWithJiti(filePath, fileURL, mtime) {
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
142
|
+
const { createJiti, version: jitiVersion } =
|
143
|
+
// eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing
|
144
|
+
await ConfigLoader.loadJiti().catch(() => {
|
145
|
+
throw new Error(
|
146
|
+
"The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.",
|
147
|
+
);
|
148
|
+
});
|
146
149
|
|
147
150
|
// `createJiti` was added in jiti v2.
|
148
151
|
if (typeof createJiti !== "function") {
|
@@ -155,11 +158,15 @@ async function loadTypeScriptConfigFileWithJiti(filePath, fileURL, mtime) {
|
|
155
158
|
* Disabling `moduleCache` allows us to reload a
|
156
159
|
* config file when the last modified timestamp changes.
|
157
160
|
*/
|
158
|
-
|
159
|
-
const jiti = createJiti(__filename, {
|
161
|
+
const jitiOptions = {
|
160
162
|
moduleCache: false,
|
161
|
-
|
162
|
-
|
163
|
+
};
|
164
|
+
|
165
|
+
if (jitiVersion.startsWith("2.1.")) {
|
166
|
+
jitiOptions.interopDefault = false;
|
167
|
+
}
|
168
|
+
|
169
|
+
const jiti = createJiti(__filename, jitiOptions);
|
163
170
|
const config = await jiti.import(fileURL.href);
|
164
171
|
|
165
172
|
importedConfigFileModificationTime.set(filePath, mtime);
|
@@ -301,7 +308,9 @@ class ConfigLoader {
|
|
301
308
|
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
302
309
|
*/
|
303
310
|
constructor(options) {
|
304
|
-
this.#options = options
|
311
|
+
this.#options = options.warningService
|
312
|
+
? options
|
313
|
+
: { ...options, warningService: new WarningService() };
|
305
314
|
}
|
306
315
|
|
307
316
|
/**
|
@@ -494,11 +503,12 @@ class ConfigLoader {
|
|
494
503
|
|
495
504
|
/**
|
496
505
|
* Used to import the jiti dependency. This method is exposed internally for testing purposes.
|
497
|
-
* @returns {Promise<
|
498
|
-
* or rejects with an error if jiti is not found.
|
506
|
+
* @returns {Promise<{createJiti: Function|undefined, version: string;}>} A promise that fulfills with an object containing the jiti module's createJiti function and version.
|
499
507
|
*/
|
500
|
-
static loadJiti() {
|
501
|
-
|
508
|
+
static async loadJiti() {
|
509
|
+
const { createJiti } = await import("jiti");
|
510
|
+
const version = require("jiti/package.json").version;
|
511
|
+
return { createJiti, version };
|
502
512
|
}
|
503
513
|
|
504
514
|
/**
|
@@ -561,6 +571,7 @@ class ConfigLoader {
|
|
561
571
|
overrideConfig,
|
562
572
|
hasUnstableNativeNodeJsTSConfigFlag = false,
|
563
573
|
defaultConfigs = [],
|
574
|
+
warningService,
|
564
575
|
} = options;
|
565
576
|
|
566
577
|
debug(
|
@@ -622,10 +633,7 @@ class ConfigLoader {
|
|
622
633
|
}
|
623
634
|
|
624
635
|
if (emptyConfig) {
|
625
|
-
|
626
|
-
`Running ESLint with an empty config (from ${configFilePath}). Please double-check that this is what you want. If you want to run ESLint with an empty config, export [{}] to remove this warning.`,
|
627
|
-
"ESLintEmptyConfigWarning",
|
628
|
-
);
|
636
|
+
warningService.emitEmptyConfigWarning(configFilePath);
|
629
637
|
}
|
630
638
|
}
|
631
639
|
|
@@ -713,8 +721,11 @@ class LegacyConfigLoader extends ConfigLoader {
|
|
713
721
|
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
714
722
|
*/
|
715
723
|
constructor(options) {
|
716
|
-
|
717
|
-
|
724
|
+
const normalizedOptions = options.warningService
|
725
|
+
? options
|
726
|
+
: { ...options, warningService: new WarningService() };
|
727
|
+
super(normalizedOptions);
|
728
|
+
this.#options = normalizedOptions;
|
718
729
|
}
|
719
730
|
|
720
731
|
/**
|
package/lib/config/config.js
CHANGED
@@ -265,6 +265,27 @@ function getObjectId(object) {
|
|
265
265
|
return name;
|
266
266
|
}
|
267
267
|
|
268
|
+
/**
|
269
|
+
* Asserts that a value is not a function.
|
270
|
+
* @param {any} value The value to check.
|
271
|
+
* @param {string} key The key of the value in the object.
|
272
|
+
* @param {string} objectKey The key of the object being checked.
|
273
|
+
* @returns {void}
|
274
|
+
* @throws {TypeError} If the value is a function.
|
275
|
+
*/
|
276
|
+
function assertNotFunction(value, key, objectKey) {
|
277
|
+
if (typeof value === "function") {
|
278
|
+
const error = new TypeError(
|
279
|
+
`Cannot serialize key "${key}" in "${objectKey}": Function values are not supported.`,
|
280
|
+
);
|
281
|
+
|
282
|
+
error.messageTemplate = "config-serialize-function";
|
283
|
+
error.messageData = { key, objectKey };
|
284
|
+
|
285
|
+
throw error;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
268
289
|
/**
|
269
290
|
* Converts a languageOptions object to a JSON representation.
|
270
291
|
* @param {Record<string, any>} languageOptions The options to create a JSON
|
@@ -274,6 +295,14 @@ function getObjectId(object) {
|
|
274
295
|
* @throws {TypeError} If a function is found in the languageOptions.
|
275
296
|
*/
|
276
297
|
function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
|
298
|
+
if (typeof languageOptions.toJSON === "function") {
|
299
|
+
const result = languageOptions.toJSON();
|
300
|
+
|
301
|
+
assertNotFunction(result, "toJSON", objectKey);
|
302
|
+
|
303
|
+
return result;
|
304
|
+
}
|
305
|
+
|
277
306
|
const result = {};
|
278
307
|
|
279
308
|
for (const [key, value] of Object.entries(languageOptions)) {
|
@@ -281,7 +310,10 @@ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
|
|
281
310
|
if (typeof value === "object") {
|
282
311
|
const name = getObjectId(value);
|
283
312
|
|
284
|
-
if (
|
313
|
+
if (typeof value.toJSON === "function") {
|
314
|
+
result[key] = value.toJSON();
|
315
|
+
assertNotFunction(result[key], key, objectKey);
|
316
|
+
} else if (name && hasMethod(value)) {
|
285
317
|
result[key] = name;
|
286
318
|
} else {
|
287
319
|
result[key] = languageOptionsToJSON(value, key);
|
@@ -289,16 +321,7 @@ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
|
|
289
321
|
continue;
|
290
322
|
}
|
291
323
|
|
292
|
-
|
293
|
-
const error = new TypeError(
|
294
|
-
`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`,
|
295
|
-
);
|
296
|
-
|
297
|
-
error.messageTemplate = "config-serialize-function";
|
298
|
-
error.messageData = { key, objectKey };
|
299
|
-
|
300
|
-
throw error;
|
301
|
-
}
|
324
|
+
assertNotFunction(value, key, objectKey);
|
302
325
|
}
|
303
326
|
|
304
327
|
result[key] = value;
|
package/lib/eslint/eslint.js
CHANGED
@@ -15,13 +15,6 @@ const path = require("node:path");
|
|
15
15
|
const { version } = require("../../package.json");
|
16
16
|
const { Linter } = require("../linter");
|
17
17
|
const { defaultConfig } = require("../config/default-config");
|
18
|
-
const {
|
19
|
-
Legacy: {
|
20
|
-
ConfigOps: { getRuleSeverity },
|
21
|
-
ModuleResolver,
|
22
|
-
naming,
|
23
|
-
},
|
24
|
-
} = require("@eslint/eslintrc");
|
25
18
|
|
26
19
|
const {
|
27
20
|
findFiles,
|
@@ -40,6 +33,14 @@ const { pathToFileURL } = require("node:url");
|
|
40
33
|
const LintResultCache = require("../cli-engine/lint-result-cache");
|
41
34
|
const { Retrier } = require("@humanwhocodes/retry");
|
42
35
|
const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
|
36
|
+
const { WarningService } = require("../services/warning-service");
|
37
|
+
const { Config } = require("../config/config.js");
|
38
|
+
const {
|
39
|
+
getShorthandName,
|
40
|
+
getNamespaceFromTerm,
|
41
|
+
normalizePackageName,
|
42
|
+
} = require("../shared/naming.js");
|
43
|
+
const { resolve } = require("../shared/relative-module-resolver.js");
|
43
44
|
|
44
45
|
/*
|
45
46
|
* This is necessary to allow overwriting writeFile for testing purposes.
|
@@ -159,7 +160,7 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
|
|
159
160
|
|
160
161
|
if (config.rules) {
|
161
162
|
for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
|
162
|
-
if (
|
163
|
+
if (Config.getRuleNumericSeverity(ruleConf) === 0) {
|
163
164
|
continue;
|
164
165
|
}
|
165
166
|
const rule = config.getRuleDefinition(ruleId);
|
@@ -173,7 +174,7 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
|
|
173
174
|
replacedBy: usesNewFormat
|
174
175
|
? (meta.deprecated.replacedBy?.map(
|
175
176
|
replacement =>
|
176
|
-
`${replacement.plugin?.name !== void 0 ? `${
|
177
|
+
`${replacement.plugin?.name !== void 0 ? `${getShorthandName(replacement.plugin.name, "eslint-plugin")}/` : ""}${replacement.rule?.name ?? ""}`,
|
177
178
|
) ?? [])
|
178
179
|
: meta.replacedBy || [],
|
179
180
|
info: usesNewFormat ? meta.deprecated : void 0,
|
@@ -431,10 +432,12 @@ class ESLint {
|
|
431
432
|
constructor(options = {}) {
|
432
433
|
const defaultConfigs = [];
|
433
434
|
const processedOptions = processOptions(options);
|
435
|
+
const warningService = new WarningService();
|
434
436
|
const linter = new Linter({
|
435
437
|
cwd: processedOptions.cwd,
|
436
438
|
configType: "flat",
|
437
439
|
flags: mergeEnvironmentFlags(processedOptions.flags),
|
440
|
+
warningService,
|
438
441
|
});
|
439
442
|
|
440
443
|
const cacheFilePath = getCacheFile(
|
@@ -457,6 +460,7 @@ class ESLint {
|
|
457
460
|
hasUnstableNativeNodeJsTSConfigFlag: linter.hasFlag(
|
458
461
|
"unstable_native_nodejs_ts_config",
|
459
462
|
),
|
463
|
+
warningService,
|
460
464
|
};
|
461
465
|
|
462
466
|
this.#configLoader = linter.hasFlag("unstable_config_lookup_from_file")
|
@@ -485,8 +489,7 @@ class ESLint {
|
|
485
489
|
for (const [pluginName, plugin] of Object.entries(
|
486
490
|
options.plugins,
|
487
491
|
)) {
|
488
|
-
plugins[
|
489
|
-
plugin;
|
492
|
+
plugins[getShorthandName(pluginName, "eslint-plugin")] = plugin;
|
490
493
|
}
|
491
494
|
|
492
495
|
defaultConfigs.push({
|
@@ -496,10 +499,7 @@ class ESLint {
|
|
496
499
|
|
497
500
|
// Check for the .eslintignore file, and warn if it's present.
|
498
501
|
if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) {
|
499
|
-
|
500
|
-
'The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files',
|
501
|
-
"ESLintIgnoreWarning",
|
502
|
-
);
|
502
|
+
warningService.emitESLintIgnoreWarning();
|
503
503
|
}
|
504
504
|
}
|
505
505
|
|
@@ -997,7 +997,7 @@ class ESLint {
|
|
997
997
|
|
998
998
|
// replace \ with / for Windows compatibility
|
999
999
|
const normalizedFormatName = name.replace(/\\/gu, "/");
|
1000
|
-
const namespace =
|
1000
|
+
const namespace = getNamespaceFromTerm(normalizedFormatName);
|
1001
1001
|
|
1002
1002
|
// grab our options
|
1003
1003
|
const { cwd } = privateMembers.get(this).options;
|
@@ -1009,16 +1009,13 @@ class ESLint {
|
|
1009
1009
|
formatterPath = path.resolve(cwd, normalizedFormatName);
|
1010
1010
|
} else {
|
1011
1011
|
try {
|
1012
|
-
const npmFormat =
|
1012
|
+
const npmFormat = normalizePackageName(
|
1013
1013
|
normalizedFormatName,
|
1014
1014
|
"eslint-formatter",
|
1015
1015
|
);
|
1016
1016
|
|
1017
1017
|
// TODO: This is pretty dirty...would be nice to clean up at some point.
|
1018
|
-
formatterPath =
|
1019
|
-
npmFormat,
|
1020
|
-
getPlaceholderPath(cwd),
|
1021
|
-
);
|
1018
|
+
formatterPath = resolve(npmFormat, getPlaceholderPath(cwd));
|
1022
1019
|
} catch {
|
1023
1020
|
formatterPath = path.resolve(
|
1024
1021
|
__dirname,
|
@@ -15,7 +15,6 @@ const { isCommentToken } = require("@eslint-community/eslint-utils"),
|
|
15
15
|
globals = require("../../../../conf/globals"),
|
16
16
|
{ directivesPattern } = require("../../../shared/directives"),
|
17
17
|
CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"),
|
18
|
-
createEmitter = require("../../../linter/safe-emitter"),
|
19
18
|
{
|
20
19
|
ConfigCommentParser,
|
21
20
|
VisitNodeStep,
|
@@ -40,16 +39,6 @@ const { isCommentToken } = require("@eslint-community/eslint-utils"),
|
|
40
39
|
|
41
40
|
const commentParser = new ConfigCommentParser();
|
42
41
|
|
43
|
-
const CODE_PATH_EVENTS = [
|
44
|
-
"onCodePathStart",
|
45
|
-
"onCodePathEnd",
|
46
|
-
"onCodePathSegmentStart",
|
47
|
-
"onCodePathSegmentEnd",
|
48
|
-
"onCodePathSegmentLoop",
|
49
|
-
"onUnreachableCodePathSegmentStart",
|
50
|
-
"onUnreachableCodePathSegmentEnd",
|
51
|
-
];
|
52
|
-
|
53
42
|
/**
|
54
43
|
* Validates that the given AST has the required information.
|
55
44
|
* @param {ASTNode} ast The Program node of the AST to check.
|
@@ -240,6 +229,33 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) {
|
|
240
229
|
return false;
|
241
230
|
}
|
242
231
|
|
232
|
+
/**
|
233
|
+
* Performs binary search to find the line number containing a given character index.
|
234
|
+
* Returns the lower bound - the index of the first element greater than the target.
|
235
|
+
* **Please note that the `lineStartIndices` should be sorted in ascending order**.
|
236
|
+
* - Time Complexity: O(log n) - Significantly faster than linear search for large files.
|
237
|
+
* @param {number[]} lineStartIndices Sorted array of line start indices.
|
238
|
+
* @param {number} target The character index to find the line number for.
|
239
|
+
* @returns {number} The 1-based line number for the target index.
|
240
|
+
* @private
|
241
|
+
*/
|
242
|
+
function findLineNumberBinarySearch(lineStartIndices, target) {
|
243
|
+
let low = 0;
|
244
|
+
let high = lineStartIndices.length;
|
245
|
+
|
246
|
+
while (low < high) {
|
247
|
+
const mid = ((low + high) / 2) | 0; // Use bitwise OR to floor the division
|
248
|
+
|
249
|
+
if (target < lineStartIndices[mid]) {
|
250
|
+
high = mid;
|
251
|
+
} else {
|
252
|
+
low = mid + 1;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
return low;
|
257
|
+
}
|
258
|
+
|
243
259
|
//-----------------------------------------------------------------------------
|
244
260
|
// Directive Comments
|
245
261
|
//-----------------------------------------------------------------------------
|
@@ -316,6 +332,36 @@ function addDeclaredGlobals(
|
|
316
332
|
|
317
333
|
return true;
|
318
334
|
});
|
335
|
+
|
336
|
+
/*
|
337
|
+
* "implicit" contains information about implicit global variables (those created
|
338
|
+
* implicitly by assigning values to undeclared variables in non-strict code).
|
339
|
+
* Since we augment the global scope using configuration, we need to remove
|
340
|
+
* the ones that were added by configuration, as they are either built-in
|
341
|
+
* or declared elsewhere, therefore not implicit.
|
342
|
+
* Since the "implicit" property was not documented, first we'll check if it exists
|
343
|
+
* because it's possible that not all custom scope managers create this property.
|
344
|
+
* If it exists, we assume it has properties `variables` and `set`. Property
|
345
|
+
* `left` is considered optional (for example, typescript-eslint's scope manage
|
346
|
+
* has this property named `leftToBeResolved`).
|
347
|
+
*/
|
348
|
+
const { implicit } = globalScope;
|
349
|
+
if (typeof implicit === "object" && implicit !== null) {
|
350
|
+
implicit.variables = implicit.variables.filter(variable => {
|
351
|
+
const name = variable.name;
|
352
|
+
if (globalScope.set.has(name)) {
|
353
|
+
implicit.set.delete(name);
|
354
|
+
return false;
|
355
|
+
}
|
356
|
+
return true;
|
357
|
+
});
|
358
|
+
|
359
|
+
if (implicit.left) {
|
360
|
+
implicit.left = implicit.left.filter(
|
361
|
+
reference => !globalScope.set.has(reference.identifier.name),
|
362
|
+
);
|
363
|
+
}
|
364
|
+
}
|
319
365
|
}
|
320
366
|
|
321
367
|
/**
|
@@ -392,6 +438,7 @@ class SourceCode extends TokenStore {
|
|
392
438
|
["scopes", new WeakMap()],
|
393
439
|
["vars", new Map()],
|
394
440
|
["configNodes", void 0],
|
441
|
+
["isGlobalReference", new WeakMap()],
|
395
442
|
]);
|
396
443
|
|
397
444
|
/**
|
@@ -690,9 +737,9 @@ class SourceCode extends TokenStore {
|
|
690
737
|
|
691
738
|
/**
|
692
739
|
* Converts a source text index into a (line, column) pair.
|
693
|
-
* @param {number} index The index of a character in a file
|
740
|
+
* @param {number} index The index of a character in a file.
|
694
741
|
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
|
695
|
-
* @returns {{line: number, column: number}} A {line, column} location object with
|
742
|
+
* @returns {{line: number, column: number}} A {line, column} location object with 1-indexed line and 0-indexed column.
|
696
743
|
* @public
|
697
744
|
*/
|
698
745
|
getLocFromIndex(index) {
|
@@ -727,7 +774,7 @@ class SourceCode extends TokenStore {
|
|
727
774
|
const lineNumber =
|
728
775
|
index >= this.lineStartIndices.at(-1)
|
729
776
|
? this.lineStartIndices.length
|
730
|
-
: this.lineStartIndices
|
777
|
+
: findLineNumberBinarySearch(this.lineStartIndices, index);
|
731
778
|
|
732
779
|
return {
|
733
780
|
line: lineNumber,
|
@@ -872,6 +919,41 @@ class SourceCode extends TokenStore {
|
|
872
919
|
return ancestorsStartingAtParent.reverse();
|
873
920
|
}
|
874
921
|
|
922
|
+
/**
|
923
|
+
* Determines whether the given identifier node is a reference to a global variable.
|
924
|
+
* @param {ASTNode} node `Identifier` node to check.
|
925
|
+
* @returns {boolean} True if the identifier is a reference to a global variable.
|
926
|
+
*/
|
927
|
+
isGlobalReference(node) {
|
928
|
+
if (!node) {
|
929
|
+
throw new TypeError("Missing required argument: node.");
|
930
|
+
}
|
931
|
+
|
932
|
+
const cache = this[caches].get("isGlobalReference");
|
933
|
+
|
934
|
+
if (cache.has(node)) {
|
935
|
+
return cache.get(node);
|
936
|
+
}
|
937
|
+
|
938
|
+
if (node.type !== "Identifier") {
|
939
|
+
cache.set(node, false);
|
940
|
+
return false;
|
941
|
+
}
|
942
|
+
|
943
|
+
const variable = this.scopeManager.scopes[0].set.get(node.name);
|
944
|
+
|
945
|
+
if (!variable || variable.defs.length > 0) {
|
946
|
+
cache.set(node, false);
|
947
|
+
return false;
|
948
|
+
}
|
949
|
+
|
950
|
+
const result = variable.references.some(
|
951
|
+
({ identifier }) => identifier === node,
|
952
|
+
);
|
953
|
+
cache.set(node, result);
|
954
|
+
return result;
|
955
|
+
}
|
956
|
+
|
875
957
|
/**
|
876
958
|
* Returns the location of the given node or token.
|
877
959
|
* @param {ASTNode|Token} nodeOrToken The node or token to get the location of.
|
@@ -1209,7 +1291,6 @@ class SourceCode extends TokenStore {
|
|
1209
1291
|
* custom parsers to return any AST, we need to ensure that the traversal
|
1210
1292
|
* logic works for any AST.
|
1211
1293
|
*/
|
1212
|
-
const emitter = createEmitter();
|
1213
1294
|
let analyzer = {
|
1214
1295
|
enterNode(node) {
|
1215
1296
|
steps.push(
|
@@ -1229,7 +1310,14 @@ class SourceCode extends TokenStore {
|
|
1229
1310
|
}),
|
1230
1311
|
);
|
1231
1312
|
},
|
1232
|
-
|
1313
|
+
emit(eventName, args) {
|
1314
|
+
steps.push(
|
1315
|
+
new CallMethodStep({
|
1316
|
+
target: eventName,
|
1317
|
+
args,
|
1318
|
+
}),
|
1319
|
+
);
|
1320
|
+
},
|
1233
1321
|
};
|
1234
1322
|
|
1235
1323
|
/*
|
@@ -1243,17 +1331,6 @@ class SourceCode extends TokenStore {
|
|
1243
1331
|
*/
|
1244
1332
|
if (this.isESTree) {
|
1245
1333
|
analyzer = new CodePathAnalyzer(analyzer);
|
1246
|
-
|
1247
|
-
CODE_PATH_EVENTS.forEach(eventName => {
|
1248
|
-
emitter.on(eventName, (...args) => {
|
1249
|
-
steps.push(
|
1250
|
-
new CallMethodStep({
|
1251
|
-
target: eventName,
|
1252
|
-
args,
|
1253
|
-
}),
|
1254
|
-
);
|
1255
|
-
});
|
1256
|
-
});
|
1257
1334
|
}
|
1258
1335
|
|
1259
1336
|
/*
|
@@ -19,9 +19,7 @@
|
|
19
19
|
//------------------------------------------------------------------------------
|
20
20
|
|
21
21
|
const escapeRegExp = require("escape-string-regexp");
|
22
|
-
const {
|
23
|
-
Legacy: { ConfigOps },
|
24
|
-
} = require("@eslint/eslintrc/universal");
|
22
|
+
const { Config } = require("../config/config.js");
|
25
23
|
|
26
24
|
/**
|
27
25
|
* Compares the locations of two objects in a source file
|
@@ -539,7 +537,7 @@ module.exports = ({
|
|
539
537
|
configuredRules && ruleFilter
|
540
538
|
? new Set(
|
541
539
|
Object.keys(configuredRules).filter(ruleId => {
|
542
|
-
const severity =
|
540
|
+
const severity = Config.getRuleNumericSeverity(
|
543
541
|
configuredRules[ruleId],
|
544
542
|
);
|
545
543
|
|