UNPKG

6.48 kBJavaScriptView Raw
1/**
2 * @fileoverview Main CLI object.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8/*
9 * The CLI object should *not* call process.exit() directly. It should only return
10 * exit codes. This allows other programs to use the CLI object and still control
11 * when the program exits.
12 */
13
14//------------------------------------------------------------------------------
15// Requirements
16//------------------------------------------------------------------------------
17
18const fs = require("fs"),
19 path = require("path"),
20 shell = require("shelljs"),
21 options = require("./options"),
22 CLIEngine = require("./cli-engine"),
23 mkdirp = require("mkdirp"),
24 log = require("./logging");
25
26const debug = require("debug")("eslint:cli");
27
28//------------------------------------------------------------------------------
29// Helpers
30//------------------------------------------------------------------------------
31
32/**
33 * Translates the CLI options into the options expected by the CLIEngine.
34 * @param {Object} cliOptions The CLI options to translate.
35 * @returns {CLIEngineOptions} The options object for the CLIEngine.
36 * @private
37 */
38function translateOptions(cliOptions) {
39 return {
40 envs: cliOptions.env,
41 extensions: cliOptions.ext,
42 rules: cliOptions.rule,
43 plugins: cliOptions.plugin,
44 globals: cliOptions.global,
45 ignore: cliOptions.ignore,
46 ignorePath: cliOptions.ignorePath,
47 ignorePattern: cliOptions.ignorePattern,
48 configFile: cliOptions.config,
49 rulePaths: cliOptions.rulesdir,
50 useEslintrc: cliOptions.eslintrc,
51 parser: cliOptions.parser,
52 parserOptions: cliOptions.parserOptions,
53 cache: cliOptions.cache,
54 cacheFile: cliOptions.cacheFile,
55 cacheLocation: cliOptions.cacheLocation,
56 fix: cliOptions.fix,
57 allowInlineConfig: cliOptions.inlineConfig
58 };
59}
60
61/**
62 * Outputs the results of the linting.
63 * @param {CLIEngine} engine The CLIEngine to use.
64 * @param {LintResult[]} results The results to print.
65 * @param {string} format The name of the formatter to use or the path to the formatter.
66 * @param {string} outputFile The path for the output file.
67 * @returns {boolean} True if the printing succeeds, false if not.
68 * @private
69 */
70function printResults(engine, results, format, outputFile) {
71 let formatter;
72
73 try {
74 formatter = engine.getFormatter(format);
75 } catch (e) {
76 log.error(e.message);
77 return false;
78 }
79
80 const output = formatter(results);
81
82 if (output) {
83 if (outputFile) {
84 const filePath = path.resolve(process.cwd(), outputFile);
85
86 if (shell.test("-d", filePath)) {
87 log.error("Cannot write to output file path, it is a directory: %s", outputFile);
88 return false;
89 }
90
91 try {
92 mkdirp.sync(path.dirname(filePath));
93 fs.writeFileSync(filePath, output);
94 } catch (ex) {
95 log.error("There was a problem writing the output file:\n%s", ex);
96 return false;
97 }
98 } else {
99 log.info(output);
100 }
101 }
102
103 return true;
104
105}
106
107//------------------------------------------------------------------------------
108// Public Interface
109//------------------------------------------------------------------------------
110
111/**
112 * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
113 * for other Node.js programs to effectively run the CLI.
114 */
115const cli = {
116
117 /**
118 * Executes the CLI based on an array of arguments that is passed in.
119 * @param {string|Array|Object} args The arguments to process.
120 * @param {string} [text] The text to lint (used for TTY).
121 * @returns {int} The exit code for the operation.
122 */
123 execute(args, text) {
124
125 let currentOptions;
126
127 try {
128 currentOptions = options.parse(args);
129 } catch (error) {
130 log.error(error.message);
131 return 1;
132 }
133
134 const files = currentOptions._;
135
136 if (currentOptions.version) { // version from package.json
137
138 log.info("v" + require("../package.json").version);
139
140 } else if (currentOptions.help || (!files.length && !text)) {
141
142 log.info(options.generateHelp());
143
144 } else {
145
146 debug("Running on " + (text ? "text" : "files"));
147
148 // disable --fix for piped-in code until we know how to do it correctly
149 if (text && currentOptions.fix) {
150 log.error("The --fix option is not available for piped-in code.");
151 return 1;
152 }
153
154 const engine = new CLIEngine(translateOptions(currentOptions));
155
156 if (currentOptions.printConfig) {
157 if (files.length !== 1) {
158 log.error("The --print-config option requires a " +
159 "single file as positional argument.");
160 return 1;
161 }
162
163 if (text) {
164 log.error("The --print-config option is not available for piped-in code.");
165 return 1;
166 }
167
168 const fileConfig = engine.getConfigForFile(files[0]);
169
170 log.info(JSON.stringify(fileConfig, null, " "));
171 return 0;
172 }
173
174 const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
175
176 if (currentOptions.fix) {
177 debug("Fix mode enabled - applying fixes");
178 CLIEngine.outputFixes(report);
179 }
180
181 if (currentOptions.quiet) {
182 debug("Quiet mode enabled - filtering out warnings");
183 report.results = CLIEngine.getErrorResults(report.results);
184 }
185
186 if (printResults(engine, report.results, currentOptions.format, currentOptions.outputFile)) {
187 const tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings;
188
189 if (!report.errorCount && tooManyWarnings) {
190 log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings);
191 }
192
193 return (report.errorCount || tooManyWarnings) ? 1 : 0;
194 } else {
195 return 1;
196 }
197
198 }
199
200 return 0;
201 }
202};
203
204module.exports = cli;