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