eslint.js 5.5 KB

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