]>
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 | ||
8 | /* eslint no-console:off */ | |
9 | ||
10 | "use strict"; | |
11 | ||
12 | // to use V8's code cache to speed up instantiation time | |
13 | require("v8-compile-cache"); | |
14 | ||
eb39fafa | 15 | // must do this initialization *before* other requires in order to work |
56c4a2cb | 16 | if (process.argv.includes("--debug")) { |
eb39fafa DC |
17 | require("debug").enable("eslint:*,-eslint:code-path"); |
18 | } | |
19 | ||
20 | //------------------------------------------------------------------------------ | |
56c4a2cb | 21 | // Helpers |
eb39fafa DC |
22 | //------------------------------------------------------------------------------ |
23 | ||
56c4a2cb DC |
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 | } | |
eb39fafa | 61 | |
56c4a2cb DC |
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) { | |
eb39fafa | 68 | |
56c4a2cb DC |
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"); | |
eb39fafa DC |
73 | const lodash = require("lodash"); |
74 | ||
56c4a2cb DC |
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 | } | |
eb39fafa | 98 | |
56c4a2cb DC |
99 | // Use the stacktrace if it's an error object. |
100 | if (typeof error.stack === "string") { | |
101 | return error.stack; | |
eb39fafa DC |
102 | } |
103 | ||
56c4a2cb DC |
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) { | |
eb39fafa | 114 | process.exitCode = 2; |
eb39fafa | 115 | |
56c4a2cb DC |
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}`); | |
eb39fafa | 125 | } |
56c4a2cb DC |
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); |