process-exit-as-throw.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * @author Toru Nagashima
  3. * See LICENSE file in root directory for full license.
  4. */
  5. "use strict"
  6. const CodePathAnalyzer = safeRequire(
  7. "eslint/lib/linter/code-path-analysis/code-path-analyzer",
  8. "eslint/lib/code-path-analysis/code-path-analyzer"
  9. )
  10. const CodePathSegment = safeRequire(
  11. "eslint/lib/linter/code-path-analysis/code-path-segment",
  12. "eslint/lib/code-path-analysis/code-path-segment"
  13. )
  14. const CodePath = safeRequire(
  15. "eslint/lib/linter/code-path-analysis/code-path",
  16. "eslint/lib/code-path-analysis/code-path"
  17. )
  18. const originalLeaveNode =
  19. CodePathAnalyzer && CodePathAnalyzer.prototype.leaveNode
  20. /**
  21. * Imports a specific module.
  22. * @param {...string} moduleNames - module names to import.
  23. * @returns {object|null} The imported object, or null.
  24. */
  25. function safeRequire(...moduleNames) {
  26. for (const moduleName of moduleNames) {
  27. try {
  28. return require(moduleName)
  29. } catch (_err) {
  30. // Ignore.
  31. }
  32. }
  33. return null
  34. }
  35. /* istanbul ignore next */
  36. /**
  37. * Copied from https://github.com/eslint/eslint/blob/16fad5880bb70e9dddbeab8ed0f425ae51f5841f/lib/code-path-analysis/code-path-analyzer.js#L137
  38. *
  39. * @param {CodePathAnalyzer} analyzer - The instance.
  40. * @param {ASTNode} node - The current AST node.
  41. * @returns {void}
  42. */
  43. function forwardCurrentToHead(analyzer, node) {
  44. const codePath = analyzer.codePath
  45. const state = CodePath.getState(codePath)
  46. const currentSegments = state.currentSegments
  47. const headSegments = state.headSegments
  48. const end = Math.max(currentSegments.length, headSegments.length)
  49. let i = 0
  50. let currentSegment = null
  51. let headSegment = null
  52. // Fires leaving events.
  53. for (i = 0; i < end; ++i) {
  54. currentSegment = currentSegments[i]
  55. headSegment = headSegments[i]
  56. if (currentSegment !== headSegment && currentSegment) {
  57. if (currentSegment.reachable) {
  58. analyzer.emitter.emit(
  59. "onCodePathSegmentEnd",
  60. currentSegment,
  61. node
  62. )
  63. }
  64. }
  65. }
  66. // Update state.
  67. state.currentSegments = headSegments
  68. // Fires entering events.
  69. for (i = 0; i < end; ++i) {
  70. currentSegment = currentSegments[i]
  71. headSegment = headSegments[i]
  72. if (currentSegment !== headSegment && headSegment) {
  73. CodePathSegment.markUsed(headSegment)
  74. if (headSegment.reachable) {
  75. analyzer.emitter.emit(
  76. "onCodePathSegmentStart",
  77. headSegment,
  78. node
  79. )
  80. }
  81. }
  82. }
  83. }
  84. /**
  85. * Checks whether a given node is `process.exit()` or not.
  86. *
  87. * @param {ASTNode} node - A node to check.
  88. * @returns {boolean} `true` if the node is `process.exit()`.
  89. */
  90. function isProcessExit(node) {
  91. return (
  92. node.type === "CallExpression" &&
  93. node.callee.type === "MemberExpression" &&
  94. node.callee.computed === false &&
  95. node.callee.object.type === "Identifier" &&
  96. node.callee.object.name === "process" &&
  97. node.callee.property.type === "Identifier" &&
  98. node.callee.property.name === "exit"
  99. )
  100. }
  101. /**
  102. * The function to override `CodePathAnalyzer.prototype.leaveNode` in order to
  103. * address `process.exit()` as throw.
  104. *
  105. * @this CodePathAnalyzer
  106. * @param {ASTNode} node - A node to be left.
  107. * @returns {void}
  108. */
  109. function overrideLeaveNode(node) {
  110. if (isProcessExit(node)) {
  111. this.currentNode = node
  112. forwardCurrentToHead(this, node)
  113. CodePath.getState(this.codePath).makeThrow()
  114. this.original.leaveNode(node)
  115. this.currentNode = null
  116. } else {
  117. originalLeaveNode.call(this, node)
  118. }
  119. }
  120. const visitor =
  121. CodePathAnalyzer == null
  122. ? {}
  123. : {
  124. Program: function installProcessExitAsThrow() {
  125. CodePathAnalyzer.prototype.leaveNode = overrideLeaveNode
  126. },
  127. "Program:exit": function restoreProcessExitAsThrow() {
  128. CodePathAnalyzer.prototype.leaveNode = originalLeaveNode
  129. },
  130. }
  131. module.exports = {
  132. meta: {
  133. docs: {
  134. description:
  135. "make `process.exit()` expressions the same code path as `throw`",
  136. category: "Possible Errors",
  137. recommended: true,
  138. url:
  139. "https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/process-exit-as-throw.md",
  140. },
  141. type: "problem",
  142. fixable: null,
  143. schema: [],
  144. supported: CodePathAnalyzer != null,
  145. },
  146. create() {
  147. return visitor
  148. },
  149. }