J'ai juste update le cutie.js, CONNARD
Ah oui, j'ai aussi add le info.js, qui est merdique d'ailleurs
This commit is contained in:
Syxpi
2025-10-11 22:06:46 +02:00
parent 14d4df5a40
commit b5cba1c318
283 changed files with 15040 additions and 12924 deletions

View File

@@ -51,7 +51,7 @@ const debug = require("debug")("eslint:file-enumerator");
//------------------------------------------------------------------------------
const minimatchOpts = { dot: true, matchBase: true };
const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;
const dotfilesPattern = /(?:^\.|[/\\]\.)[^/\\.].*/u;
const NONE = 0;
const IGNORED_SILENTLY = 1;
const IGNORED = 2;

328
node_modules/eslint/lib/cli.js generated vendored
View File

@@ -16,8 +16,9 @@
//------------------------------------------------------------------------------
const fs = require("node:fs"),
{ mkdir, stat, writeFile } = require("node:fs/promises"),
path = require("node:path"),
{ promisify } = require("node:util"),
{ pathToFileURL } = require("node:url"),
{ LegacyESLint } = require("./eslint"),
{
ESLint,
@@ -27,282 +28,23 @@ const fs = require("node:fs"),
createCLIOptions = require("./options"),
log = require("./shared/logging"),
RuntimeInfo = require("./shared/runtime-info"),
{ normalizeSeverityToString } = require("./shared/severity");
const { ModuleImporter } = require("@humanwhocodes/module-importer");
translateOptions = require("./shared/translate-cli-options");
const { getCacheFile } = require("./eslint/eslint-helpers");
const { SuppressionsService } = require("./services/suppressions-service");
const debug = require("debug")("eslint:cli");
const {
normalizePackageName,
getShorthandName,
} = require("./shared/naming.js");
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @import { ESLintOptions } from "./eslint/eslint.js" */
/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
/** @typedef {import("./types").Linter.LintMessage} LintMessage */
/** @typedef {import("./types").ESLint.LintResult} LintResult */
/** @typedef {import("./types").ESLint.Plugin} Plugin */
/** @typedef {import("./types").ESLint.ResultsMeta} ResultsMeta */
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const mkdir = promisify(fs.mkdir);
const stat = promisify(fs.stat);
const writeFile = promisify(fs.writeFile);
/**
* Loads plugins with the specified names.
* @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
* @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
* @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
*/
async function loadPlugins(importer, pluginNames) {
const plugins = {};
await Promise.all(
pluginNames.map(async pluginName => {
const longName = normalizePackageName(pluginName, "eslint-plugin");
const module = await importer.import(longName);
if (!("default" in module)) {
throw new Error(
`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`,
);
}
const shortName = getShorthandName(pluginName, "eslint-plugin");
plugins[shortName] = module.default;
}),
);
return plugins;
}
/**
* Predicate function for whether or not to apply fixes in quiet mode.
* If a message is a warning, do not apply a fix.
* @param {LintMessage} message The lint result.
* @returns {boolean} True if the lint message is an error (and thus should be
* autofixed), false otherwise.
*/
function quietFixPredicate(message) {
return message.severity === 2;
}
/**
* Predicate function for whether or not to run a rule in quiet mode.
* If a rule is set to warning, do not run it.
* @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
* @returns {boolean} True if the lint rule should run, false otherwise.
*/
function quietRuleFilter(rule) {
return rule.severity === 2;
}
/**
* Translates the CLI options into the options expected by the ESLint constructor.
* @param {ParsedCLIOptions} cliOptions The CLI options to translate.
* @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
* config to generate.
* @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
* @private
*/
async function translateOptions(
{
cache,
cacheFile,
cacheLocation,
cacheStrategy,
config,
configLookup,
env,
errorOnUnmatchedPattern,
eslintrc,
ext,
fix,
fixDryRun,
fixType,
flag,
global,
ignore,
ignorePath,
ignorePattern,
inlineConfig,
parser,
parserOptions,
plugin,
quiet,
reportUnusedDisableDirectives,
reportUnusedDisableDirectivesSeverity,
reportUnusedInlineConfigs,
resolvePluginsRelativeTo,
rule,
rulesdir,
stats,
warnIgnored,
passOnNoPatterns,
maxWarnings,
},
configType,
) {
let overrideConfig, overrideConfigFile;
const importer = new ModuleImporter();
if (configType === "flat") {
overrideConfigFile =
typeof config === "string" ? config : !configLookup;
if (overrideConfigFile === false) {
overrideConfigFile = void 0;
}
const languageOptions = {};
if (global) {
languageOptions.globals = global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, {});
}
if (parserOptions) {
languageOptions.parserOptions = parserOptions;
}
if (parser) {
languageOptions.parser = await importer.import(parser);
}
overrideConfig = [
{
...(Object.keys(languageOptions).length > 0
? { languageOptions }
: {}),
rules: rule ? rule : {},
},
];
if (
reportUnusedDisableDirectives ||
reportUnusedDisableDirectivesSeverity !== void 0
) {
overrideConfig[0].linterOptions = {
reportUnusedDisableDirectives: reportUnusedDisableDirectives
? "error"
: normalizeSeverityToString(
reportUnusedDisableDirectivesSeverity,
),
};
}
if (reportUnusedInlineConfigs !== void 0) {
overrideConfig[0].linterOptions = {
...overrideConfig[0].linterOptions,
reportUnusedInlineConfigs: normalizeSeverityToString(
reportUnusedInlineConfigs,
),
};
}
if (plugin) {
overrideConfig[0].plugins = await loadPlugins(importer, plugin);
}
if (ext) {
overrideConfig.push({
files: ext.map(
extension =>
`**/*${extension.startsWith(".") ? "" : "."}${extension}`,
),
});
}
} else {
overrideConfigFile = config;
overrideConfig = {
env:
env &&
env.reduce((obj, name) => {
obj[name] = true;
return obj;
}, {}),
globals:
global &&
global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, {}),
ignorePatterns: ignorePattern,
parser,
parserOptions,
plugins: plugin,
rules: rule,
};
}
const options = {
allowInlineConfig: inlineConfig,
cache,
cacheLocation: cacheLocation || cacheFile,
cacheStrategy,
errorOnUnmatchedPattern,
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
fixTypes: fixType,
ignore,
overrideConfig,
overrideConfigFile,
passOnNoPatterns,
};
if (configType === "flat") {
options.ignorePatterns = ignorePattern;
options.stats = stats;
options.warnIgnored = warnIgnored;
options.flags = flag;
/*
* For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
* requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
*/
options.ruleFilter =
quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
} else {
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
options.rulePaths = rulesdir;
options.useEslintrc = eslintrc;
options.extensions = ext;
options.ignorePath = ignorePath;
if (
reportUnusedDisableDirectives ||
reportUnusedDisableDirectivesSeverity !== void 0
) {
options.reportUnusedDisableDirectives =
reportUnusedDisableDirectives
? "error"
: normalizeSeverityToString(
reportUnusedDisableDirectivesSeverity,
);
}
}
return options;
}
/**
* Count error messages.
* @param {LintResult[]} results The lint results.
@@ -322,6 +64,26 @@ function countErrors(results) {
return { errorCount, fatalErrorCount, warningCount };
}
/**
* Creates an options module from the provided CLI options and encodes it as a data URL.
* @param {ParsedCLIOptions} options The CLI options.
* @returns {URL} The URL of the options module.
*/
function createOptionsModule(options) {
const translateOptionsFileURL = new URL(
"./shared/translate-cli-options.js",
pathToFileURL(__filename),
).href;
const optionsSrc =
`import translateOptions from ${JSON.stringify(translateOptionsFileURL)};\n` +
`export default await translateOptions(${JSON.stringify(options)}, "flat");\n`;
// Base64 encoding is typically shorter than URL encoding
return new URL(
`data:text/javascript;base64,${Buffer.from(optionsSrc).toString("base64")}`,
);
}
/**
* Check if a given file path is a directory or not.
* @param {string} filePath The path to a file to check.
@@ -385,6 +147,30 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
return true;
}
/**
* Validates the `--concurrency` flag value.
* @param {string} concurrency The `--concurrency` flag value to validate.
* @returns {void}
* @throws {Error} If the `--concurrency` flag value is invalid.
*/
function validateConcurrency(concurrency) {
if (
concurrency === void 0 ||
concurrency === "auto" ||
concurrency === "off"
) {
return;
}
const concurrencyValue = Number(concurrency);
if (!Number.isInteger(concurrencyValue) || concurrencyValue < 1) {
throw new Error(
`Option concurrency: '${concurrency}' is not a positive integer, 'auto' or 'off'.`,
);
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -445,6 +231,7 @@ const cli = {
try {
options = CLIOptions.parse(args);
validateConcurrency(options.concurrency);
} catch (error) {
debug("Error parsing CLI options:", error.message);
@@ -614,11 +401,20 @@ const cli = {
}
const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint;
const eslintOptions = await translateOptions(
options,
usingFlatConfig ? "flat" : "eslintrc",
);
const engine = new ActiveESLint(eslintOptions);
/** @type {ESLint|LegacyESLint} */
let engine;
if (options.concurrency && options.concurrency !== "off") {
const optionsURL = createOptionsModule(options);
engine = await ESLint.fromOptionsModule(optionsURL);
} else {
const eslintOptions = await translateOptions(
options,
usingFlatConfig ? "flat" : "eslintrc",
);
engine = new ActiveESLint(eslintOptions);
}
let results;
if (useStdin) {

View File

@@ -230,7 +230,7 @@ function assertIsRuleSeverity(ruleId, value) {
* @throws {TypeError} If the string isn't in the correct format.
*/
function assertIsPluginMemberName(value) {
if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
if (!/[\w\-@$]+(?:\/[\w\-$]+)+$/iu.test(value)) {
throw new TypeError(
`Expected string in the form "pluginName/objectName" but found "${value}".`,
);

View File

@@ -11,11 +11,17 @@
const path = require("node:path");
const fs = require("node:fs");
const { isMainThread, threadId } = require("node:worker_threads");
const fsp = fs.promises;
const isGlob = require("is-glob");
const hash = require("../cli-engine/hash");
const minimatch = require("minimatch");
const globParent = require("glob-parent");
const { Linter } = require("../linter");
const { getShorthandName } = require("../shared/naming");
const LintResultCache = require("../cli-engine/lint-result-cache");
const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
const createDebug = require("debug");
//-----------------------------------------------------------------------------
// Fixup references
@@ -30,11 +36,16 @@ const MINIMATCH_OPTIONS = { dot: true };
/**
* @import { ESLintOptions } from "./eslint.js";
* @import { ConfigLoader, LegacyConfigLoader } from "../config/config-loader.js";
* @import { Config as CalculatedConfig } from "../config/config.js";
* @import { FlatConfigArray } from "../config/flat-config-array.js";
* @import { WarningService } from "../services/warning-service.js";
* @import { Retrier } from "@humanwhocodes/retry";
*/
/** @typedef {import("../types").Linter.Config} Config */
/** @typedef {import("../types").Linter.LintMessage} LintMessage */
/** @typedef {import("../types").ESLint.LintResult} LintResult */
/** @typedef {import("../types").ESLint.Plugin} Plugin */
/**
* @typedef {Object} GlobSearch
@@ -43,6 +54,18 @@ const MINIMATCH_OPTIONS = { dot: true };
* before doing any normalization.
*/
//------------------------------------------------------------------------------
// Internal Helpers
//------------------------------------------------------------------------------
const hrtimeBigint = process.hrtime.bigint;
createDebug.formatters.t = timeDiff =>
`${(timeDiff + 500_000n) / 1_000_000n} ms`;
const debug = createDebug(
`eslint:eslint-helpers${isMainThread ? "" : `:thread-${threadId}`}`,
);
//-----------------------------------------------------------------------------
// Errors
//-----------------------------------------------------------------------------
@@ -137,6 +160,15 @@ function isEmptyArrayOrArrayOfNonEmptyString(value) {
return Array.isArray(value) && value.every(isNonEmptyString);
}
/**
* Check if a given value is a positive integer.
* @param {unknown} value The value to check.
* @returns {boolean} `true` if `value` is a positive integer.
*/
function isPositiveInteger(value) {
return Number.isInteger(value) && value > 0;
}
//-----------------------------------------------------------------------------
// File-related Helpers
//-----------------------------------------------------------------------------
@@ -537,6 +569,7 @@ async function findFiles({
filePaths.map(filePath => fsp.stat(filePath).catch(() => {})),
);
const promises = [];
stats.forEach((stat, index) => {
const filePath = filePaths[index];
const pattern = normalizeToPosix(patterns[index]);
@@ -545,6 +578,7 @@ async function findFiles({
// files are added directly to the list
if (stat.isFile()) {
results.push(filePath);
promises.push(configLoader.loadConfigArrayForFile(filePath));
}
// directories need extensions attached
@@ -590,15 +624,28 @@ async function findFiles({
}
// now we are safe to do the search
const globbyResults = await globMultiSearch({
searches,
configLoader,
errorOnUnmatchedPattern,
});
promises.push(
globMultiSearch({
searches,
configLoader,
errorOnUnmatchedPattern,
}),
);
const globbyResults = (await Promise.all(promises)).at(-1);
return [...new Set([...results, ...globbyResults])];
}
/**
* Return the absolute path of a file named `"__placeholder__.js"` in a given directory.
* This is used as a replacement for a missing file path.
* @param {string} cwd An absolute directory path.
* @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory.
*/
function getPlaceholderPath(cwd) {
return path.join(cwd, "__placeholder__.js");
}
//-----------------------------------------------------------------------------
// Results-related Helpers
//-----------------------------------------------------------------------------
@@ -758,6 +805,7 @@ function processOptions({
cache = false,
cacheLocation = ".eslintcache",
cacheStrategy = "metadata",
concurrency = "off",
cwd = process.cwd(),
errorOnUnmatchedPattern = true,
fix = false,
@@ -853,6 +901,15 @@ function processOptions({
if (cacheStrategy !== "metadata" && cacheStrategy !== "content") {
errors.push('\'cacheStrategy\' must be any of "metadata", "content".');
}
if (
concurrency !== "off" &&
concurrency !== "auto" &&
!isPositiveInteger(concurrency)
) {
errors.push(
'\'concurrency\' must be a positive integer, "auto", or "off".',
);
}
if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
errors.push("'cwd' must be an absolute path.");
}
@@ -928,6 +985,7 @@ function processOptions({
cache,
cacheLocation,
cacheStrategy,
concurrency,
// when overrideConfigFile is true that means don't do config file lookup
configFile: overrideConfigFile === true ? false : overrideConfigFile,
@@ -947,6 +1005,15 @@ function processOptions({
};
}
/**
* Loads ESLint constructor options from an options module.
* @param {string} optionsURL The URL string of the options module to load.
* @returns {Promise<ESLintOptions>} ESLint constructor options.
*/
async function loadOptionsFromModule(optionsURL) {
return (await import(optionsURL)).default;
}
//-----------------------------------------------------------------------------
// Cache-related helpers
//-----------------------------------------------------------------------------
@@ -1022,6 +1089,349 @@ function getCacheFile(cacheFile, cwd, { prefix = ".cache_" } = {}) {
return resolvedCacheFile;
}
/**
* Creates a new lint result cache.
* @param {ESLintOptions} eslintOptions The processed ESLint options.
* @param {string} cacheFilePath The path to the cache file.
* @returns {?LintResultCache} A new lint result cache or `null`.
*/
function createLintResultCache({ cache, cacheStrategy }, cacheFilePath) {
return cache ? new LintResultCache(cacheFilePath, cacheStrategy) : null;
}
//-----------------------------------------------------------------------------
// Lint helpers
//-----------------------------------------------------------------------------
/**
* Checks whether a message's rule type should be fixed.
* @param {LintMessage} message The message to check.
* @param {CalculatedConfig} config The config for the file that generated the message.
* @param {string[]} fixTypes An array of fix types to check.
* @returns {boolean} Whether the message should be fixed.
*/
function shouldMessageBeFixed(message, config, fixTypes) {
if (!message.ruleId) {
return fixTypes.has("directive");
}
const rule = message.ruleId && config.getRuleDefinition(message.ruleId);
return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
}
/**
* Creates a fixer function based on the provided fix, fixTypesSet, and config.
* @param {Function|boolean} fix The original fix option.
* @param {Set<string>} fixTypesSet A set of fix types to filter messages for fixing.
* @param {CalculatedConfig} config The config for the file that generated the message.
* @returns {Function|boolean} The fixer function or the original fix value.
*/
function getFixerForFixTypes(fix, fixTypesSet, config) {
if (!fix || !fixTypesSet) {
return fix;
}
const originalFix = typeof fix === "function" ? fix : () => true;
return message =>
shouldMessageBeFixed(message, config, fixTypesSet) &&
originalFix(message);
}
/**
* Processes a source code using ESLint.
* @param {Object} config The config object.
* @param {string} config.text The source code to verify.
* @param {string} config.cwd The path to the current working directory.
* @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
* @param {FlatConfigArray} config.configs The config.
* @param {boolean} config.fix If `true` then it does fix.
* @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
* @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
* @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
* @param {Linter} config.linter The linter instance to verify.
* @returns {LintResult} The result of linting.
* @private
*/
function verifyText({
text,
cwd,
filePath: providedFilePath,
configs,
fix,
allowInlineConfig,
ruleFilter,
stats,
linter,
}) {
const startTime = hrtimeBigint();
const filePath = providedFilePath || "<text>";
/*
* Verify.
* `config.extractConfig(filePath)` requires an absolute path, but `linter`
* doesn't know CWD, so it gives `linter` an absolute path always.
*/
const filePathToVerify =
filePath === "<text>" ? getPlaceholderPath(cwd) : filePath;
const { fixed, messages, output } = linter.verifyAndFix(text, configs, {
allowInlineConfig,
filename: filePathToVerify,
fix,
ruleFilter,
stats,
/**
* Check if the linter should adopt a given code block or not.
* @param {string} blockFilename The virtual filename of a code block.
* @returns {boolean} `true` if the linter should adopt the code block.
*/
filterCodeBlock(blockFilename) {
return configs.getConfig(blockFilename) !== void 0;
},
});
// Tweak and return.
const result = {
filePath: filePath === "<text>" ? filePath : path.resolve(filePath),
messages,
suppressedMessages: linter.getSuppressedMessages(),
...calculateStatsPerFile(messages),
};
if (fixed) {
result.output = output;
}
if (
result.errorCount + result.warningCount > 0 &&
typeof result.output === "undefined"
) {
result.source = text;
}
if (stats) {
result.stats = {
times: linter.getTimes(),
fixPasses: linter.getFixPassCount(),
};
}
const endTime = hrtimeBigint();
debug('File "%s" linted in %t', filePath, endTime - startTime);
return result;
}
/**
* Lints a single file.
* @param {string} filePath File path to lint.
* @param {FlatConfigArray} configs The config array for the file.
* @param {ESLintOptions} eslintOptions The processed ESLint options.
* @param {Linter} linter The linter instance to use.
* @param {?LintResultCache} lintResultCache The result cache or `null`.
* @param {?{ duration: bigint; }} readFileCounter Used to keep track of the time spent reading files.
* @param {Retrier} [retrier] Used to retry linting on certain errors.
* @param {AbortController} [controller] Used to stop linting when an error occurs.
* @returns {Promise<LintResult>} The lint result.
*/
async function lintFile(
filePath,
configs,
eslintOptions,
linter,
lintResultCache,
readFileCounter,
retrier,
controller,
) {
const config = configs.getConfig(filePath);
const {
allowInlineConfig,
cwd,
fix,
fixTypes,
ruleFilter,
stats,
warnIgnored,
} = eslintOptions;
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
/*
* If a filename was entered that cannot be matched
* to a config, then notify the user.
*/
if (!config) {
if (warnIgnored) {
const configStatus = configs.getConfigStatus(filePath);
return createIgnoreResult(filePath, cwd, configStatus);
}
return void 0;
}
// Skip if there is cached result.
if (lintResultCache) {
const cachedResult = lintResultCache.getCachedLintResults(
filePath,
config,
);
if (cachedResult) {
const hadMessages =
cachedResult.messages && cachedResult.messages.length > 0;
if (hadMessages && fix) {
debug(`Reprocessing cached file to allow autofix: ${filePath}`);
} else {
debug(`Skipping file since it hasn't changed: ${filePath}`);
return cachedResult;
}
}
}
// set up fixer for fixTypes if necessary
const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
/**
* Reads the file and lints its content.
* @returns {Promise<LintResult>} A lint result.
*/
async function readAndVerifyFile() {
const readFileEnterTime = hrtimeBigint();
const text = await fsp.readFile(filePath, {
encoding: "utf8",
signal: controller?.signal,
});
const readFileExitTime = hrtimeBigint();
const readFileDuration = readFileExitTime - readFileEnterTime;
debug('File "%s" read in %t', filePath, readFileDuration);
if (readFileCounter) {
readFileCounter.duration += readFileDuration;
}
// fail immediately if an error occurred in another file
controller?.signal.throwIfAborted();
// do the linting
return verifyText({
text,
filePath,
configs,
cwd,
fix: fixer,
allowInlineConfig,
ruleFilter,
stats,
linter,
});
}
// Use the retrier if provided, otherwise just call the function.
const readAndVerifyFilePromise = retrier
? retrier.retry(readAndVerifyFile, { signal: controller?.signal })
: readAndVerifyFile();
return readAndVerifyFilePromise.catch(error => {
controller?.abort(error);
throw error;
});
}
/**
* Retrieves flags from the environment variable ESLINT_FLAGS.
* @param {string[]} flags The flags defined via the API.
* @returns {string[]} The merged flags to use.
*/
function mergeEnvironmentFlags(flags) {
if (!process.env.ESLINT_FLAGS) {
return flags;
}
const envFlags = process.env.ESLINT_FLAGS.trim().split(/\s*,\s*/gu);
return Array.from(new Set([...envFlags, ...flags]));
}
/**
* Creates a new linter instance.
* @param {ESLintOptions} eslintOptions The processed ESLint options.
* @param {WarningService} warningService The warning service to use.
* @returns {Linter} The linter instance.
*/
function createLinter({ cwd, flags }, warningService) {
return new Linter({
configType: "flat",
cwd,
flags: mergeEnvironmentFlags(flags),
warningService,
});
}
/**
* Creates default configs with the specified plugins.
* @param {Record<string, Plugin> | undefined} optionPlugins The plugins specified in the ESLint options.
* @returns {Config[]} The default configs.
*/
function createDefaultConfigs(optionPlugins) {
const defaultConfigs = [];
// Add plugins
if (optionPlugins) {
const plugins = {};
for (const [pluginName, plugin] of Object.entries(optionPlugins)) {
plugins[getShorthandName(pluginName, "eslint-plugin")] = plugin;
}
defaultConfigs.push({ plugins });
}
return defaultConfigs;
}
/**
* Creates a config loader.
* @param {ESLintOptions} eslintOptions The processed ESLint options.
* @param {Config[]} defaultConfigs The default configs.
* @param {Linter} linter The linter instance.
* @param {WarningService} warningService The warning service to use.
* @returns {ConfigLoader} The config loader.
*/
function createConfigLoader(
{
cwd,
baseConfig,
overrideConfig,
configFile,
ignore: ignoreEnabled,
ignorePatterns,
},
defaultConfigs,
linter,
warningService,
) {
const configLoaderOptions = {
cwd,
baseConfig,
overrideConfig,
configFile,
ignoreEnabled,
ignorePatterns,
defaultConfigs,
hasUnstableNativeNodeJsTSConfigFlag: linter.hasFlag(
"unstable_native_nodejs_ts_config",
),
warningService,
};
return linter.hasFlag("v10_config_lookup_from_file")
? new ConfigLoader(configLoaderOptions)
: new LegacyConfigLoader(configLoaderOptions);
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
@@ -1035,8 +1445,18 @@ module.exports = {
createIgnoreResult,
isErrorMessage,
calculateStatsPerFile,
getPlaceholderPath,
processOptions,
loadOptionsFromModule,
getCacheFile,
createLintResultCache,
getFixerForFixTypes,
verifyText,
lintFile,
createLinter,
createDefaultConfigs,
createConfigLoader,
};

View File

@@ -9,11 +9,13 @@
// Requirements
//------------------------------------------------------------------------------
const fs = require("node:fs/promises");
const { existsSync } = require("node:fs");
const fs = require("node:fs/promises");
const os = require("node:os");
const path = require("node:path");
const { pathToFileURL } = require("node:url");
const { SHARE_ENV, Worker } = require("node:worker_threads");
const { version } = require("../../package.json");
const { Linter } = require("../linter");
const { defaultConfig } = require("../config/default-config");
const {
@@ -25,14 +27,21 @@ const {
createIgnoreResult,
isErrorMessage,
calculateStatsPerFile,
getPlaceholderPath,
processOptions,
loadOptionsFromModule,
getFixerForFixTypes,
verifyText,
lintFile,
createLinter,
createLintResultCache,
createDefaultConfigs,
createConfigLoader,
} = require("./eslint-helpers");
const { pathToFileURL } = require("node:url");
const LintResultCache = require("../cli-engine/lint-result-cache");
const { Retrier } = require("@humanwhocodes/retry");
const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
const { ConfigLoader } = require("../config/config-loader");
const { WarningService } = require("../services/warning-service");
const { Config } = require("../config/config.js");
const {
@@ -53,16 +62,14 @@ const { resolve } = require("../shared/relative-module-resolver.js");
// For VSCode IntelliSense
/**
* @import { ConfigArray } from "../cli-engine/cli-engine.js";
* @import { CLIEngineLintReport } from "./legacy-eslint.js";
* @import { Config as CalculatedConfig } from "../config/config.js";
* @import { FlatConfigArray } from "../config/flat-config-array.js";
* @import { RuleDefinition } from "@eslint/core";
* @import { RuleDefinition, RulesMeta } from "@eslint/core";
* @import { WorkerLintResults } from "./worker.js";
*/
/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
/** @typedef {import("../types").Linter.Config} Config */
/** @typedef {import("../types").ESLint.DeprecatedRuleUse} DeprecatedRuleInfo */
/** @typedef {import("../types").Linter.LintMessage} LintMessage */
/** @typedef {import("../types").ESLint.LintResult} LintResult */
/** @typedef {import("../types").ESLint.Plugin} Plugin */
/** @typedef {import("../types").ESLint.ResultsMeta} ResultsMeta */
@@ -75,6 +82,7 @@ const { resolve } = require("../shared/relative-module-resolver.js");
* @property {boolean} [cache] Enable result caching.
* @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
* @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
* @property {number | "auto" | "off"} [concurrency] Maximum number of linting threads, "auto" to choose automatically, "off" for no multithreading.
* @property {string} [cwd] The value to use for the current working directory.
* @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
* @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
@@ -87,11 +95,11 @@ const { resolve } = require("../shared/relative-module-resolver.js");
* @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
* doesn't do any config file lookup when `true`; considered to be a config filename
* when a string.
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
* @property {boolean} [stats] True enables added statistics on lint results.
* @property {boolean} [warnIgnored] Show warnings when the file list includes ignored files
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
*/
//------------------------------------------------------------------------------
@@ -111,11 +119,12 @@ const removedFormatters = new Set([
"unix",
"visualstudio",
]);
const fileRetryCodes = new Set(["ENFILE", "EMFILE"]);
/**
* Create rulesMeta object.
* @param {Map<string,RuleDefinition>} rules a map of rules from which to generate the object.
* @returns {Object} metadata for all enabled rules.
* @param {Map<string, RuleDefinition>} rules a map of rules from which to generate the object.
* @returns {Record<string, RulesMeta>} metadata for all enabled rules.
*/
function createRulesMeta(rules) {
return Array.from(rules).reduce((retVal, [id, rule]) => {
@@ -124,17 +133,7 @@ function createRulesMeta(rules) {
}, {});
}
/**
* Return the absolute path of a file named `"__placeholder__.js"` in a given directory.
* This is used as a replacement for a missing file path.
* @param {string} cwd An absolute directory path.
* @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory.
*/
function getPlaceholderPath(cwd) {
return path.join(cwd, "__placeholder__.js");
}
/** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
/** @type {WeakMap<CalculatedConfig, DeprecatedRuleInfo[]>} */
const usedDeprecatedRulesCache = new WeakMap();
/**
@@ -193,10 +192,10 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
* Processes the linting results generated by a CLIEngine linting report to
* match the ESLint class's API.
* @param {ESLint} eslint The ESLint instance.
* @param {CLIEngineLintReport} report The CLIEngine linting report to process.
* @param {LintResult[]} results The linting results to process.
* @returns {LintResult[]} The processed linting results.
*/
function processLintReport(eslint, { results }) {
function processLintReport(eslint, results) {
const descriptor = {
configurable: true,
enumerable: true,
@@ -262,147 +261,296 @@ async function locateConfigFileToUse({ configFile, cwd }) {
};
}
/**
* Processes an source code using ESLint.
* @param {Object} config The config object.
* @param {string} config.text The source code to verify.
* @param {string} config.cwd The path to the current working directory.
* @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
* @param {FlatConfigArray} config.configs The config.
* @param {boolean} config.fix If `true` then it does fix.
* @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
* @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
* @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
* @param {Linter} config.linter The linter instance to verify.
* @returns {LintResult} The result of linting.
* @private
*/
function verifyText({
text,
cwd,
filePath: providedFilePath,
configs,
fix,
allowInlineConfig,
ruleFilter,
stats,
linter,
}) {
const filePath = providedFilePath || "<text>";
debug(`Lint ${filePath}`);
/*
* Verify.
* `config.extractConfig(filePath)` requires an absolute path, but `linter`
* doesn't know CWD, so it gives `linter` an absolute path always.
*/
const filePathToVerify =
filePath === "<text>" ? getPlaceholderPath(cwd) : filePath;
const { fixed, messages, output } = linter.verifyAndFix(text, configs, {
allowInlineConfig,
filename: filePathToVerify,
fix,
ruleFilter,
stats,
/**
* Check if the linter should adopt a given code block or not.
* @param {string} blockFilename The virtual filename of a code block.
* @returns {boolean} `true` if the linter should adopt the code block.
*/
filterCodeBlock(blockFilename) {
return configs.getConfig(blockFilename) !== void 0;
},
});
// Tweak and return.
const result = {
filePath: filePath === "<text>" ? filePath : path.resolve(filePath),
messages,
suppressedMessages: linter.getSuppressedMessages(),
...calculateStatsPerFile(messages),
};
if (fixed) {
result.output = output;
}
if (
result.errorCount + result.warningCount > 0 &&
typeof result.output === "undefined"
) {
result.source = text;
}
if (stats) {
result.stats = {
times: linter.getTimes(),
fixPasses: linter.getFixPassCount(),
};
}
return result;
}
/**
* Checks whether a message's rule type should be fixed.
* @param {LintMessage} message The message to check.
* @param {FlatConfigArray} config The config for the file that generated the message.
* @param {string[]} fixTypes An array of fix types to check.
* @returns {boolean} Whether the message should be fixed.
*/
function shouldMessageBeFixed(message, config, fixTypes) {
if (!message.ruleId) {
return fixTypes.has("directive");
}
const rule = message.ruleId && config.getRuleDefinition(message.ruleId);
return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
}
/**
* Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine.
* @param {Error|undefined} cause The original error that led to this symptom error being thrown. Might not always be available.
* @returns {TypeError} An error object.
*/
function createExtraneousResultsError() {
function createExtraneousResultsError(cause) {
return new TypeError(
"Results object was not created from this ESLint instance.",
{
cause,
},
);
}
/**
* Creates a fixer function based on the provided fix, fixTypesSet, and config.
* @param {Function|boolean} fix The original fix option.
* @param {Set<string>} fixTypesSet A set of fix types to filter messages for fixing.
* @param {FlatConfigArray} config The config for the file that generated the message.
* @returns {Function|boolean} The fixer function or the original fix value.
* Maximum number of files assumed to be best handled by one worker thread.
* This value is a heuristic estimation that can be adjusted if required.
*/
function getFixerForFixTypes(fix, fixTypesSet, config) {
if (!fix || !fixTypesSet) {
return fix;
const AUTO_FILES_PER_WORKER = 35;
/**
* Calculates the number of workers to run based on the concurrency setting and the number of files to lint.
* @param {number | "auto" | "off"} concurrency The normalized concurrency setting.
* @param {number} fileCount The number of files to be linted.
* @param {{ availableParallelism: () => number }} [os] Node.js `os` module, or a mock for testing.
* @returns {number} The effective number of worker threads to be started. A value of zero disables multithread linting.
*/
function calculateWorkerCount(
concurrency,
fileCount,
{ availableParallelism } = os,
) {
let workerCount;
switch (concurrency) {
case "off":
return 0;
case "auto": {
workerCount = Math.min(
availableParallelism() >> 1,
Math.ceil(fileCount / AUTO_FILES_PER_WORKER),
);
break;
}
default:
workerCount = Math.min(concurrency, fileCount);
break;
}
return workerCount > 1 ? workerCount : 0;
}
// Used internally. Do not expose.
const disableCloneabilityCheck = Symbol(
"Do not check for uncloneable options.",
);
/**
* The smallest net linting ratio that doesn't trigger a poor concurrency warning.
* The net linting ratio is defined as the net linting duration divided by the thread's total runtime,
* where the net linting duration is the total linting time minus the time spent on I/O-intensive operations:
* **Net Linting Ratio** = (**Linting Time** **I/O Time**) / **Thread Runtime**.
* - **Linting Time**: Total time spent linting files
* - **I/O Time**: Portion of linting time spent loading configs and reading files
* - **Thread Runtime**: End-to-end execution time of the thread
*
* This value is a heuristic estimation that can be adjusted if required.
*/
const LOW_NET_LINTING_RATIO = 0.7;
/**
* Runs worker threads to lint files.
* @param {string[]} filePaths File paths to lint.
* @param {number} workerCount The number of worker threads to run.
* @param {ESLintOptions | string} eslintOptionsOrURL The unprocessed ESLint options or the URL of the options module.
* @param {() => void} warnOnLowNetLintingRatio A function to call if the net linting ratio is low.
* @returns {Promise<LintResult[]>} Lint results.
*/
async function runWorkers(
filePaths,
workerCount,
eslintOptionsOrURL,
warnOnLowNetLintingRatio,
) {
const fileCount = filePaths.length;
const results = Array(fileCount);
const workerURL = pathToFileURL(path.join(__dirname, "./worker.js"));
const filePathIndexArray = new Uint32Array(
new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT),
);
const abortController = new AbortController();
const abortSignal = abortController.signal;
const workerOptions = {
env: SHARE_ENV,
workerData: {
eslintOptionsOrURL,
filePathIndexArray,
filePaths,
},
};
const hrtimeBigint = process.hrtime.bigint;
let worstNetLintingRatio = 1;
/**
* A promise executor function that starts a worker thread on each invocation.
* @param {() => void} resolve_ Called when the worker thread terminates successfully.
* @param {(error: Error) => void} reject Called when the worker thread terminates with an error.
* @returns {void}
*/
function workerExecutor(resolve_, reject) {
const workerStartTime = hrtimeBigint();
const worker = new Worker(workerURL, workerOptions);
worker.once(
"message",
(/** @type {WorkerLintResults} */ indexedResults) => {
const workerDuration = hrtimeBigint() - workerStartTime;
// The net linting ratio provides an approximate measure of worker thread efficiency, defined as the net linting duration divided by the thread's total runtime.
const netLintingRatio =
Number(indexedResults.netLintingDuration) /
Number(workerDuration);
worstNetLintingRatio = Math.min(
worstNetLintingRatio,
netLintingRatio,
);
for (const result of indexedResults) {
const { index } = result;
delete result.index;
results[index] = result;
}
resolve_();
},
);
worker.once("error", error => {
abortController.abort(error);
reject(error);
});
abortSignal.addEventListener("abort", () => worker.terminate());
}
const originalFix = typeof fix === "function" ? fix : () => true;
const promises = Array(workerCount);
for (let index = 0; index < workerCount; ++index) {
promises[index] = new Promise(workerExecutor);
}
await Promise.all(promises);
return message =>
shouldMessageBeFixed(message, config, fixTypesSet) &&
originalFix(message);
if (worstNetLintingRatio < LOW_NET_LINTING_RATIO) {
warnOnLowNetLintingRatio();
}
return results;
}
/**
* Retrieves flags from the environment variable ESLINT_FLAGS.
* @param {string[]} flags The flags defined via the API.
* @returns {string[]} The merged flags to use.
* Lint files in multithread mode.
* @param {ESLint} eslint ESLint instance.
* @param {string[]} filePaths File paths to lint.
* @param {number} workerCount The number of worker threads to run.
* @param {ESLintOptions | string} eslintOptionsOrURL The unprocessed ESLint options or the URL of the options module.
* @param {() => void} warnOnLowNetLintingRatio A function to call if the net linting ratio is low.
* @returns {Promise<LintResult[]>} Lint results.
*/
function mergeEnvironmentFlags(flags) {
if (!process.env.ESLINT_FLAGS) {
return flags;
}
async function lintFilesWithMultithreading(
eslint,
filePaths,
workerCount,
eslintOptionsOrURL,
warnOnLowNetLintingRatio,
) {
const { configLoader, lintResultCache } = privateMembers.get(eslint);
const envFlags = process.env.ESLINT_FLAGS.trim().split(/\s*,\s*/gu);
return Array.from(new Set([...envFlags, ...flags]));
const results = await runWorkers(
filePaths,
workerCount,
eslintOptionsOrURL,
warnOnLowNetLintingRatio,
);
// Persist the cache to disk.
if (lintResultCache) {
results.forEach((result, index) => {
if (result) {
const filePath = filePaths[index];
const configs =
configLoader.getCachedConfigArrayForFile(filePath);
const config = configs.getConfig(filePath);
if (config) {
/*
* Store the lint result in the LintResultCache.
* NOTE: The LintResultCache will remove the file source and any
* other properties that are difficult to serialize, and will
* hydrate those properties back in on future lint runs.
*/
lintResultCache.setCachedLintResults(
filePath,
config,
result,
);
}
}
});
}
return results;
}
/**
* Lint files in single-thread mode.
* @param {ESLint} eslint ESLint instance.
* @param {string[]} filePaths File paths to lint.
* @returns {Promise<LintResult[]>} Lint results.
*/
async function lintFilesWithoutMultithreading(eslint, filePaths) {
const {
configLoader,
linter,
lintResultCache,
options: eslintOptions,
} = privateMembers.get(eslint);
const controller = new AbortController();
const retrier = new Retrier(error => fileRetryCodes.has(error.code), {
concurrency: 100,
});
/*
* Because we need to process multiple files, including reading from disk,
* it is most efficient to start by reading each file via promises so that
* they can be done in parallel. Then, we can lint the returned text. This
* ensures we are waiting the minimum amount of time in between lints.
*/
const results = await Promise.all(
filePaths.map(async filePath => {
const configs = configLoader.getCachedConfigArrayForFile(filePath);
const config = configs.getConfig(filePath);
const result = await lintFile(
filePath,
configs,
eslintOptions,
linter,
lintResultCache,
null,
retrier,
controller,
);
if (config) {
/*
* Store the lint result in the LintResultCache.
* NOTE: The LintResultCache will remove the file source and any
* other properties that are difficult to serialize, and will
* hydrate those properties back in on future lint runs.
*/
lintResultCache?.setCachedLintResults(filePath, config, result);
}
return result;
}),
);
return results;
}
/**
* Throws an error if the given options are not cloneable.
* @param {ESLintOptions} options The options to check.
* @returns {void}
* @throws {TypeError} If the options are not cloneable.
*/
function validateOptionCloneability(options) {
try {
structuredClone(options);
return;
} catch {
// continue
}
const uncloneableOptionKeys = Object.keys(options)
.filter(key => {
try {
structuredClone(options[key]);
} catch {
return true;
}
return false;
})
.sort();
const error = new TypeError(
`The ${uncloneableOptionKeys.length === 1 ? "option" : "options"} ${new Intl.ListFormat("en-US").format(uncloneableOptionKeys.map(key => `"${key}"`))} cannot be cloned. When concurrency is enabled, all options must be cloneable values (JSON values). Remove uncloneable options or use an options module.`,
);
error.code = "ESLINT_UNCLONEABLE_OPTIONS";
throw error;
}
//-----------------------------------------------------------------------------
@@ -421,51 +569,51 @@ class ESLint {
/**
* The loader to use for finding config files.
* @type {ConfigLoader|LegacyConfigLoader}
* @type {ConfigLoader}
*/
#configLoader;
/**
* The unprocessed options or the URL of the options module. Only set when concurrency is enabled.
* @type {ESLintOptions | string | undefined}
*/
#optionsOrURL;
/**
* Creates a new instance of the main ESLint API.
* @param {ESLintOptions} options The options for this instance.
*/
constructor(options = {}) {
const defaultConfigs = [];
const processedOptions = processOptions(options);
if (
!options[disableCloneabilityCheck] &&
processedOptions.concurrency !== "off"
) {
validateOptionCloneability(options);
// Save the unprocessed options in an instance field to pass to worker threads in `lintFiles()`.
this.#optionsOrURL = options;
}
const warningService = new WarningService();
const linter = new Linter({
cwd: processedOptions.cwd,
configType: "flat",
flags: mergeEnvironmentFlags(processedOptions.flags),
warningService,
});
const linter = createLinter(processedOptions, warningService);
const cacheFilePath = getCacheFile(
processedOptions.cacheLocation,
processedOptions.cwd,
);
const lintResultCache = processedOptions.cache
? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy)
: null;
const lintResultCache = createLintResultCache(
processedOptions,
cacheFilePath,
);
const defaultConfigs = createDefaultConfigs(options.plugins);
const configLoaderOptions = {
cwd: processedOptions.cwd,
baseConfig: processedOptions.baseConfig,
overrideConfig: processedOptions.overrideConfig,
configFile: processedOptions.configFile,
ignoreEnabled: processedOptions.ignore,
ignorePatterns: processedOptions.ignorePatterns,
this.#configLoader = createConfigLoader(
processedOptions,
defaultConfigs,
hasUnstableNativeNodeJsTSConfigFlag: linter.hasFlag(
"unstable_native_nodejs_ts_config",
),
linter,
warningService,
};
this.#configLoader = linter.hasFlag("v10_config_lookup_from_file")
? new ConfigLoader(configLoaderOptions)
: new LegacyConfigLoader(configLoaderOptions);
);
debug(`Using config loader ${this.#configLoader.constructor.name}`);
@@ -477,26 +625,9 @@ class ESLint {
defaultConfigs,
configs: null,
configLoader: this.#configLoader,
warningService,
});
/**
* If additional plugins are passed in, add that to the default
* configs for this instance.
*/
if (options.plugins) {
const plugins = {};
for (const [pluginName, plugin] of Object.entries(
options.plugins,
)) {
plugins[getShorthandName(pluginName, "eslint-plugin")] = plugin;
}
defaultConfigs.push({
plugins,
});
}
// Check for the .eslintignore file, and warn if it's present.
if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) {
warningService.emitESLintIgnoreWarning();
@@ -514,7 +645,7 @@ class ESLint {
/**
* The default configuration that ESLint uses internally. This is provided for tooling that wants to calculate configurations using the same defaults as ESLint.
* Keep in mind that the default configuration may change from version to version, so you shouldn't rely on any particular keys or values to be present.
* @type {ConfigArray}
* @type {FlatConfigArray}
*/
static get defaultConfig() {
return defaultConfig;
@@ -530,8 +661,7 @@ class ESLint {
throw new Error("'results' must be an array");
}
const retryCodes = new Set(["ENFILE", "EMFILE"]);
const retrier = new Retrier(error => retryCodes.has(error.code), {
const retrier = new Retrier(error => fileRetryCodes.has(error.code), {
concurrency: 100,
});
@@ -581,10 +711,31 @@ class ESLint {
return filtered;
}
/**
* Creates a new ESLint instance using options loaded from a module.
* @param {URL} optionsURL The URL of the options module.
* @returns {ESLint} The new ESLint instance.
*/
static async fromOptionsModule(optionsURL) {
if (!(optionsURL instanceof URL)) {
throw new TypeError("Argument must be a URL object");
}
const optionsURLString = optionsURL.href;
const loadedOptions = await loadOptionsFromModule(optionsURLString);
const options = { ...loadedOptions, [disableCloneabilityCheck]: true };
const eslint = new ESLint(options);
if (options.concurrency !== "off") {
// Save the options module URL in an instance field to pass to worker threads in `lintFiles()`.
eslint.#optionsOrURL = optionsURLString;
}
return eslint;
}
/**
* Returns meta objects for each rule represented in the lint results.
* @param {LintResult[]} results The results to fetch rules meta for.
* @returns {Object} A mapping of ruleIds to rule meta objects.
* @returns {Record<string, RulesMeta>} A mapping of ruleIds to rule meta objects.
* @throws {TypeError} When the results object wasn't created from this ESLint instance.
* @throws {TypeError} When a plugin or rule is missing.
*/
@@ -626,8 +777,8 @@ class ESLint {
try {
configs =
configLoader.getCachedConfigArrayForFile(filePath);
} catch {
throw createExtraneousResultsError();
} catch (err) {
throw createExtraneousResultsError(err);
}
const config = configs.getConfig(filePath);
@@ -667,8 +818,8 @@ class ESLint {
const {
cacheFilePath,
lintResultCache,
linter,
options: eslintOptions,
warningService,
} = privateMembers.get(this);
/*
@@ -709,19 +860,12 @@ class ESLint {
debug(`Using file patterns: ${normalizedPatterns}`);
const {
allowInlineConfig,
cache,
concurrency,
cwd,
fix,
fixTypes,
ruleFilter,
stats,
globInputPaths,
errorOnUnmatchedPattern,
warnIgnored,
} = eslintOptions;
const startTime = Date.now();
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
// Delete cache file; should this be done here?
if (!cache && cacheFilePath) {
@@ -738,6 +882,7 @@ class ESLint {
}
}
const startTime = Date.now();
const filePaths = await findFiles({
patterns: normalizedPatterns,
cwd,
@@ -745,119 +890,45 @@ class ESLint {
configLoader: this.#configLoader,
errorOnUnmatchedPattern,
});
const controller = new AbortController();
const retryCodes = new Set(["ENFILE", "EMFILE"]);
const retrier = new Retrier(error => retryCodes.has(error.code), {
concurrency: 100,
});
debug(
`${filePaths.length} files found in: ${Date.now() - startTime}ms`,
);
/*
* Because we need to process multiple files, including reading from disk,
* it is most efficient to start by reading each file via promises so that
* they can be done in parallel. Then, we can lint the returned text. This
* ensures we are waiting the minimum amount of time in between lints.
*/
const results = await Promise.all(
filePaths.map(async filePath => {
const configs =
await this.#configLoader.loadConfigArrayForFile(filePath);
const config = configs.getConfig(filePath);
/** @type {LintResult[]} */
let results;
/*
* If a filename was entered that cannot be matched
* to a config, then notify the user.
*/
if (!config) {
if (warnIgnored) {
const configStatus = configs.getConfigStatus(filePath);
return createIgnoreResult(filePath, cwd, configStatus);
}
return void 0;
}
// Skip if there is cached result.
if (lintResultCache) {
const cachedResult = lintResultCache.getCachedLintResults(
filePath,
config,
);
if (cachedResult) {
const hadMessages =
cachedResult.messages &&
cachedResult.messages.length > 0;
if (hadMessages && fix) {
debug(
`Reprocessing cached file to allow autofix: ${filePath}`,
);
} else {
debug(
`Skipping file since it hasn't changed: ${filePath}`,
);
return cachedResult;
}
}
}
// set up fixer for fixTypes if necessary
const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
return retrier
.retry(
() =>
fs
.readFile(filePath, {
encoding: "utf8",
signal: controller.signal,
})
.then(text => {
// fail immediately if an error occurred in another file
controller.signal.throwIfAborted();
// do the linting
const result = verifyText({
text,
filePath,
configs,
cwd,
fix: fixer,
allowInlineConfig,
ruleFilter,
stats,
linter,
});
/*
* Store the lint result in the LintResultCache.
* NOTE: The LintResultCache will remove the file source and any
* other properties that are difficult to serialize, and will
* hydrate those properties back in on future lint runs.
*/
if (lintResultCache) {
lintResultCache.setCachedLintResults(
filePath,
config,
result,
);
}
return result;
}),
{ signal: controller.signal },
)
.catch(error => {
controller.abort(error);
throw error;
});
}),
// The value of `module.exports.calculateWorkerCount` can be overridden in tests.
const workerCount = module.exports.calculateWorkerCount(
concurrency,
filePaths.length,
);
if (workerCount) {
debug(`Linting using ${workerCount} worker thread(s).`);
let poorConcurrencyNotice;
if (workerCount <= 2) {
poorConcurrencyNotice = "disable concurrency";
} else {
if (concurrency === "auto") {
poorConcurrencyNotice =
"disable concurrency or use a numeric concurrency setting";
} else {
poorConcurrencyNotice = "reduce or disable concurrency";
}
}
results = await lintFilesWithMultithreading(
this,
filePaths,
workerCount,
this.#optionsOrURL,
() =>
warningService.emitPoorConcurrencyWarning(
poorConcurrencyNotice,
),
);
} else {
debug(`Linting in single-thread mode.`);
results = await lintFilesWithoutMultithreading(this, filePaths);
}
// Persist the cache to disk.
if (lintResultCache) {
@@ -866,9 +937,7 @@ class ESLint {
const finalResults = results.filter(result => !!result);
return processLintReport(this, {
results: finalResults,
});
return processLintReport(this, finalResults);
}
/**
@@ -977,9 +1046,7 @@ class ESLint {
debug(`Linting complete in: ${Date.now() - startTime}ms`);
return processLintReport(this, {
results,
});
return processLintReport(this, results);
}
/**
@@ -1089,7 +1156,7 @@ class ESLint {
* This is the same logic used by the ESLint CLI executable to determine
* configuration for each file it processes.
* @param {string} filePath The path of the file to retrieve a config object for.
* @returns {Promise<Config|undefined>} A configuration object for the file
* @returns {Promise<CalculatedConfig|undefined>} A configuration object for the file
* or `undefined` if there is no configuration data for the object.
*/
async calculateConfigForFile(filePath) {
@@ -1161,4 +1228,5 @@ module.exports = {
ESLint,
shouldUseFlatConfig,
locateConfigFileToUse,
calculateWorkerCount,
};

164
node_modules/eslint/lib/eslint/worker.js generated vendored Normal file
View File

@@ -0,0 +1,164 @@
/**
* @fileoverview Worker thread for multithread linting.
* @author Francesco Trotta
*/
"use strict";
const hrtimeBigint = process.hrtime.bigint;
const startTime = hrtimeBigint();
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- enable V8's code cache if supported
require("node:module").enableCompileCache?.();
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { parentPort, threadId, workerData } = require("node:worker_threads");
const createDebug = require("debug");
const {
createConfigLoader,
createDefaultConfigs,
createLinter,
createLintResultCache,
getCacheFile,
lintFile,
loadOptionsFromModule,
processOptions,
} = require("./eslint-helpers");
const { WarningService } = require("../services/warning-service");
const depsLoadedTime = hrtimeBigint();
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/** @typedef {import("../types").ESLint.LintResult} LintResult */
/** @typedef {import("../types").ESLint.Options} ESLintOptions */
/** @typedef {LintResult & { index?: number; }} IndexedLintResult */
/** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; }} WorkerLintResults */
/**
* @typedef {Object} WorkerData - Data passed to the worker thread.
* @property {ESLintOptions | string} eslintOptionsOrURL - The unprocessed ESLint options or the URL of the options module.
* @property {Uint32Array<SharedArrayBuffer>} filePathIndexArray - Shared counter used to track the next file to lint.
* @property {string[]} filePaths - File paths to lint.
*/
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const debug = createDebug(`eslint:worker:thread-${threadId}`);
createDebug.formatters.t = timeDiff =>
`${(timeDiff + 500_000n) / 1_000_000n} ms`;
//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------
debug("Dependencies loaded in %t", depsLoadedTime - startTime);
(async () => {
/** @type {WorkerData} */
const { eslintOptionsOrURL, filePathIndexArray, filePaths } = workerData;
const eslintOptions =
typeof eslintOptionsOrURL === "object"
? eslintOptionsOrURL
: await loadOptionsFromModule(eslintOptionsOrURL);
const processedESLintOptions = processOptions(eslintOptions);
const warningService = new WarningService();
// These warnings are always emitted by the controlling thread.
warningService.emitEmptyConfigWarning =
warningService.emitInactiveFlagWarning = () => {};
const linter = createLinter(processedESLintOptions, warningService);
const cacheFilePath = getCacheFile(
processedESLintOptions.cacheLocation,
processedESLintOptions.cwd,
);
const lintResultCache = createLintResultCache(
processedESLintOptions,
cacheFilePath,
);
const defaultConfigs = createDefaultConfigs(eslintOptions.plugins);
const configLoader = createConfigLoader(
processedESLintOptions,
defaultConfigs,
linter,
warningService,
);
/** @type {WorkerLintResults} */
const indexedResults = [];
let loadConfigTotalDuration = 0n;
const readFileCounter = { duration: 0n };
const lintingStartTime = hrtimeBigint();
debug(
"Linting started %t after dependencies loaded",
lintingStartTime - depsLoadedTime,
);
for (;;) {
const fileLintingStartTime = hrtimeBigint();
// It seems hard to produce an arithmetic overflow under realistic conditions here.
const index = Atomics.add(filePathIndexArray, 0, 1);
const filePath = filePaths[index];
if (!filePath) {
break;
}
const loadConfigEnterTime = hrtimeBigint();
const configs = await configLoader.loadConfigArrayForFile(filePath);
const loadConfigExitTime = hrtimeBigint();
const loadConfigDuration = loadConfigExitTime - loadConfigEnterTime;
debug(
'Config array for file "%s" loaded in %t',
filePath,
loadConfigDuration,
);
loadConfigTotalDuration += loadConfigDuration;
/** @type {IndexedLintResult} */
const result = await lintFile(
filePath,
configs,
processedESLintOptions,
linter,
lintResultCache,
readFileCounter,
);
if (result) {
result.index = index;
indexedResults.push(result);
}
const fileLintingEndTime = hrtimeBigint();
debug(
'File "%s" processed in %t',
filePath,
fileLintingEndTime - fileLintingStartTime,
);
}
const lintingDuration = hrtimeBigint() - lintingStartTime;
/*
* The net linting duration is the total linting time minus the time spent loading configs and reading files.
* It captures the processing time dedicated to computation-intensive tasks that are highly parallelizable and not repeated across threads.
*/
indexedResults.netLintingDuration =
lintingDuration - loadConfigTotalDuration - readFileCounter.duration;
parentPort.postMessage(indexedResults);
})();

View File

@@ -1056,7 +1056,7 @@ class SourceCode extends TokenStore {
// only certain comment types are supported as line comments
return (
comment.type !== "Line" ||
!!/^eslint-disable-(next-)?line$/u.test(directive.label)
!!/^eslint-disable-(?:next-)?line$/u.test(directive.label)
);
});
@@ -1091,9 +1091,8 @@ class SourceCode extends TokenStore {
} = commentParser.parseDirective(comment.value);
// Step 2: Extract the directive value
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(
label,
);
const lineCommentSupported =
/^eslint-disable-(?:next-)?line$/u.test(label);
if (comment.type === "Line" && !lineCommentSupported) {
return;

View File

@@ -271,6 +271,9 @@ function tryParseSelector(selector) {
) {
throw new SyntaxError(
`Syntax error in selector "${selector}" at position ${err.location.start.offset}: ${err.message}`,
{
cause: err,
},
);
}
throw err;

View File

@@ -14,7 +14,7 @@
* @returns {RegExp} Global regular expression matching placeholders
*/
function getPlaceholderMatcher() {
return /\{\{([^{}]+?)\}\}/gu;
return /\{\{([^{}]+)\}\}/gu;
}
/**

View File

@@ -438,9 +438,8 @@ function getDirectiveComments(
justification: justificationPart,
} = directive;
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(
label,
);
const lineCommentSupported =
/^eslint-disable-(?:next-)?line$/u.test(label);
if (comment.type === "Line" && !lineCommentSupported) {
return;
@@ -739,7 +738,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
return LATEST_ECMA_VERSION;
}
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
const eslintEnvPattern = /\/\*\s*eslint-env\s.+?(?:\*\/|$)/gsu;
/**
* Checks whether or not there is a comment which has "eslint-env *" in a given text.

32
node_modules/eslint/lib/options.js generated vendored
View File

@@ -23,6 +23,7 @@ const optionator = require("optionator");
* @property {string} [cacheLocation] Path to the cache file or directory
* @property {"metadata" | "content"} cacheStrategy Strategy to use for detecting changed files in the cache
* @property {boolean} [color] Force enabling/disabling of color
* @property {number | "auto" | "off"} [concurrency] Number of linting threads, "auto" to choose automatically, "off" for no multithreading
* @property {string} [config] Use this configuration, overriding .eslintrc.* config options if present
* @property {boolean} debug Output debugging information
* @property {string[]} [env] Specify environments
@@ -46,27 +47,27 @@ const optionator = require("optionator");
* @property {string} [outputFile] Specify file to write report to
* @property {string} [parser] Specify the parser to be used
* @property {Object} [parserOptions] Specify parser options
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
* @property {boolean} [passOnUnprunedSuppressions] Ignore unused suppressions
* @property {string[]} [plugin] Specify plugins
* @property {string} [printConfig] Print the configuration for the given file
* @property {boolean} [pruneSuppressions] Prune unused suppressions
* @property {boolean} quiet Report errors only
* @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable and eslint-enable directives
* @property {string | undefined} reportUnusedDisableDirectivesSeverity A severity string indicating if and how unused disable and enable directives should be tracked and reported.
* @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
* @property {Object} [rule] Specify rules
* @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins
* @property {boolean} [stats] Report additional statistics
* @property {boolean} stdin Lint code provided on <STDIN>
* @property {string} [stdinFilename] Specify filename to process STDIN as
* @property {boolean} quiet Report errors only
* @property {boolean} [suppressAll] Suppress all error violations
* @property {string} [suppressionsLocation] Path to the suppressions file or directory
* @property {string[]} [suppressRule] Suppress specific rules
* @property {boolean} [version] Output the version number
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
* @property {string[]} _ Positional filenames or patterns
* @property {boolean} [stats] Report additional statistics
* @property {boolean} [suppressAll] Suppress all error violations
* @property {string[]} [suppressRule] Suppress specific rules
* @property {string} [suppressionsLocation] Path to the suppressions file or directory
* @property {boolean} [pruneSuppressions] Prune unused suppressions
* @property {boolean} [passOnUnprunedSuppressions] Ignore unused suppressions
*/
//------------------------------------------------------------------------------
@@ -224,6 +225,18 @@ module.exports = function (usingFlatConfig) {
};
}
let concurrencyFlag;
if (usingFlatConfig) {
concurrencyFlag = {
option: "concurrency",
type: "Int|String",
default: "off",
description:
"Number of linting threads, auto to choose automatically, off for no multithreading",
};
}
return optionator({
prepend: "eslint [options] file.js [file.js] [dir]",
defaults: {
@@ -517,6 +530,7 @@ module.exports = function (usingFlatConfig) {
statsFlag,
flagFlag,
mcpFlag,
concurrencyFlag,
].filter(value => !!value),
});
};

View File

@@ -851,6 +851,9 @@ class RuleTester {
} catch (err) {
throw new Error(
`Schema for rule ${ruleName} is invalid: ${err.message}`,
{
cause: err,
},
);
}
}

View File

@@ -233,7 +233,6 @@ module.exports = {
url: "https://eslint.org/docs/latest/rules/array-callback-return",
},
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
hasSuggestions: true,
schema: [

View File

@@ -15,7 +15,7 @@ const keywords = require("./utils/keywords");
// Rule Definition
//------------------------------------------------------------------------------
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
const validIdentifier = /^[a-zA-Z_$][\w$]*$/u;
// `null` literal must be handled separately.
const literalTypesToCheck = new Set(["string", "boolean"]);

View File

@@ -87,8 +87,6 @@ function isAccessorKind(node) {
return node.kind === "get" || node.kind === "set";
}
const DEFAULT_ORDER = "anyOrder";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -98,7 +96,12 @@ module.exports = {
meta: {
type: "suggestion",
defaultOptions: [DEFAULT_ORDER],
defaultOptions: [
"anyOrder",
{
enforceForTSTypes: false,
},
],
docs: {
description:
@@ -129,10 +132,8 @@ module.exports = {
},
create(context) {
const order = context.options[0] ?? DEFAULT_ORDER;
const enforceForTSTypes =
context.options[1]?.enforceForTSTypes ?? false;
const sourceCode = context.sourceCode;
const [order, { enforceForTSTypes }] = context.options;
const { sourceCode } = context;
/**
* Reports the given accessor pair.

View File

@@ -1162,7 +1162,7 @@ module.exports = {
* @returns {boolean} the result
*/
function isWrappedInParenthesis(node) {
const regex = /^return\s*?\(\s*?\);*?/u;
const regex = /^return\s*\(\s*\)/u;
const statementWithoutArgument = sourceCode
.getText(node)

View File

@@ -293,6 +293,7 @@ module.exports = new LazyLoadingRuleMap(
"prefer-rest-params": () => require("./prefer-rest-params"),
"prefer-spread": () => require("./prefer-spread"),
"prefer-template": () => require("./prefer-template"),
"preserve-caught-error": () => require("./preserve-caught-error"),
"quote-props": () => require("./quote-props"),
quotes: () => require("./quotes"),
radix: () => require("./radix"),

View File

@@ -24,7 +24,7 @@ const {
* @returns {boolean} Whether or not the name is prohibited.
*/
function isProhibitedIdentifier(name) {
return /^(alert|confirm|prompt)$/u.test(name);
return /^(?:alert|confirm|prompt)$/u.test(name);
}
/**

View File

@@ -105,6 +105,7 @@ module.exports = {
meta: {
dialects: ["javascript", "typescript"],
language: "javascript",
hasSuggestions: true,
type: "suggestion",
defaultOptions: [{ allow: [] }],
@@ -131,6 +132,7 @@ module.exports = {
messages: {
unexpected: "Unexpected empty {{name}}.",
suggestComment: "Add comment inside empty {{name}}.",
},
},
@@ -162,7 +164,7 @@ module.exports = {
}
}
if (/(g|s)etters|methods$/iu.test(kind)) {
if (/(?:g|s)etters|methods$/iu.test(kind)) {
if (
(node.parent.decorators?.length &&
allow.includes("decoratedFunctions")) ||
@@ -204,6 +206,23 @@ module.exports = {
loc: node.body.loc,
messageId: "unexpected",
data: { name },
suggest: [
{
messageId: "suggestComment",
data: { name },
fix(fixer) {
const range = [
node.body.range[0] + 1,
node.body.range[1] - 1,
];
return fixer.replaceTextRange(
range,
" /* empty */ ",
);
},
},
],
});
}
}

View File

@@ -11,6 +11,7 @@
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
hasSuggestions: true,
type: "suggestion",
docs: {
@@ -23,6 +24,7 @@ module.exports = {
messages: {
unexpected: "Unexpected empty static block.",
suggestComment: "Add comment inside empty static block.",
},
},
@@ -32,14 +34,36 @@ module.exports = {
return {
StaticBlock(node) {
if (node.body.length === 0) {
const openingBrace = sourceCode.getFirstToken(node, {
skip: 1,
});
const closingBrace = sourceCode.getLastToken(node);
if (
sourceCode.getCommentsBefore(closingBrace).length === 0
) {
context.report({
node,
loc: {
start: openingBrace.loc.start,
end: closingBrace.loc.end,
},
messageId: "unexpected",
suggest: [
{
messageId: "suggestComment",
fix(fixer) {
const range = [
openingBrace.range[1],
closingBrace.range[0],
];
return fixer.replaceTextRange(
range,
" /* empty */ ",
);
},
},
],
});
}
}

View File

@@ -104,10 +104,47 @@ module.exports = {
typeof node.cases === "undefined" ||
node.cases.length === 0
) {
const openingBrace = sourceCode.getTokenAfter(
node.discriminant,
astUtils.isOpeningBraceToken,
);
const closingBrace = sourceCode.getLastToken(node);
if (
sourceCode.commentsExistBetween(
openingBrace,
closingBrace,
)
) {
return;
}
context.report({
node,
loc: {
start: openingBrace.loc.start,
end: closingBrace.loc.end,
},
messageId: "unexpected",
data: { type: "switch" },
suggest: [
{
messageId: "suggestComment",
data: { type: "switch" },
fix(fixer) {
const range = [
openingBrace.range[1],
closingBrace.range[0],
];
return fixer.replaceTextRange(
range,
" /* empty */ ",
);
},
},
],
});
}
},

View File

@@ -226,7 +226,9 @@ module.exports = {
Program(node) {
const scope = sourceCode.getScope(node),
features = context.parserOptions.ecmaFeatures || {},
features =
context.languageOptions.parserOptions.ecmaFeatures ||
{},
strict =
scope.isStrict ||
node.sourceType === "module" ||

View File

@@ -19,8 +19,8 @@ const astUtils = require("./utils/ast-utils");
const ALL_IRREGULARS =
/[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u;
const IRREGULAR_WHITESPACE =
/[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gmu;
const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gmu;
/[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gu;
const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gu;
const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
//------------------------------------------------------------------------------

View File

@@ -189,9 +189,32 @@ module.exports = {
* @returns {boolean} true if they do not match
*/
function baseTenLosesPrecision(node) {
const normalizedRawNumber = convertNumberToScientificNotation(
getRaw(node),
);
const rawNumber = getRaw(node).toLowerCase();
/*
* If trailing zeros equal the exponent, this is a valid representation
* like "9.00e2" where 00 = 2 (meaning 9.00 * 10^2 = 900)
* https://github.com/eslint/eslint/issues/19957
*/
if (rawNumber.includes(".") && rawNumber.includes("e")) {
const parts = rawNumber.split("e");
const coefficient = parts[0];
const exponent = parseInt(parts[1], 10);
// Count trailing zeros after decimal
const decimalParts = coefficient.split(".");
if (decimalParts.length === 2) {
const decimalPart = decimalParts[1];
const trailingZeros = decimalPart.match(/0*$/u)[0].length;
if (trailingZeros === exponent) {
return false;
}
}
}
const normalizedRawNumber =
convertNumberToScientificNotation(rawNumber);
const requestedPrecision = normalizedRawNumber
.split("e")[0]
.replace(".", "").length;

View File

@@ -278,6 +278,12 @@ module.exports = {
meta: {
type: "problem",
defaultOptions: [
{
allowEscape: false,
},
],
docs: {
description:
"Disallow characters which are made with multiple code points in character class syntax",
@@ -293,7 +299,6 @@ module.exports = {
properties: {
allowEscape: {
type: "boolean",
default: false,
},
},
additionalProperties: false,
@@ -313,7 +318,7 @@ module.exports = {
},
},
create(context) {
const allowEscape = context.options[0]?.allowEscape;
const [{ allowEscape }] = context.options;
const sourceCode = context.sourceCode;
const parser = new RegExpParser();
const checkedPatternNodes = new Set();

View File

@@ -99,6 +99,7 @@ module.exports = {
* At least one space followed by a tab
* before non-tab/-space characters begin.
*/
// eslint-disable-next-line regexp/no-empty-lookarounds-assertion -- False positive
regex = /^(?=(\t*))\1(?=( +))\2\t/u;
}

View File

@@ -30,10 +30,7 @@ module.exports = {
create(context) {
return {
Literal(node) {
if (
typeof node.value === "number" &&
/^0[0-9]/u.test(node.raw)
) {
if (typeof node.value === "number" && /^0\d/u.test(node.raw)) {
context.report({
node,
messageId: "noOctal",

View File

@@ -82,7 +82,8 @@ module.exports = {
create(context) {
const sourceCode = context.sourceCode;
const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
const BLANK_CLASS =
"[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u3000]",
SKIP_BLANK = `^${BLANK_CLASS}*$`,
NONBLANK = `${BLANK_CLASS}+$`;

View File

@@ -387,7 +387,7 @@ module.exports = {
const value = isTemplateElement
? sourceCode.getText(node)
: node.raw;
const pattern = /\\[^\d]/gu;
const pattern = /\\\D/gu;
let match;
while ((match = pattern.exec(value))) {

View File

@@ -522,7 +522,7 @@ module.exports = {
}
if (
!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.><?,'"|:;]*$/u.test(
!/^[-\w\\[\](){} \t\r\n\v\f!@#$%^&*+=/~`.><?,'"|:;]*$/u.test(
regexContent,
)
) {

539
node_modules/eslint/lib/rules/preserve-caught-error.js generated vendored Normal file
View File

@@ -0,0 +1,539 @@
/**
* @fileoverview Rule to preserve caught errors when re-throwing exceptions
* @author Amnish Singh Arora
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @typedef {import("estree").Node} ASTNode */
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/*
* This is an indicator of an error cause node, that is too complicated to be detected and fixed.
* Eg, when error options is an `Identifier` or a `SpreadElement`.
*/
const UNKNOWN_CAUSE = Symbol("unknown_cause");
const BUILT_IN_ERROR_TYPES = new Set([
"Error",
"EvalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
"AggregateError",
]);
/**
* Finds and returns information about the `cause` property of an error being thrown.
* @param {ASTNode} throwStatement `ThrowStatement` to be checked.
* @returns {{ value: ASTNode; multipleDefinitions: boolean; } | UNKNOWN_CAUSE | null}
* Information about the `cause` of the error being thrown, such as the value node and
* whether there are multiple definitions of `cause`. `null` if there is no `cause`.
*/
function getErrorCause(throwStatement) {
const throwExpression = throwStatement.argument;
/*
* Determine which argument index holds the options object
* `AggregateError` is a special case as it accepts the `options` object as third argument.
*/
const optionsIndex =
throwExpression.callee.name === "AggregateError" ? 2 : 1;
/*
* Make sure there is no `SpreadElement` at or before the `optionsIndex`
* as this messes up the effective order of arguments and makes it complicated
* to track where the actual error options need to be at
*/
const spreadExpressionIndex = throwExpression.arguments.findIndex(
arg => arg.type === "SpreadElement",
);
if (spreadExpressionIndex >= 0 && spreadExpressionIndex <= optionsIndex) {
return UNKNOWN_CAUSE;
}
const errorOptions = throwExpression.arguments[optionsIndex];
if (errorOptions) {
if (errorOptions.type === "ObjectExpression") {
if (
errorOptions.properties.some(
prop => prop.type === "SpreadElement",
)
) {
/*
* If there is a spread element as part of error options, it is too complicated
* to verify if the cause is used properly and auto-fix.
*/
return UNKNOWN_CAUSE;
}
const causeProperties = errorOptions.properties.filter(
prop =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "cause" &&
!prop.computed, // It is hard to accurately identify the value of computed props
);
const causeProperty = causeProperties.at(-1);
return causeProperty
? {
value: causeProperty.value,
multipleDefinitions: causeProperties.length > 1,
}
: null;
}
// Error options exist, but too complicated to be analyzed/fixed
return UNKNOWN_CAUSE;
}
return null;
}
/**
* Finds and returns the `CatchClause` node, that the `node` is part of.
* @param {ASTNode} node The AST node to be evaluated.
* @returns {ASTNode | null } The closest parent `CatchClause` node, `null` if the `node` is not in a catch block.
*/
function findParentCatch(node) {
let currentNode = node;
while (currentNode && currentNode.type !== "CatchClause") {
if (
[
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"StaticBlock",
].includes(currentNode.type)
) {
/*
* Make sure the ThrowStatement is not made inside a function definition or a static block inside a high level catch.
* In such cases, the caught error is not directly related to the Throw.
*
* For example,
* try {
* } catch (error) {
* foo = {
* bar() {
* throw new Error();
* }
* };
* }
*/
return null;
}
currentNode = currentNode.parent;
}
return currentNode;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
defaultOptions: [
{
requireCatchParameter: false,
},
],
docs: {
description:
"Disallow losing originally caught error when re-throwing custom errors",
recommended: false,
url: "https://eslint.org/docs/latest/rules/preserve-caught-error", // URL to the documentation page for this rule
},
/*
* TODO: We should allow passing `customErrorTypes` option once something like `typescript-eslint`'s
* `TypeOrValueSpecifier` is implemented in core Eslint.
* See:
* 1. https://typescript-eslint.io/packages/type-utils/type-or-value-specifier/
* 2. https://github.com/eslint/eslint/pull/19913#discussion_r2192608593
* 3. https://github.com/eslint/eslint/discussions/16540
*/
schema: [
{
type: "object",
properties: {
requireCatchParameter: {
type: "boolean",
description:
"Requires the catch blocks to always have the caught error parameter so it is not discarded.",
},
},
additionalProperties: false,
},
],
messages: {
missingCause:
"There is no `cause` attached to the symptom error being thrown.",
incorrectCause:
"The symptom error is being thrown with an incorrect `cause`.",
includeCause:
"Include the original caught error as the `cause` of the symptom error.",
missingCatchErrorParam:
"The caught error is not accessible because the catch clause lacks the error parameter. Start referencing the caught error using the catch parameter.",
partiallyLostError:
"Re-throws cannot preserve the caught error as a part of it is being lost due to destructuring.",
caughtErrorShadowed:
"The caught error is being attached as `cause`, but is shadowed by a closer scoped redeclaration.",
},
hasSuggestions: true,
},
create(context) {
const sourceCode = context.sourceCode;
const [{ requireCatchParameter }] = context.options;
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
/**
* Checks if a `ThrowStatement` is constructing and throwing a new `Error` object.
*
* Covers all the error types on `globalThis` that support `cause` property:
* https://github.com/microsoft/TypeScript/blob/main/src/lib/es2022.error.d.ts
* @param {ASTNode} throwStatement The `ThrowStatement` that needs to be checked.
* @returns {boolean} `true` if a new "Error" is being thrown, else `false`.
*/
function isThrowingNewError(throwStatement) {
return (
(throwStatement.argument.type === "NewExpression" ||
throwStatement.argument.type === "CallExpression") &&
throwStatement.argument.callee.type === "Identifier" &&
BUILT_IN_ERROR_TYPES.has(throwStatement.argument.callee.name) &&
/*
* Make sure the thrown Error is instance is one of the built-in global error types.
* Custom imports could shadow this, which would lead to false positives.
* e.g. import { Error } from "./my-custom-error.js";
* throw Error("Failed to perform error prone operations");
*/
sourceCode.isGlobalReference(throwStatement.argument.callee)
);
}
/**
* Inserts `cause: <caughtErrorName>` into an inline options object expression.
* @param {RuleFixer} fixer The fixer object.
* @param {ASTNode} optionsNode The options object node.
* @param {string} caughtErrorName The name of the caught error (e.g., "err").
* @returns {Fix} The fix object.
*/
function insertCauseIntoOptions(fixer, optionsNode, caughtErrorName) {
const properties = optionsNode.properties;
if (properties.length === 0) {
// Insert inside empty braces: `{}` → `{ cause: err }`
return fixer.insertTextAfter(
sourceCode.getFirstToken(optionsNode),
`cause: ${caughtErrorName}`,
);
}
const lastProp = properties.at(-1);
return fixer.insertTextAfter(
lastProp,
`, cause: ${caughtErrorName}`,
);
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ThrowStatement(node) {
// Check if the throw is inside a catch block
const parentCatch = findParentCatch(node);
const throwStatement = node;
// Check if a new error is being thrown in a catch block
if (parentCatch && isThrowingNewError(throwStatement)) {
if (
parentCatch.param &&
parentCatch.param.type !== "Identifier"
) {
/*
* When a part of the caught error is being lost at the parameter level, commonly due to destructuring.
* e.g. catch({ message, ...rest })
*/
context.report({
messageId: "partiallyLostError",
node: parentCatch,
});
return;
}
const caughtError =
parentCatch.param?.type === "Identifier"
? parentCatch.param
: null;
// Check if there are throw statements and caught error is being ignored
if (!caughtError) {
if (requireCatchParameter) {
context.report({
node: throwStatement,
messageId: "missingCatchErrorParam",
});
return;
}
return;
}
// Check if there is a cause attached to the new error
const errorCauseInfo = getErrorCause(throwStatement);
if (errorCauseInfo === UNKNOWN_CAUSE) {
// Error options exist, but too complicated to be analyzed/fixed
return;
}
if (errorCauseInfo === null) {
// If there is no `cause` attached to the error being thrown.
context.report({
messageId: "missingCause",
node: throwStatement,
suggest: [
{
messageId: "includeCause",
fix(fixer) {
const throwExpression =
throwStatement.argument;
const args = throwExpression.arguments;
const errorType =
throwExpression.callee.name;
// AggregateError: errors, message, options
if (errorType === "AggregateError") {
const errorsArg = args[0];
const messageArg = args[1];
const optionsArg = args[2];
if (!errorsArg) {
// Case: `throw new AggregateError()` → insert all arguments
const lastToken =
sourceCode.getLastToken(
throwExpression,
);
const lastCalleeToken =
sourceCode.getLastToken(
throwExpression.callee,
);
const parenToken =
sourceCode.getFirstTokenBetween(
lastCalleeToken,
lastToken,
astUtils.isOpeningParenToken,
);
if (parenToken) {
return fixer.insertTextAfter(
parenToken,
`[], "", { cause: ${caughtError.name} }`,
);
}
return fixer.insertTextAfter(
throwExpression.callee,
`([], "", { cause: ${caughtError.name} })`,
);
}
if (!messageArg) {
// Case: `throw new AggregateError([])` → insert message and options
return fixer.insertTextAfter(
errorsArg,
`, "", { cause: ${caughtError.name} }`,
);
}
if (!optionsArg) {
// Case: `throw new AggregateError([], "")` → insert error options only
return fixer.insertTextAfter(
messageArg,
`, { cause: ${caughtError.name} }`,
);
}
if (
optionsArg.type ===
"ObjectExpression"
) {
return insertCauseIntoOptions(
fixer,
optionsArg,
caughtError.name,
);
}
// Complex dynamic options — skip
return null;
}
// Normal Error types
const messageArg = args[0];
const optionsArg = args[1];
if (!messageArg) {
// Case: `throw new Error()` → insert both message and options
const lastToken =
sourceCode.getLastToken(
throwExpression,
);
const lastCalleeToken =
sourceCode.getLastToken(
throwExpression.callee,
);
const parenToken =
sourceCode.getFirstTokenBetween(
lastCalleeToken,
lastToken,
astUtils.isOpeningParenToken,
);
if (parenToken) {
return fixer.insertTextAfter(
parenToken,
`"", { cause: ${caughtError.name} }`,
);
}
return fixer.insertTextAfter(
throwExpression.callee,
`("", { cause: ${caughtError.name} })`,
);
}
if (!optionsArg) {
// Case: `throw new Error("Some message")` → insert only options
return fixer.insertTextAfter(
messageArg,
`, { cause: ${caughtError.name} }`,
);
}
if (
optionsArg.type ===
"ObjectExpression"
) {
return insertCauseIntoOptions(
fixer,
optionsArg,
caughtError.name,
);
}
return null; // Identifier or spread — do not fix
},
},
],
});
// We don't need to check further
return;
}
const { value: thrownErrorCause } = errorCauseInfo;
// If there is an attached cause, verify that it matches the caught error
if (
!(
thrownErrorCause.type === "Identifier" &&
thrownErrorCause.name === caughtError.name
)
) {
const suggest = errorCauseInfo.multipleDefinitions
? null // If there are multiple `cause` definitions, a suggestion could be confusing.
: [
{
messageId: "includeCause",
fix(fixer) {
/*
* In case `cause` is attached using object property shorthand or as a method or accessor.
* e.g. throw Error("fail", { cause });
* throw Error("fail", { cause() { doSomething(); } });
* throw Error("fail", { get cause() { return error; } });
*/
if (
thrownErrorCause.parent
.method ||
thrownErrorCause.parent
.shorthand ||
thrownErrorCause.parent.kind !==
"init"
) {
return fixer.replaceText(
thrownErrorCause.parent,
`cause: ${caughtError.name}`,
);
}
return fixer.replaceText(
thrownErrorCause,
caughtError.name,
);
},
},
];
context.report({
messageId: "incorrectCause",
node: thrownErrorCause,
suggest,
});
return;
}
/*
* If the attached cause matches the identifier name of the caught error,
* make sure it is not being shadowed by a closer scoped redeclaration.
*
* e.g. try {
* doSomething();
* } catch (error) {
* if (whatever) {
* const error = anotherError;
* throw new Error("Something went wrong");
* }
* }
*/
let scope = sourceCode.getScope(throwStatement);
do {
const variable = scope.set.get(caughtError.name);
if (variable) {
break;
}
scope = scope.upper;
} while (scope);
if (scope?.block !== parentCatch) {
// Caught error is being shadowed
context.report({
messageId: "caughtErrorShadowed",
node: throwStatement,
});
}
}
},
};
},
};

View File

@@ -47,6 +47,8 @@ module.exports = {
meta: {
type: "suggestion",
defaultOptions: [{}],
docs: {
description:
"Enforce the use of `u` or `v` flag on regular expressions",
@@ -79,7 +81,7 @@ module.exports = {
create(context) {
const sourceCode = context.sourceCode;
const { requireFlag } = context.options[0] ?? {};
const [{ requireFlag }] = context.options;
return {
"Literal[regex]"(node) {

View File

@@ -101,7 +101,8 @@ module.exports = {
},
create(context) {
const ecmaFeatures = context.parserOptions.ecmaFeatures || {},
const ecmaFeatures =
context.languageOptions.parserOptions.ecmaFeatures || {},
scopes = [],
classScopes = [];
let [mode] = context.options;

View File

@@ -58,7 +58,7 @@ const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN =
/^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
/^(?:[^\\]|\\.)*\\(?:[1-9]|0\d)/su;
const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);

View File

@@ -85,7 +85,7 @@ function readHexSequence(reader, length) {
* @returns {string} A code unit.
*/
function readUnicodeSequence(reader) {
const regExp = /\{(?<hexDigits>[\dA-Fa-f]+)\}/uy;
const regExp = /\{(?<hexDigits>[\dA-F]+)\}/iuy;
regExp.lastIndex = reader.pos;
const match = regExp.exec(reader.source);

View File

@@ -20,7 +20,7 @@ const astUtils = require("./utils/ast-utils");
* @returns {boolean} Whether or not it is a comparison operator.
*/
function isComparisonOperator(operator) {
return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
return /^(?:==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
}
/**
@@ -29,7 +29,7 @@ function isComparisonOperator(operator) {
* @returns {boolean} Whether or not it is an equality operator.
*/
function isEqualityOperator(operator) {
return /^(==|===)$/u.test(operator);
return /^(?:==|===)$/u.test(operator);
}
/**

View File

@@ -221,6 +221,9 @@ class SuppressionsService {
}
throw new Error(
`Failed to parse suppressions file at ${this.filePath}`,
{
cause: err,
},
);
}
}

View File

@@ -80,6 +80,19 @@ class WarningService {
emitInactiveFlagWarning(flag, message) {
this.emitWarning(message, `ESLintInactiveFlag_${flag}`);
}
/**
* Emits a warning when a suboptimal concurrency setting is detected.
* Currently, this is only used to warn when the net linting ratio is low.
* @param {string} notice A notice about how to improve performance.
* @returns {void}
*/
emitPoorConcurrencyWarning(notice) {
this.emitWarning(
`You may ${notice} to improve performance.`,
"ESLintPoorConcurrencyWarning",
);
}
}
module.exports = { WarningService };

View File

@@ -4,7 +4,7 @@
"use strict";
const NAMESPACE_REGEX = /^@.*\//iu;
const NAMESPACE_REGEX = /^@.*\//u;
/**
* Brings package name to correct format based on prefix

281
node_modules/eslint/lib/shared/translate-cli-options.js generated vendored Normal file
View File

@@ -0,0 +1,281 @@
/**
* @fileoverview Translates CLI options into ESLint constructor options.
* @author Nicholas C. Zakas
* @author Francesco Trotta
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { normalizeSeverityToString } = require("./severity");
const { getShorthandName, normalizePackageName } = require("./naming");
const { ModuleImporter } = require("@humanwhocodes/module-importer");
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @typedef {import("../types").ESLint.Options} ESLintOptions */
/** @typedef {import("../types").ESLint.LegacyOptions} LegacyESLintOptions */
/** @typedef {import("../types").Linter.LintMessage} LintMessage */
/** @typedef {import("../options").ParsedCLIOptions} ParsedCLIOptions */
/** @typedef {import("../types").ESLint.Plugin} Plugin */
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Loads plugins with the specified names.
* @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
* @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
* @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
*/
async function loadPlugins(importer, pluginNames) {
const plugins = {};
await Promise.all(
pluginNames.map(async pluginName => {
const longName = normalizePackageName(pluginName, "eslint-plugin");
const module = await importer.import(longName);
if (!("default" in module)) {
throw new Error(
`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`,
);
}
const shortName = getShorthandName(pluginName, "eslint-plugin");
plugins[shortName] = module.default;
}),
);
return plugins;
}
/**
* Predicate function for whether or not to apply fixes in quiet mode.
* If a message is a warning, do not apply a fix.
* @param {LintMessage} message The lint result.
* @returns {boolean} True if the lint message is an error (and thus should be
* autofixed), false otherwise.
*/
function quietFixPredicate(message) {
return message.severity === 2;
}
/**
* Predicate function for whether or not to run a rule in quiet mode.
* If a rule is set to warning, do not run it.
* @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
* @returns {boolean} True if the lint rule should run, false otherwise.
*/
function quietRuleFilter(rule) {
return rule.severity === 2;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Translates the CLI options into the options expected by the ESLint constructor.
* @param {ParsedCLIOptions} cliOptions The CLI options to translate.
* @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the config to generate.
* @returns {Promise<ESLintOptions | LegacyESLintOptions>} The options object for the ESLint constructor.
*/
async function translateOptions(
{
cache,
cacheFile,
cacheLocation,
cacheStrategy,
concurrency,
config,
configLookup,
env,
errorOnUnmatchedPattern,
eslintrc,
ext,
fix,
fixDryRun,
fixType,
flag,
global,
ignore,
ignorePath,
ignorePattern,
inlineConfig,
parser,
parserOptions,
plugin,
quiet,
reportUnusedDisableDirectives,
reportUnusedDisableDirectivesSeverity,
reportUnusedInlineConfigs,
resolvePluginsRelativeTo,
rule,
rulesdir,
stats,
warnIgnored,
passOnNoPatterns,
maxWarnings,
},
configType,
) {
let overrideConfig, overrideConfigFile;
const importer = new ModuleImporter();
if (configType === "flat") {
overrideConfigFile =
typeof config === "string" ? config : !configLookup;
if (overrideConfigFile === false) {
overrideConfigFile = void 0;
}
const languageOptions = {};
if (global) {
languageOptions.globals = global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, {});
}
if (parserOptions) {
languageOptions.parserOptions = parserOptions;
}
if (parser) {
languageOptions.parser = await importer.import(parser);
}
overrideConfig = [
{
...(Object.keys(languageOptions).length > 0
? { languageOptions }
: {}),
rules: rule ? rule : {},
},
];
if (
reportUnusedDisableDirectives ||
reportUnusedDisableDirectivesSeverity !== void 0
) {
overrideConfig[0].linterOptions = {
reportUnusedDisableDirectives: reportUnusedDisableDirectives
? "error"
: normalizeSeverityToString(
reportUnusedDisableDirectivesSeverity,
),
};
}
if (reportUnusedInlineConfigs !== void 0) {
overrideConfig[0].linterOptions = {
...overrideConfig[0].linterOptions,
reportUnusedInlineConfigs: normalizeSeverityToString(
reportUnusedInlineConfigs,
),
};
}
if (plugin) {
overrideConfig[0].plugins = await loadPlugins(importer, plugin);
}
if (ext) {
overrideConfig.push({
files: ext.map(
extension =>
`**/*${extension.startsWith(".") ? "" : "."}${extension}`,
),
});
}
} else {
overrideConfigFile = config;
overrideConfig = {
env:
env &&
env.reduce((obj, name) => {
obj[name] = true;
return obj;
}, {}),
globals:
global &&
global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, {}),
ignorePatterns: ignorePattern,
parser,
parserOptions,
plugins: plugin,
rules: rule,
};
}
const options = {
allowInlineConfig: inlineConfig,
cache,
cacheLocation: cacheLocation || cacheFile,
cacheStrategy,
errorOnUnmatchedPattern,
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
fixTypes: fixType,
ignore,
overrideConfig,
overrideConfigFile,
passOnNoPatterns,
};
if (configType === "flat") {
options.concurrency = concurrency;
options.flags = flag;
options.ignorePatterns = ignorePattern;
options.stats = stats;
options.warnIgnored = warnIgnored;
/*
* For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
* requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
*/
options.ruleFilter =
quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
} else {
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
options.rulePaths = rulesdir;
options.useEslintrc = eslintrc;
options.extensions = ext;
options.ignorePath = ignorePath;
if (
reportUnusedDisableDirectives ||
reportUnusedDisableDirectivesSeverity !== void 0
) {
options.reportUnusedDisableDirectives =
reportUnusedDisableDirectives
? "error"
: normalizeSeverityToString(
reportUnusedDisableDirectivesSeverity,
);
}
}
return options;
}
module.exports = translateOptions;

View File

@@ -92,6 +92,8 @@ export namespace Scope {
| "block"
| "catch"
| "class"
| "class-field-initializer"
| "class-static-block"
| "for"
| "function"
| "function-expression-name"
@@ -1966,6 +1968,10 @@ export class ESLint {
isPathIgnored(filePath: string): Promise<boolean>;
loadFormatter(nameOrPath?: string): Promise<ESLint.LoadedFormatter>;
static fromOptionsModule(optionsURL: {
readonly href: string;
}): Promise<ESLint>;
}
export namespace ESLint {
@@ -2047,6 +2053,7 @@ export namespace ESLint {
cacheStrategy?: CacheStrategy | undefined;
// Other Options
concurrency?: number | "auto" | "off" | undefined;
flags?: string[] | undefined;
}
@@ -2254,6 +2261,8 @@ export namespace RuleTester {
only?: boolean;
languageOptions?: Linter.LanguageOptions | undefined;
settings?: { [name: string]: any } | undefined;
before?: () => void;
after?: () => void;
}
interface SuggestionOutput {

View File

@@ -60,25 +60,19 @@ type EitherGroupOrRegEx =
// Base type for import name specifiers, ensuring mutual exclusivity
type EitherNameSpecifiers =
| {
importNames: string[];
importNames?: string[];
importNamePattern?: string;
allowImportNames?: never;
importNamePattern?: never;
allowImportNamePattern?: never;
}
| {
importNamePattern: string;
allowImportNames?: never;
importNames?: never;
allowImportNamePattern?: never;
}
| {
allowImportNames: string[];
allowImportNames?: string[];
importNames?: never;
importNamePattern?: never;
allowImportNamePattern?: never;
}
| {
allowImportNamePattern: string;
allowImportNamePattern?: string;
importNames?: never;
allowImportNames?: never;
importNamePattern?: never;
@@ -90,6 +84,18 @@ type ValidNoRestrictedImportPatternOptions =
EitherGroupOrRegEx &
EitherNameSpecifiers;
interface CapitalizedCommentsCommonOptions {
ignorePattern?: string;
/**
* @default false
*/
ignoreInlineComments?: boolean;
/**
* @default false
*/
ignoreConsecutiveComments?: boolean;
}
//-----------------------------------------------------------------------------
// Public types
//-----------------------------------------------------------------------------
@@ -117,7 +123,7 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
enforceForClassMembers: boolean;
/**
* @default true
* @default false
*/
enforceForTSTypes: boolean;
}>,
@@ -402,17 +408,13 @@ export interface ESLintRules extends Linter.RulesRecord {
"capitalized-comments": Linter.RuleEntry<
[
"always" | "never",
Partial<{
ignorePattern: string;
/**
* @default false
*/
ignoreInlineComments: boolean;
/**
* @default false
*/
ignoreConsecutiveComments: boolean;
}>,
(
| CapitalizedCommentsCommonOptions
| Partial<{
line: CapitalizedCommentsCommonOptions;
block: CapitalizedCommentsCommonOptions;
}>
),
]
>;
@@ -1020,6 +1022,7 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
properties: "always" | "never";
exceptions: string[];
exceptionPatterns: string[];
}>,
]
>;
@@ -1038,6 +1041,10 @@ export interface ESLintRules extends Linter.RulesRecord {
* @default false
*/
properties: boolean;
/**
* @default false
*/
classFields: boolean;
/**
* @default false
*/
@@ -1661,7 +1668,21 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 5.0.0-alpha.3
* @see https://eslint.org/docs/latest/rules/max-classes-per-file
*/
"max-classes-per-file": Linter.RuleEntry<[number]>;
"max-classes-per-file": Linter.RuleEntry<
[
| number
| Partial<{
/**
* @default false
*/
ignoreExpressions: boolean;
/**
* @default 1
*/
max: number;
}>,
]
>;
/**
* Rule to enforce a maximum depth that blocks can be nested.
@@ -1671,12 +1692,18 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"max-depth": Linter.RuleEntry<
[
Partial<{
/**
* @default 4
*/
max: number;
}>,
| number
| Partial<{
/**
* @deprecated
* @default 4
*/
maximum: number;
/**
* @default 4
*/
max: number;
}>,
]
>;
@@ -1764,24 +1791,25 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"max-lines-per-function": Linter.RuleEntry<
[
Partial<{
/**
* @default 50
*/
max: number;
/**
* @default false
*/
skipBlankLines: boolean;
/**
* @default false
*/
skipComments: boolean;
/**
* @default false
*/
IIFEs: boolean;
}>,
| number
| Partial<{
/**
* @default 50
*/
max: number;
/**
* @default false
*/
skipBlankLines: boolean;
/**
* @default false
*/
skipComments: boolean;
/**
* @default false
*/
IIFEs: boolean;
}>,
]
>;
@@ -1793,13 +1821,18 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"max-nested-callbacks": Linter.RuleEntry<
[
| number
| Partial<{
/**
* @deprecated
* @default 10
*/
maximum: number;
/**
* @default 10
*/
max: number;
}>
| number,
}>,
]
>;
@@ -1811,7 +1844,13 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"max-params": Linter.RuleEntry<
[
| number
| Partial<{
/**
* @deprecated
* @default 3
*/
maximum: number;
/**
* @default 3
*/
@@ -1820,8 +1859,7 @@ export interface ESLintRules extends Linter.RulesRecord {
* @default false
*/
countVoidThis: boolean;
}>
| number,
}>,
]
>;
@@ -1833,17 +1871,26 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"max-statements": Linter.RuleEntry<
[
| Partial<{
/**
* @default 10
*/
max: number;
/**
* @default false
*/
ignoreTopLevelFunctions: boolean;
}>
| number,
(
| number
| Partial<{
/**
* @deprecated
* @default 10
*/
maximum: number;
/**
* @default 10
*/
max: number;
}>
),
Partial<{
/**
* @default false
*/
ignoreTopLevelFunctions: boolean;
}>,
]
>;
@@ -2020,7 +2067,24 @@ export interface ESLintRules extends Linter.RulesRecord {
"no-bitwise": Linter.RuleEntry<
[
Partial<{
allow: string[];
/**
* @default []
*/
allow: Array<
| "^"
| "|"
| "&"
| "<<"
| ">>"
| ">>>"
| "^="
| "|="
| "&="
| "<<="
| ">>="
| ">>>="
| "~"
>;
/**
* @default false
*/
@@ -2172,9 +2236,9 @@ export interface ESLintRules extends Linter.RulesRecord {
[
{
/**
* @default true
* @default "allExceptWhileTrue"
*/
checkLoops: boolean;
checkLoops: "all" | "allExceptWhileTrue" | "none" | boolean;
},
]
>;
@@ -2401,7 +2465,16 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 1.7.0
* @see https://eslint.org/docs/latest/rules/no-empty-pattern
*/
"no-empty-pattern": Linter.RuleEntry<[]>;
"no-empty-pattern": Linter.RuleEntry<
[
Partial<{
/**
* @default false
*/
allowObjectPatternsAsParameters: boolean;
}>,
]
>;
/**
* Rule to disallow empty static blocks.
@@ -2674,7 +2747,16 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 2.0.0-alpha-1
* @see https://eslint.org/docs/latest/rules/no-implicit-globals
*/
"no-implicit-globals": Linter.RuleEntry<[]>;
"no-implicit-globals": Linter.RuleEntry<
[
Partial<{
/**
* @default false
*/
lexicalBindings: boolean;
}>,
]
>;
/**
* Rule to disallow the use of `eval()`-like methods.
@@ -2701,7 +2783,13 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 0.10.0
* @see https://eslint.org/docs/latest/rules/no-inline-comments
*/
"no-inline-comments": Linter.RuleEntry<[]>;
"no-inline-comments": Linter.RuleEntry<
[
Partial<{
ignorePattern: string;
}>,
]
>;
/**
* Rule to disallow variable or `function` declarations in nested blocks.
@@ -2709,7 +2797,17 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 0.6.0
* @see https://eslint.org/docs/latest/rules/no-inner-declarations
*/
"no-inner-declarations": Linter.RuleEntry<["functions" | "both"]>;
"no-inner-declarations": Linter.RuleEntry<
[
"functions" | "both",
Partial<{
/**
* @default "allow"
*/
blockScopedFunctions: "allow" | "disallow";
}>,
]
>;
/**
* Rule to disallow invalid regular expression strings in `RegExp` constructors.
@@ -2773,6 +2871,10 @@ export interface ESLintRules extends Linter.RulesRecord {
* @default false
*/
skipTemplates: boolean;
/**
* @default false
*/
skipJSXText: boolean;
}>,
]
>;
@@ -2861,11 +2963,19 @@ export interface ESLintRules extends Linter.RulesRecord {
/**
* @default []
*/
ignore: number[];
ignore: Array<number | string>;
/**
* @default false
*/
ignoreArrayIndexes: boolean;
/**
* @default false
*/
ignoreDefaultValues: boolean;
/**
* @default false
*/
ignoreClassFieldInitialValues: boolean;
/**
* @default false
*/
@@ -2987,7 +3097,16 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 3.14.0
* @see https://eslint.org/docs/latest/rules/no-multi-assign
*/
"no-multi-assign": Linter.RuleEntry<[]>;
"no-multi-assign": Linter.RuleEntry<
[
Partial<{
/**
* @default false
*/
ignoreNonDeclaration: boolean;
}>,
]
>;
/**
* Rule to disallow multiple spaces.
@@ -3441,9 +3560,9 @@ export interface ESLintRules extends Linter.RulesRecord {
paths: Array<
string | ValidNoRestrictedImportPathOptions
>;
patterns: Array<
string | ValidNoRestrictedImportPatternOptions
>;
patterns:
| Array<string>
| Array<ValidNoRestrictedImportPatternOptions>;
}>
>,
]
@@ -3561,7 +3680,16 @@ export interface ESLintRules extends Linter.RulesRecord {
* @since 2.0.0-rc.0
* @see https://eslint.org/docs/latest/rules/no-self-assign
*/
"no-self-assign": Linter.RuleEntry<[]>;
"no-self-assign": Linter.RuleEntry<
[
Partial<{
/**
* @default true
*/
props: boolean;
}>,
]
>;
/**
* Rule to disallow comparisons where both sides are exactly the same.
@@ -3935,12 +4063,13 @@ export interface ESLintRules extends Linter.RulesRecord {
/**
* @default []
*/
ignore:
ignore: Array<
| "WhileStatement"
| "DoWhileStatement"
| "ForStatement"
| "ForInStatement"
| "ForOfStatement";
| "ForOfStatement"
>;
}>,
]
>;
@@ -4299,7 +4428,7 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"no-warning-comments": Linter.RuleEntry<
[
{
Partial<{
/**
* @default ["todo", "fixme", "xxx"]
*/
@@ -4308,7 +4437,8 @@ export interface ESLintRules extends Linter.RulesRecord {
* @default 'start'
*/
location: "start" | "anywhere";
},
decoration: string[];
}>,
]
>;
@@ -4809,6 +4939,23 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"prefer-template": Linter.RuleEntry<[]>;
/**
* Rule to disallow losing originally caught error when re-throwing custom errors.
*
* @since 9.35.0
* @see https://eslint.org/docs/latest/rules/preserve-caught-error
*/
"preserve-caught-error": Linter.RuleEntry<
[
Partial<{
/**
* @default false
*/
requireCatchParameter: boolean;
}>,
]
>;
/**
* Rule to require quotes around object literal property names.
*
@@ -5331,7 +5478,7 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
enforceForSwitchCase: boolean;
/**
* @default true
* @default false
*/
enforceForIndexOf: boolean;
}>,