|                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |  | /** * @fileoverview Main CLI object. * @author Nicholas C. Zakas */
"use strict";
/* * NOTE: The CLI object should *not* call process.exit() directly. It should only return * exit codes. This allows other programs to use the CLI object and still control * when the program exits. */
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
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");
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions *//** @typedef {import("./eslint/eslint").LintMessage} LintMessage *//** @typedef {import("./eslint/eslint").LintResult} LintResult *//** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions *//** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const mkdir = promisify(fs.mkdir);const stat = promisify(fs.stat);const writeFile = promisify(fs.writeFile);
/** * 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;}
/** * 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,    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;}
/** * Count error messages. * @param {LintResult[]} results The lint results. * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages. */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 };}
/** * Check if a given file path is a directory or not. * @param {string} filePath The path to a file to check. * @returns {Promise<boolean>} `true` if the given path is a directory. */async function isDirectory(filePath) {    try {        return (await stat(filePath)).isDirectory();    } catch (error) {        if (error.code === "ENOENT" || error.code === "ENOTDIR") {            return false;        }        throw error;    }}
/** * Outputs the results of the linting. * @param {ESLint} engine The ESLint instance to use. * @param {LintResult[]} results The results to print. * @param {string} format The name of the formatter to use or the path to the formatter. * @param {string} outputFile The path for the output file. * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {Promise<boolean>} True if the printing succeeds, false if not. * @private */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;}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/** * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as * for other Node.js programs to effectively run the CLI. */const cli = {
    /**     * Executes the CLI based on an array of arguments that is passed in.     * @param {string|Array|Object} args The arguments to process.     * @param {string} [text] The text to lint (used for TTY).     * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.     * @returns {Promise<number>} The exit code for the operation.     */    async execute(args, text, allowFlatConfig) {        if (Array.isArray(args)) {            debug("CLI args: %o", args.slice(2));        }
        /*         * Before doing anything, we need to see if we are using a         * flat config file. If so, then we need to change the way command         * line args are parsed. This is temporary, and when we fully         * switch to flat config we can remove this logic.         */
        const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig();
        debug("Using flat config?", usingFlatConfig);
        const CLIOptions = createCLIOptions(usingFlatConfig);
        /** @type {ParsedCLIOptions} */        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,
                // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility
                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)) {
            // Errors and warnings from the original unfiltered results should determine the exit code
            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;
 |