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 */
|
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 those are used only if error happened.
|
70 | const fs = require("fs");
|
71 | const path = require("path");
|
72 | const util = require("util");
|
73 | const lodash = require("lodash");
|
74 |
|
75 | // Foolproof -- thirdparty module might throw non-object.
|
76 | if (typeof error !== "object" || error === null) {
|
77 | return String(error);
|
78 | }
|
79 |
|
80 | // Use templates if `error.messageTemplate` is present.
|
81 | if (typeof error.messageTemplate === "string") {
|
82 | try {
|
83 | const templateFilePath = path.resolve(
|
84 | __dirname,
|
85 | `../messages/${error.messageTemplate}.txt`
|
86 | );
|
87 |
|
88 | // Use sync API because Node.js should exit at this tick.
|
89 | const templateText = fs.readFileSync(templateFilePath, "utf-8");
|
90 | const template = lodash.template(templateText);
|
91 |
|
92 | return template(error.messageData || {});
|
93 | } catch {
|
94 |
|
95 | // Ignore template error then fallback to use `error.stack`.
|
96 | }
|
97 | }
|
98 |
|
99 | // Use the stacktrace if it's an error object.
|
100 | if (typeof error.stack === "string") {
|
101 | return error.stack;
|
102 | }
|
103 |
|
104 | // Otherwise, dump the object.
|
105 | return util.format("%o", error);
|
106 | }
|
107 |
|
108 | /**
|
109 | * Catch and report unexpected error.
|
110 | * @param {any} error The thrown error object.
|
111 | * @returns {void}
|
112 | */
|
113 | function onFatalError(error) {
|
114 | process.exitCode = 2;
|
115 |
|
116 | const { version } = require("../package.json");
|
117 | const message = getErrorMessage(error);
|
118 |
|
119 | console.error(`
|
120 | Oops! Something went wrong! :(
|
121 |
|
122 | ESLint: ${version}
|
123 |
|
124 | ${message}`);
|
125 | }
|
126 |
|
127 | //------------------------------------------------------------------------------
|
128 | // Execution
|
129 | //------------------------------------------------------------------------------
|
130 |
|
131 | (async function main() {
|
132 | process.on("uncaughtException", onFatalError);
|
133 | process.on("unhandledRejection", onFatalError);
|
134 |
|
135 | // Call the config initializer if `--init` is present.
|
136 | if (process.argv.includes("--init")) {
|
137 | await require("../lib/init/config-initializer").initializeConfig();
|
138 | return;
|
139 | }
|
140 |
|
141 | // Otherwise, call the CLI.
|
142 | process.exitCode = await require("../lib/cli").execute(
|
143 | process.argv,
|
144 | process.argv.includes("--stdin") ? await readStdin() : null
|
145 | );
|
146 | }()).catch(onFatalError);
|