]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | #!/usr/bin/env node |
2 | ||
3 | /** | |
4 | * @fileoverview Main CLI that is run via the eslint command. | |
5 | * @author Nicholas C. Zakas | |
6 | */ | |
7 | ||
609c276f | 8 | /* eslint no-console:off -- CLI */ |
eb39fafa DC |
9 | |
10 | "use strict"; | |
11 | ||
eb39fafa | 12 | // must do this initialization *before* other requires in order to work |
56c4a2cb | 13 | if (process.argv.includes("--debug")) { |
456be15e | 14 | require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*"); |
eb39fafa DC |
15 | } |
16 | ||
17 | //------------------------------------------------------------------------------ | |
56c4a2cb | 18 | // Helpers |
eb39fafa DC |
19 | //------------------------------------------------------------------------------ |
20 | ||
56c4a2cb DC |
21 | /** |
22 | * Read data from stdin til the end. | |
23 | * | |
24 | * Note: See | |
25 | * - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin | |
26 | * - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io | |
27 | * - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html | |
28 | * - https://github.com/nodejs/node/issues/7439 (historical) | |
29 | * | |
30 | * On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems | |
31 | * to read 4096 bytes before blocking and never drains to read further data. | |
32 | * | |
33 | * The investigation on the Emacs thread indicates: | |
34 | * | |
35 | * > Emacs on MS-Windows uses pipes to communicate with subprocesses; a | |
36 | * > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than | |
37 | * > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for | |
38 | * > the subprocess to read its end of the pipe, at which time Emacs will | |
39 | * > write the rest of the stuff. | |
40 | * @returns {Promise<string>} The read text. | |
41 | */ | |
42 | function readStdin() { | |
43 | return new Promise((resolve, reject) => { | |
44 | let content = ""; | |
45 | let chunk = ""; | |
46 | ||
47 | process.stdin | |
48 | .setEncoding("utf8") | |
49 | .on("readable", () => { | |
50 | while ((chunk = process.stdin.read()) !== null) { | |
51 | content += chunk; | |
52 | } | |
53 | }) | |
54 | .on("end", () => resolve(content)) | |
55 | .on("error", reject); | |
56 | }); | |
57 | } | |
eb39fafa | 58 | |
56c4a2cb DC |
59 | /** |
60 | * Get the error message of a given value. | |
61 | * @param {any} error The value to get. | |
62 | * @returns {string} The error message. | |
63 | */ | |
64 | function getErrorMessage(error) { | |
eb39fafa | 65 | |
5422a9cc | 66 | // Lazy loading because this is used only if an error happened. |
56c4a2cb | 67 | const util = require("util"); |
eb39fafa | 68 | |
8f9d1d4d | 69 | // Foolproof -- third-party module might throw non-object. |
56c4a2cb DC |
70 | if (typeof error !== "object" || error === null) { |
71 | return String(error); | |
72 | } | |
73 | ||
74 | // Use templates if `error.messageTemplate` is present. | |
75 | if (typeof error.messageTemplate === "string") { | |
76 | try { | |
5422a9cc | 77 | const template = require(`../messages/${error.messageTemplate}.js`); |
56c4a2cb DC |
78 | |
79 | return template(error.messageData || {}); | |
80 | } catch { | |
81 | ||
82 | // Ignore template error then fallback to use `error.stack`. | |
83 | } | |
84 | } | |
eb39fafa | 85 | |
56c4a2cb DC |
86 | // Use the stacktrace if it's an error object. |
87 | if (typeof error.stack === "string") { | |
88 | return error.stack; | |
eb39fafa DC |
89 | } |
90 | ||
56c4a2cb DC |
91 | // Otherwise, dump the object. |
92 | return util.format("%o", error); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Catch and report unexpected error. | |
97 | * @param {any} error The thrown error object. | |
98 | * @returns {void} | |
99 | */ | |
100 | function onFatalError(error) { | |
eb39fafa | 101 | process.exitCode = 2; |
eb39fafa | 102 | |
56c4a2cb DC |
103 | const { version } = require("../package.json"); |
104 | const message = getErrorMessage(error); | |
105 | ||
106 | console.error(` | |
107 | Oops! Something went wrong! :( | |
108 | ||
109 | ESLint: ${version} | |
110 | ||
111 | ${message}`); | |
eb39fafa | 112 | } |
56c4a2cb DC |
113 | |
114 | //------------------------------------------------------------------------------ | |
115 | // Execution | |
116 | //------------------------------------------------------------------------------ | |
117 | ||
118 | (async function main() { | |
119 | process.on("uncaughtException", onFatalError); | |
120 | process.on("unhandledRejection", onFatalError); | |
121 | ||
122 | // Call the config initializer if `--init` is present. | |
123 | if (process.argv.includes("--init")) { | |
8f9d1d4d DC |
124 | |
125 | // `eslint --init` has been moved to `@eslint/create-config` | |
126 | console.warn("You can also run this command directly using 'npm init @eslint/config'."); | |
127 | ||
128 | const spawn = require("cross-spawn"); | |
129 | ||
130 | spawn.sync("npm", ["init", "@eslint/config"], { encoding: "utf8", stdio: "inherit" }); | |
56c4a2cb DC |
131 | return; |
132 | } | |
133 | ||
134 | // Otherwise, call the CLI. | |
135 | process.exitCode = await require("../lib/cli").execute( | |
136 | process.argv, | |
8f9d1d4d DC |
137 | process.argv.includes("--stdin") ? await readStdin() : null, |
138 | true | |
56c4a2cb DC |
139 | ); |
140 | }()).catch(onFatalError); |