massive update, probably broken
[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 fatalErrorCount = 0;
135     let warningCount = 0;
136
137     for (const result of results) {
138         errorCount += result.errorCount;
139         fatalErrorCount += result.fatalErrorCount;
140         warningCount += result.warningCount;
141     }
142
143     return { errorCount, fatalErrorCount, warningCount };
144 }
145
146 /**
147  * Check if a given file path is a directory or not.
148  * @param {string} filePath The path to a file to check.
149  * @returns {Promise<boolean>} `true` if the given path is a directory.
150  */
151 async function isDirectory(filePath) {
152     try {
153         return (await stat(filePath)).isDirectory();
154     } catch (error) {
155         if (error.code === "ENOENT" || error.code === "ENOTDIR") {
156             return false;
157         }
158         throw error;
159     }
160 }
161
162 /**
163  * Outputs the results of the linting.
164  * @param {ESLint} engine The ESLint instance to use.
165  * @param {LintResult[]} results The results to print.
166  * @param {string} format The name of the formatter to use or the path to the formatter.
167  * @param {string} outputFile The path for the output file.
168  * @returns {Promise<boolean>} True if the printing succeeds, false if not.
169  * @private
170  */
171 async function printResults(engine, results, format, outputFile) {
172     let formatter;
173
174     try {
175         formatter = await engine.loadFormatter(format);
176     } catch (e) {
177         log.error(e.message);
178         return false;
179     }
180
181     const output = formatter.format(results);
182
183     if (output) {
184         if (outputFile) {
185             const filePath = path.resolve(process.cwd(), outputFile);
186
187             if (await isDirectory(filePath)) {
188                 log.error("Cannot write to output file path, it is a directory: %s", outputFile);
189                 return false;
190             }
191
192             try {
193                 await mkdir(path.dirname(filePath), { recursive: true });
194                 await writeFile(filePath, output);
195             } catch (ex) {
196                 log.error("There was a problem writing the output file:\n%s", ex);
197                 return false;
198             }
199         } else {
200             log.info(output);
201         }
202     }
203
204     return true;
205 }
206
207 //------------------------------------------------------------------------------
208 // Public Interface
209 //------------------------------------------------------------------------------
210
211 /**
212  * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
213  * for other Node.js programs to effectively run the CLI.
214  */
215 const cli = {
216
217     /**
218      * Executes the CLI based on an array of arguments that is passed in.
219      * @param {string|Array|Object} args The arguments to process.
220      * @param {string} [text] The text to lint (used for TTY).
221      * @returns {Promise<number>} The exit code for the operation.
222      */
223     async execute(args, text) {
224         if (Array.isArray(args)) {
225             debug("CLI args: %o", args.slice(2));
226         }
227
228         /** @type {ParsedCLIOptions} */
229         let options;
230
231         try {
232             options = CLIOptions.parse(args);
233         } catch (error) {
234             log.error(error.message);
235             return 2;
236         }
237
238         const files = options._;
239         const useStdin = typeof text === "string";
240
241         if (options.help) {
242             log.info(CLIOptions.generateHelp());
243             return 0;
244         }
245         if (options.version) {
246             log.info(RuntimeInfo.version());
247             return 0;
248         }
249         if (options.envInfo) {
250             try {
251                 log.info(RuntimeInfo.environment());
252                 return 0;
253             } catch (err) {
254                 log.error(err.message);
255                 return 2;
256             }
257         }
258
259         if (options.printConfig) {
260             if (files.length) {
261                 log.error("The --print-config option must be used with exactly one file name.");
262                 return 2;
263             }
264             if (useStdin) {
265                 log.error("The --print-config option is not available for piped-in code.");
266                 return 2;
267             }
268
269             const engine = new ESLint(translateOptions(options));
270             const fileConfig =
271                 await engine.calculateConfigForFile(options.printConfig);
272
273             log.info(JSON.stringify(fileConfig, null, "  "));
274             return 0;
275         }
276
277         debug(`Running on ${useStdin ? "text" : "files"}`);
278
279         if (options.fix && options.fixDryRun) {
280             log.error("The --fix option and the --fix-dry-run option cannot be used together.");
281             return 2;
282         }
283         if (useStdin && options.fix) {
284             log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
285             return 2;
286         }
287         if (options.fixType && !options.fix && !options.fixDryRun) {
288             log.error("The --fix-type option requires either --fix or --fix-dry-run.");
289             return 2;
290         }
291
292         const engine = new ESLint(translateOptions(options));
293         let results;
294
295         if (useStdin) {
296             results = await engine.lintText(text, {
297                 filePath: options.stdinFilename,
298                 warnIgnored: true
299             });
300         } else {
301             results = await engine.lintFiles(files);
302         }
303
304         if (options.fix) {
305             debug("Fix mode enabled - applying fixes");
306             await ESLint.outputFixes(results);
307         }
308
309         let resultsToPrint = results;
310
311         if (options.quiet) {
312             debug("Quiet mode enabled - filtering out warnings");
313             resultsToPrint = ESLint.getErrorResults(resultsToPrint);
314         }
315
316         if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
317
318             // Errors and warnings from the original unfiltered results should determine the exit code
319             const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
320
321             const tooManyWarnings =
322                 options.maxWarnings >= 0 && warningCount > options.maxWarnings;
323             const shouldExitForFatalErrors =
324                 options.exitOnFatalError && fatalErrorCount > 0;
325
326             if (!errorCount && tooManyWarnings) {
327                 log.error(
328                     "ESLint found too many warnings (maximum: %s).",
329                     options.maxWarnings
330                 );
331             }
332
333             if (shouldExitForFatalErrors) {
334                 return 2;
335             }
336
337             return (errorCount || tooManyWarnings) ? 1 : 0;
338         }
339
340         return 2;
341     }
342 };
343
344 module.exports = cli;