282 lines
7.4 KiB
JavaScript
282 lines
7.4 KiB
JavaScript
/**
|
|
* @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;
|