.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / cli.js
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
18 const fs = require("fs"),
19     path = require("path"),
20     { promisify } = require("util"),
21     { ESLint } = require("./eslint"),
22     CLIOptions = require("./options"),
23     log = require("./shared/logging"),
24     RuntimeInfo = require("./shared/runtime-info");
25
26 const debug = require("debug")("eslint:cli");
27
28 //------------------------------------------------------------------------------
29 // Types
30 //------------------------------------------------------------------------------
31
32 /** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions */
33 /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
34 /** @typedef {import("./eslint/eslint").LintResult} LintResult */
35 /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
36
37 //------------------------------------------------------------------------------
38 // Helpers
39 //------------------------------------------------------------------------------
40
41 const mkdir = promisify(fs.mkdir);
42 const stat = promisify(fs.stat);
43 const writeFile = promisify(fs.writeFile);
44
45 /**
46  * Predicate function for whether or not to apply fixes in quiet mode.
47  * If a message is a warning, do not apply a fix.
48  * @param {LintMessage} message The lint result.
49  * @returns {boolean} True if the lint message is an error (and thus should be
50  * autofixed), false otherwise.
51  */
52 function quietFixPredicate(message) {
53     return message.severity === 2;
54 }
55
56 /**
57  * Translates the CLI options into the options expected by the CLIEngine.
58  * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
59  * @returns {ESLintOptions} The options object for the CLIEngine.
60  * @private
61  */
62 function translateOptions({
63     cache,
64     cacheFile,
65     cacheLocation,
66     cacheStrategy,
67     config,
68     env,
69     errorOnUnmatchedPattern,
70     eslintrc,
71     ext,
72     fix,
73     fixDryRun,
74     fixType,
75     global,
76     ignore,
77     ignorePath,
78     ignorePattern,
79     inlineConfig,
80     parser,
81     parserOptions,
82     plugin,
83     quiet,
84     reportUnusedDisableDirectives,
85     resolvePluginsRelativeTo,
86     rule,
87     rulesdir
88 }) {
89     return {
90         allowInlineConfig: inlineConfig,
91         cache,
92         cacheLocation: cacheLocation || cacheFile,
93         cacheStrategy,
94         errorOnUnmatchedPattern,
95         extensions: ext,
96         fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
97         fixTypes: fixType,
98         ignore,
99         ignorePath,
100         overrideConfig: {
101             env: env && env.reduce((obj, name) => {
102                 obj[name] = true;
103                 return obj;
104             }, {}),
105             globals: global && global.reduce((obj, name) => {
106                 if (name.endsWith(":true")) {
107                     obj[name.slice(0, -5)] = "writable";
108                 } else {
109                     obj[name] = "readonly";
110                 }
111                 return obj;
112             }, {}),
113             ignorePatterns: ignorePattern,
114             parser,
115             parserOptions,
116             plugins: plugin,
117             rules: rule
118         },
119         overrideConfigFile: config,
120         reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
121         resolvePluginsRelativeTo,
122         rulePaths: rulesdir,
123         useEslintrc: eslintrc
124     };
125 }
126
127 /**
128  * Count error messages.
129  * @param {LintResult[]} results The lint results.
130  * @returns {{errorCount:number;warningCount:number}} The number of error messages.
131  */
132 function countErrors(results) {
133     let errorCount = 0;
134     let warningCount = 0;
135
136     for (const result of results) {
137         errorCount += result.errorCount;
138         warningCount += result.warningCount;
139     }
140
141     return { errorCount, warningCount };
142 }
143
144 /**
145  * Check if a given file path is a directory or not.
146  * @param {string} filePath The path to a file to check.
147  * @returns {Promise<boolean>} `true` if the given path is a directory.
148  */
149 async function isDirectory(filePath) {
150     try {
151         return (await stat(filePath)).isDirectory();
152     } catch (error) {
153         if (error.code === "ENOENT" || error.code === "ENOTDIR") {
154             return false;
155         }
156         throw error;
157     }
158 }
159
160 /**
161  * Outputs the results of the linting.
162  * @param {ESLint} engine The ESLint instance to use.
163  * @param {LintResult[]} results The results to print.
164  * @param {string} format The name of the formatter to use or the path to the formatter.
165  * @param {string} outputFile The path for the output file.
166  * @returns {Promise<boolean>} True if the printing succeeds, false if not.
167  * @private
168  */
169 async function printResults(engine, results, format, outputFile) {
170     let formatter;
171
172     try {
173         formatter = await engine.loadFormatter(format);
174     } catch (e) {
175         log.error(e.message);
176         return false;
177     }
178
179     const output = formatter.format(results);
180
181     if (output) {
182         if (outputFile) {
183             const filePath = path.resolve(process.cwd(), outputFile);
184
185             if (await isDirectory(filePath)) {
186                 log.error("Cannot write to output file path, it is a directory: %s", outputFile);
187                 return false;
188             }
189
190             try {
191                 await mkdir(path.dirname(filePath), { recursive: true });
192                 await writeFile(filePath, output);
193             } catch (ex) {
194                 log.error("There was a problem writing the output file:\n%s", ex);
195                 return false;
196             }
197         } else {
198             log.info(output);
199         }
200     }
201
202     return true;
203 }
204
205 //------------------------------------------------------------------------------
206 // Public Interface
207 //------------------------------------------------------------------------------
208
209 /**
210  * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
211  * for other Node.js programs to effectively run the CLI.
212  */
213 const cli = {
214
215     /**
216      * Executes the CLI based on an array of arguments that is passed in.
217      * @param {string|Array|Object} args The arguments to process.
218      * @param {string} [text] The text to lint (used for TTY).
219      * @returns {Promise<number>} The exit code for the operation.
220      */
221     async execute(args, text) {
222         if (Array.isArray(args)) {
223             debug("CLI args: %o", args.slice(2));
224         }
225
226         /** @type {ParsedCLIOptions} */
227         let options;
228
229         try {
230             options = CLIOptions.parse(args);
231         } catch (error) {
232             log.error(error.message);
233             return 2;
234         }
235
236         const files = options._;
237         const useStdin = typeof text === "string";
238
239         if (options.help) {
240             log.info(CLIOptions.generateHelp());
241             return 0;
242         }
243         if (options.version) {
244             log.info(RuntimeInfo.version());
245             return 0;
246         }
247         if (options.envInfo) {
248             try {
249                 log.info(RuntimeInfo.environment());
250                 return 0;
251             } catch (err) {
252                 log.error(err.message);
253                 return 2;
254             }
255         }
256
257         if (options.printConfig) {
258             if (files.length) {
259                 log.error("The --print-config option must be used with exactly one file name.");
260                 return 2;
261             }
262             if (useStdin) {
263                 log.error("The --print-config option is not available for piped-in code.");
264                 return 2;
265             }
266
267             const engine = new ESLint(translateOptions(options));
268             const fileConfig =
269                 await engine.calculateConfigForFile(options.printConfig);
270
271             log.info(JSON.stringify(fileConfig, null, "  "));
272             return 0;
273         }
274
275         debug(`Running on ${useStdin ? "text" : "files"}`);
276
277         if (options.fix && options.fixDryRun) {
278             log.error("The --fix option and the --fix-dry-run option cannot be used together.");
279             return 2;
280         }
281         if (useStdin && options.fix) {
282             log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
283             return 2;
284         }
285         if (options.fixType && !options.fix && !options.fixDryRun) {
286             log.error("The --fix-type option requires either --fix or --fix-dry-run.");
287             return 2;
288         }
289
290         const engine = new ESLint(translateOptions(options));
291         let results;
292
293         if (useStdin) {
294             results = await engine.lintText(text, {
295                 filePath: options.stdinFilename,
296                 warnIgnored: true
297             });
298         } else {
299             results = await engine.lintFiles(files);
300         }
301
302         if (options.fix) {
303             debug("Fix mode enabled - applying fixes");
304             await ESLint.outputFixes(results);
305         }
306
307         let resultsToPrint = results;
308
309         if (options.quiet) {
310             debug("Quiet mode enabled - filtering out warnings");
311             resultsToPrint = ESLint.getErrorResults(resultsToPrint);
312         }
313
314         if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
315
316             // Errors and warnings from the original unfiltered results should determine the exit code
317             const { errorCount, warningCount } = countErrors(results);
318             const tooManyWarnings =
319                 options.maxWarnings >= 0 && warningCount > options.maxWarnings;
320
321             if (!errorCount && tooManyWarnings) {
322                 log.error(
323                     "ESLint found too many warnings (maximum: %s).",
324                     options.maxWarnings
325                 );
326             }
327
328             return (errorCount || tooManyWarnings) ? 1 : 0;
329         }
330
331         return 2;
332     }
333 };
334
335 module.exports = cli;