process-exit-as-throw.js 4.4 KB

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