no-unreachable.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /**
  2. * @fileoverview Checks for unreachable code due to return, throws, break, and continue.
  3. * @author Joel Feenstra
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Helpers
  8. //------------------------------------------------------------------------------
  9. /**
  10. * Checks whether or not a given variable declarator has the initializer.
  11. * @param {ASTNode} node A VariableDeclarator node to check.
  12. * @returns {boolean} `true` if the node has the initializer.
  13. */
  14. function isInitialized(node) {
  15. return Boolean(node.init);
  16. }
  17. /**
  18. * Checks whether or not a given code path segment is unreachable.
  19. * @param {CodePathSegment} segment A CodePathSegment to check.
  20. * @returns {boolean} `true` if the segment is unreachable.
  21. */
  22. function isUnreachable(segment) {
  23. return !segment.reachable;
  24. }
  25. /**
  26. * The class to distinguish consecutive unreachable statements.
  27. */
  28. class ConsecutiveRange {
  29. constructor(sourceCode) {
  30. this.sourceCode = sourceCode;
  31. this.startNode = null;
  32. this.endNode = null;
  33. }
  34. /**
  35. * The location object of this range.
  36. * @type {Object}
  37. */
  38. get location() {
  39. return {
  40. start: this.startNode.loc.start,
  41. end: this.endNode.loc.end
  42. };
  43. }
  44. /**
  45. * `true` if this range is empty.
  46. * @type {boolean}
  47. */
  48. get isEmpty() {
  49. return !(this.startNode && this.endNode);
  50. }
  51. /**
  52. * Checks whether the given node is inside of this range.
  53. * @param {ASTNode|Token} node The node to check.
  54. * @returns {boolean} `true` if the node is inside of this range.
  55. */
  56. contains(node) {
  57. return (
  58. node.range[0] >= this.startNode.range[0] &&
  59. node.range[1] <= this.endNode.range[1]
  60. );
  61. }
  62. /**
  63. * Checks whether the given node is consecutive to this range.
  64. * @param {ASTNode} node The node to check.
  65. * @returns {boolean} `true` if the node is consecutive to this range.
  66. */
  67. isConsecutive(node) {
  68. return this.contains(this.sourceCode.getTokenBefore(node));
  69. }
  70. /**
  71. * Merges the given node to this range.
  72. * @param {ASTNode} node The node to merge.
  73. * @returns {void}
  74. */
  75. merge(node) {
  76. this.endNode = node;
  77. }
  78. /**
  79. * Resets this range by the given node or null.
  80. * @param {ASTNode|null} node The node to reset, or null.
  81. * @returns {void}
  82. */
  83. reset(node) {
  84. this.startNode = this.endNode = node;
  85. }
  86. }
  87. //------------------------------------------------------------------------------
  88. // Rule Definition
  89. //------------------------------------------------------------------------------
  90. module.exports = {
  91. meta: {
  92. type: "problem",
  93. docs: {
  94. description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
  95. category: "Possible Errors",
  96. recommended: true,
  97. url: "https://eslint.org/docs/rules/no-unreachable"
  98. },
  99. schema: [],
  100. messages: {
  101. unreachableCode: "Unreachable code."
  102. }
  103. },
  104. create(context) {
  105. let currentCodePath = null;
  106. const range = new ConsecutiveRange(context.getSourceCode());
  107. /**
  108. * Reports a given node if it's unreachable.
  109. * @param {ASTNode} node A statement node to report.
  110. * @returns {void}
  111. */
  112. function reportIfUnreachable(node) {
  113. let nextNode = null;
  114. if (node && currentCodePath.currentSegments.every(isUnreachable)) {
  115. // Store this statement to distinguish consecutive statements.
  116. if (range.isEmpty) {
  117. range.reset(node);
  118. return;
  119. }
  120. // Skip if this statement is inside of the current range.
  121. if (range.contains(node)) {
  122. return;
  123. }
  124. // Merge if this statement is consecutive to the current range.
  125. if (range.isConsecutive(node)) {
  126. range.merge(node);
  127. return;
  128. }
  129. nextNode = node;
  130. }
  131. /*
  132. * Report the current range since this statement is reachable or is
  133. * not consecutive to the current range.
  134. */
  135. if (!range.isEmpty) {
  136. context.report({
  137. messageId: "unreachableCode",
  138. loc: range.location,
  139. node: range.startNode
  140. });
  141. }
  142. // Update the current range.
  143. range.reset(nextNode);
  144. }
  145. return {
  146. // Manages the current code path.
  147. onCodePathStart(codePath) {
  148. currentCodePath = codePath;
  149. },
  150. onCodePathEnd() {
  151. currentCodePath = currentCodePath.upper;
  152. },
  153. // Registers for all statement nodes (excludes FunctionDeclaration).
  154. BlockStatement: reportIfUnreachable,
  155. BreakStatement: reportIfUnreachable,
  156. ClassDeclaration: reportIfUnreachable,
  157. ContinueStatement: reportIfUnreachable,
  158. DebuggerStatement: reportIfUnreachable,
  159. DoWhileStatement: reportIfUnreachable,
  160. ExpressionStatement: reportIfUnreachable,
  161. ForInStatement: reportIfUnreachable,
  162. ForOfStatement: reportIfUnreachable,
  163. ForStatement: reportIfUnreachable,
  164. IfStatement: reportIfUnreachable,
  165. ImportDeclaration: reportIfUnreachable,
  166. LabeledStatement: reportIfUnreachable,
  167. ReturnStatement: reportIfUnreachable,
  168. SwitchStatement: reportIfUnreachable,
  169. ThrowStatement: reportIfUnreachable,
  170. TryStatement: reportIfUnreachable,
  171. VariableDeclaration(node) {
  172. if (node.kind !== "var" || node.declarations.some(isInitialized)) {
  173. reportIfUnreachable(node);
  174. }
  175. },
  176. WhileStatement: reportIfUnreachable,
  177. WithStatement: reportIfUnreachable,
  178. ExportNamedDeclaration: reportIfUnreachable,
  179. ExportDefaultDeclaration: reportIfUnreachable,
  180. ExportAllDeclaration: reportIfUnreachable,
  181. "Program:exit"() {
  182. reportIfUnreachable();
  183. }
  184. };
  185. }
  186. };