process-exit-as-throw.js 4.8 KB

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