eslint.js 4.2 KB

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