|
|
|
|
|
|
|
|
|
|
|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fs = require("fs"), |
|
path = require("path"), |
|
{ promisify } = require("util"), |
|
{ ESLint } = require("./eslint"), |
|
{ FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"), |
|
createCLIOptions = require("./options"), |
|
log = require("./shared/logging"), |
|
RuntimeInfo = require("./shared/runtime-info"), |
|
{ normalizeSeverityToString } = require("./shared/severity"); |
|
const { Legacy: { naming } } = require("@eslint/eslintrc"); |
|
const { ModuleImporter } = require("@humanwhocodes/module-importer"); |
|
|
|
const debug = require("debug")("eslint:cli"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const mkdir = promisify(fs.mkdir); |
|
const stat = promisify(fs.stat); |
|
const writeFile = promisify(fs.writeFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function quietFixPredicate(message) { |
|
return message.severity === 2; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function translateOptions({ |
|
cache, |
|
cacheFile, |
|
cacheLocation, |
|
cacheStrategy, |
|
config, |
|
configLookup, |
|
env, |
|
errorOnUnmatchedPattern, |
|
eslintrc, |
|
ext, |
|
fix, |
|
fixDryRun, |
|
fixType, |
|
global, |
|
ignore, |
|
ignorePath, |
|
ignorePattern, |
|
inlineConfig, |
|
parser, |
|
parserOptions, |
|
plugin, |
|
quiet, |
|
reportUnusedDisableDirectives, |
|
reportUnusedDisableDirectivesSeverity, |
|
resolvePluginsRelativeTo, |
|
rule, |
|
rulesdir, |
|
warnIgnored |
|
}, configType) { |
|
|
|
let overrideConfig, overrideConfigFile; |
|
const importer = new ModuleImporter(); |
|
|
|
if (configType === "flat") { |
|
overrideConfigFile = (typeof config === "string") ? config : !configLookup; |
|
if (overrideConfigFile === false) { |
|
overrideConfigFile = void 0; |
|
} |
|
|
|
let globals = {}; |
|
|
|
if (global) { |
|
globals = global.reduce((obj, name) => { |
|
if (name.endsWith(":true")) { |
|
obj[name.slice(0, -5)] = "writable"; |
|
} else { |
|
obj[name] = "readonly"; |
|
} |
|
return obj; |
|
}, globals); |
|
} |
|
|
|
overrideConfig = [{ |
|
languageOptions: { |
|
globals, |
|
parserOptions: parserOptions || {} |
|
}, |
|
rules: rule ? rule : {} |
|
}]; |
|
|
|
if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) { |
|
overrideConfig[0].linterOptions = { |
|
reportUnusedDisableDirectives: reportUnusedDisableDirectives |
|
? "error" |
|
: normalizeSeverityToString(reportUnusedDisableDirectivesSeverity) |
|
}; |
|
} |
|
|
|
if (parser) { |
|
overrideConfig[0].languageOptions.parser = await importer.import(parser); |
|
} |
|
|
|
if (plugin) { |
|
const plugins = {}; |
|
|
|
for (const pluginName of plugin) { |
|
|
|
const shortName = naming.getShorthandName(pluginName, "eslint-plugin"); |
|
const longName = naming.normalizePackageName(pluginName, "eslint-plugin"); |
|
|
|
plugins[shortName] = await importer.import(longName); |
|
} |
|
|
|
overrideConfig[0].plugins = plugins; |
|
} |
|
|
|
} 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 |
|
}; |
|
|
|
if (configType === "flat") { |
|
options.ignorePatterns = ignorePattern; |
|
options.warnIgnored = warnIgnored; |
|
} 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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function countErrors(results) { |
|
let errorCount = 0; |
|
let fatalErrorCount = 0; |
|
let warningCount = 0; |
|
|
|
for (const result of results) { |
|
errorCount += result.errorCount; |
|
fatalErrorCount += result.fatalErrorCount; |
|
warningCount += result.warningCount; |
|
} |
|
|
|
return { errorCount, fatalErrorCount, warningCount }; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async function isDirectory(filePath) { |
|
try { |
|
return (await stat(filePath)).isDirectory(); |
|
} catch (error) { |
|
if (error.code === "ENOENT" || error.code === "ENOTDIR") { |
|
return false; |
|
} |
|
throw error; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function printResults(engine, results, format, outputFile, resultsMeta) { |
|
let formatter; |
|
|
|
try { |
|
formatter = await engine.loadFormatter(format); |
|
} catch (e) { |
|
log.error(e.message); |
|
return false; |
|
} |
|
|
|
const output = await formatter.format(results, resultsMeta); |
|
|
|
if (output) { |
|
if (outputFile) { |
|
const filePath = path.resolve(process.cwd(), outputFile); |
|
|
|
if (await isDirectory(filePath)) { |
|
log.error("Cannot write to output file path, it is a directory: %s", outputFile); |
|
return false; |
|
} |
|
|
|
try { |
|
await mkdir(path.dirname(filePath), { recursive: true }); |
|
await writeFile(filePath, output); |
|
} catch (ex) { |
|
log.error("There was a problem writing the output file:\n%s", ex); |
|
return false; |
|
} |
|
} else { |
|
log.info(output); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cli = { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async execute(args, text, allowFlatConfig) { |
|
if (Array.isArray(args)) { |
|
debug("CLI args: %o", args.slice(2)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig(); |
|
|
|
debug("Using flat config?", usingFlatConfig); |
|
|
|
const CLIOptions = createCLIOptions(usingFlatConfig); |
|
|
|
|
|
let options; |
|
|
|
try { |
|
options = CLIOptions.parse(args); |
|
} catch (error) { |
|
debug("Error parsing CLI options:", error.message); |
|
|
|
let errorMessage = error.message; |
|
|
|
if (usingFlatConfig) { |
|
errorMessage += "\nYou're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details."; |
|
} |
|
|
|
log.error(errorMessage); |
|
return 2; |
|
} |
|
|
|
const files = options._; |
|
const useStdin = typeof text === "string"; |
|
|
|
if (options.help) { |
|
log.info(CLIOptions.generateHelp()); |
|
return 0; |
|
} |
|
if (options.version) { |
|
log.info(RuntimeInfo.version()); |
|
return 0; |
|
} |
|
if (options.envInfo) { |
|
try { |
|
log.info(RuntimeInfo.environment()); |
|
return 0; |
|
} catch (err) { |
|
debug("Error retrieving environment info"); |
|
log.error(err.message); |
|
return 2; |
|
} |
|
} |
|
|
|
if (options.printConfig) { |
|
if (files.length) { |
|
log.error("The --print-config option must be used with exactly one file name."); |
|
return 2; |
|
} |
|
if (useStdin) { |
|
log.error("The --print-config option is not available for piped-in code."); |
|
return 2; |
|
} |
|
|
|
const engine = usingFlatConfig |
|
? new FlatESLint(await translateOptions(options, "flat")) |
|
: new ESLint(await translateOptions(options)); |
|
const fileConfig = |
|
await engine.calculateConfigForFile(options.printConfig); |
|
|
|
log.info(JSON.stringify(fileConfig, null, " ")); |
|
return 0; |
|
} |
|
|
|
debug(`Running on ${useStdin ? "text" : "files"}`); |
|
|
|
if (options.fix && options.fixDryRun) { |
|
log.error("The --fix option and the --fix-dry-run option cannot be used together."); |
|
return 2; |
|
} |
|
if (useStdin && options.fix) { |
|
log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead."); |
|
return 2; |
|
} |
|
if (options.fixType && !options.fix && !options.fixDryRun) { |
|
log.error("The --fix-type option requires either --fix or --fix-dry-run."); |
|
return 2; |
|
} |
|
|
|
if (options.reportUnusedDisableDirectives && options.reportUnusedDisableDirectivesSeverity !== void 0) { |
|
log.error("The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together."); |
|
return 2; |
|
} |
|
|
|
const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint; |
|
|
|
const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc")); |
|
let results; |
|
|
|
if (useStdin) { |
|
results = await engine.lintText(text, { |
|
filePath: options.stdinFilename, |
|
|
|
|
|
warnIgnored: usingFlatConfig ? void 0 : true |
|
}); |
|
} else { |
|
results = await engine.lintFiles(files); |
|
} |
|
|
|
if (options.fix) { |
|
debug("Fix mode enabled - applying fixes"); |
|
await ActiveESLint.outputFixes(results); |
|
} |
|
|
|
let resultsToPrint = results; |
|
|
|
if (options.quiet) { |
|
debug("Quiet mode enabled - filtering out warnings"); |
|
resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); |
|
} |
|
|
|
const resultCounts = countErrors(results); |
|
const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings; |
|
const resultsMeta = tooManyWarnings |
|
? { |
|
maxWarningsExceeded: { |
|
maxWarnings: options.maxWarnings, |
|
foundWarnings: resultCounts.warningCount |
|
} |
|
} |
|
: {}; |
|
|
|
if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) { |
|
|
|
|
|
const shouldExitForFatalErrors = |
|
options.exitOnFatalError && resultCounts.fatalErrorCount > 0; |
|
|
|
if (!resultCounts.errorCount && tooManyWarnings) { |
|
log.error( |
|
"ESLint found too many warnings (maximum: %s).", |
|
options.maxWarnings |
|
); |
|
} |
|
|
|
if (shouldExitForFatalErrors) { |
|
return 2; |
|
} |
|
|
|
return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0; |
|
} |
|
|
|
return 2; |
|
} |
|
}; |
|
|
|
module.exports = cli; |
|
|