/** * @fileoverview Main CLI object. * @author Nicholas C. Zakas */ "use strict"; /* * 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"), mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"); const debug = require("debug")("eslint:cli"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Predicate function for whether or not to apply fixes in quiet mode. * If a message is a warning, do not apply a fix. * @param {LintResult} lintResult The lint result. * @returns {boolean} True if the lint message is an error (and thus should be * autofixed), false otherwise. */ function quietFixPredicate(lintResult) { return lintResult.severity === 2; } /** * Translates the CLI options into the options expected by the CLIEngine. * @param {Object} cliOptions The CLI options to translate. * @returns {CLIEngineOptions} The options object for the CLIEngine. * @private */ function translateOptions(cliOptions) { return { envs: cliOptions.env, extensions: cliOptions.ext, rules: cliOptions.rule, plugins: cliOptions.plugin, globals: cliOptions.global, ignore: cliOptions.ignore, ignorePath: cliOptions.ignorePath, ignorePattern: cliOptions.ignorePattern, configFile: cliOptions.config, rulePaths: cliOptions.rulesdir, useEslintrc: cliOptions.eslintrc, parser: cliOptions.parser, parserOptions: cliOptions.parserOptions, cache: cliOptions.cache, cacheFile: cliOptions.cacheFile, cacheLocation: cliOptions.cacheLocation, fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true), fixTypes: cliOptions.fixType, allowInlineConfig: cliOptions.inlineConfig, reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives, resolvePluginsRelativeTo: cliOptions.resolvePluginsRelativeTo }; } /** * Outputs the results of the linting. * @param {CLIEngine} engine The CLIEngine 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. * @returns {boolean} True if the printing succeeds, false if not. * @private */ function printResults(engine, results, format, outputFile) { let formatter; let rulesMeta; try { formatter = engine.getFormatter(format); } catch (e) { log.error(e.message); return false; } const output = formatter(results, { get rulesMeta() { if (!rulesMeta) { rulesMeta = {}; for (const [ruleId, rule] of engine.getRules()) { rulesMeta[ruleId] = rule.meta; } } return rulesMeta; } }); if (output) { if (outputFile) { const filePath = path.resolve(process.cwd(), outputFile); if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { log.error("Cannot write to output file path, it is a directory: %s", outputFile); return false; } try { mkdirp.sync(path.dirname(filePath)); fs.writeFileSync(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). * @returns {int} The exit code for the operation. */ execute(args, text) { if (Array.isArray(args)) { debug("CLI args: %o", args.slice(2)); } let currentOptions; try { currentOptions = options.parse(args); } catch (error) { log.error(error.message); return 2; } const files = currentOptions._; const useStdin = typeof text === "string"; if (currentOptions.version) { log.info(RuntimeInfo.version()); } else if (currentOptions.envInfo) { try { log.info(RuntimeInfo.environment()); return 0; } catch (err) { log.error(err.message); return 2; } } else if (currentOptions.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 = new CLIEngine(translateOptions(currentOptions)); const fileConfig = engine.getConfigForFile(currentOptions.printConfig); log.info(JSON.stringify(fileConfig, null, " ")); return 0; } else if (currentOptions.help || (!files.length && !useStdin)) { log.info(options.generateHelp()); } else { debug(`Running on ${useStdin ? "text" : "files"}`); if (currentOptions.fix && currentOptions.fixDryRun) { log.error("The --fix option and the --fix-dry-run option cannot be used together."); return 2; } if (useStdin && currentOptions.fix) { log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead."); return 2; } if (currentOptions.fixType && !currentOptions.fix && !currentOptions.fixDryRun) { log.error("The --fix-type option requires either --fix or --fix-dry-run."); return 2; } const engine = new CLIEngine(translateOptions(currentOptions)); const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files); if (currentOptions.fix) { debug("Fix mode enabled - applying fixes"); CLIEngine.outputFixes(report); } if (currentOptions.quiet) { debug("Quiet mode enabled - filtering out warnings"); report.results = CLIEngine.getErrorResults(report.results); } if (printResults(engine, report.results, currentOptions.format, currentOptions.outputFile)) { const tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings; if (!report.errorCount && tooManyWarnings) { log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings); } return (report.errorCount || tooManyWarnings) ? 1 : 0; } return 2; } return 0; } }; module.exports = cli;