no-unreachable.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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. * @typedef {Object} ConstructorInfo
  11. * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor.
  12. * @property {boolean} hasSuperCall The flag about having `super()` expressions.
  13. */
  14. /**
  15. * Checks whether or not a given variable declarator has the initializer.
  16. * @param {ASTNode} node A VariableDeclarator node to check.
  17. * @returns {boolean} `true` if the node has the initializer.
  18. */
  19. function isInitialized(node) {
  20. return Boolean(node.init);
  21. }
  22. /**
  23. * Checks whether or not a given code path segment is unreachable.
  24. * @param {CodePathSegment} segment A CodePathSegment to check.
  25. * @returns {boolean} `true` if the segment is unreachable.
  26. */
  27. function isUnreachable(segment) {
  28. return !segment.reachable;
  29. }
  30. /**
  31. * The class to distinguish consecutive unreachable statements.
  32. */
  33. class ConsecutiveRange {
  34. constructor(sourceCode) {
  35. this.sourceCode = sourceCode;
  36. this.startNode = null;
  37. this.endNode = null;
  38. }
  39. /**
  40. * The location object of this range.
  41. * @type {Object}
  42. */
  43. get location() {
  44. return {
  45. start: this.startNode.loc.start,
  46. end: this.endNode.loc.end
  47. };
  48. }
  49. /**
  50. * `true` if this range is empty.
  51. * @type {boolean}
  52. */
  53. get isEmpty() {
  54. return !(this.startNode && this.endNode);
  55. }
  56. /**
  57. * Checks whether the given node is inside of this range.
  58. * @param {ASTNode|Token} node The node to check.
  59. * @returns {boolean} `true` if the node is inside of this range.
  60. */
  61. contains(node) {
  62. return (
  63. node.range[0] >= this.startNode.range[0] &&
  64. node.range[1] <= this.endNode.range[1]
  65. );
  66. }
  67. /**
  68. * Checks whether the given node is consecutive to this range.
  69. * @param {ASTNode} node The node to check.
  70. * @returns {boolean} `true` if the node is consecutive to this range.
  71. */
  72. isConsecutive(node) {
  73. return this.contains(this.sourceCode.getTokenBefore(node));
  74. }
  75. /**
  76. * Merges the given node to this range.
  77. * @param {ASTNode} node The node to merge.
  78. * @returns {void}
  79. */
  80. merge(node) {
  81. this.endNode = node;
  82. }
  83. /**
  84. * Resets this range by the given node or null.
  85. * @param {ASTNode|null} node The node to reset, or null.
  86. * @returns {void}
  87. */
  88. reset(node) {
  89. this.startNode = this.endNode = node;
  90. }
  91. }
  92. //------------------------------------------------------------------------------
  93. // Rule Definition
  94. //------------------------------------------------------------------------------
  95. /** @type {import('../shared/types').Rule} */
  96. module.exports = {
  97. meta: {
  98. type: "problem",
  99. docs: {
  100. description: "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
  101. recommended: true,
  102. url: "https://eslint.org/docs/rules/no-unreachable"
  103. },
  104. schema: [],
  105. messages: {
  106. unreachableCode: "Unreachable code."
  107. }
  108. },
  109. create(context) {
  110. let currentCodePath = null;
  111. /** @type {ConstructorInfo | null} */
  112. let constructorInfo = null;
  113. /** @type {ConsecutiveRange} */
  114. const range = new ConsecutiveRange(context.getSourceCode());
  115. /**
  116. * Reports a given node if it's unreachable.
  117. * @param {ASTNode} node A statement node to report.
  118. * @returns {void}
  119. */
  120. function reportIfUnreachable(node) {
  121. let nextNode = null;
  122. if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
  123. // Store this statement to distinguish consecutive statements.
  124. if (range.isEmpty) {
  125. range.reset(node);
  126. return;
  127. }
  128. // Skip if this statement is inside of the current range.
  129. if (range.contains(node)) {
  130. return;
  131. }
  132. // Merge if this statement is consecutive to the current range.
  133. if (range.isConsecutive(node)) {
  134. range.merge(node);
  135. return;
  136. }
  137. nextNode = node;
  138. }
  139. /*
  140. * Report the current range since this statement is reachable or is
  141. * not consecutive to the current range.
  142. */
  143. if (!range.isEmpty) {
  144. context.report({
  145. messageId: "unreachableCode",
  146. loc: range.location,
  147. node: range.startNode
  148. });
  149. }
  150. // Update the current range.
  151. range.reset(nextNode);
  152. }
  153. return {
  154. // Manages the current code path.
  155. onCodePathStart(codePath) {
  156. currentCodePath = codePath;
  157. },
  158. onCodePathEnd() {
  159. currentCodePath = currentCodePath.upper;
  160. },
  161. // Registers for all statement nodes (excludes FunctionDeclaration).
  162. BlockStatement: reportIfUnreachable,
  163. BreakStatement: reportIfUnreachable,
  164. ClassDeclaration: reportIfUnreachable,
  165. ContinueStatement: reportIfUnreachable,
  166. DebuggerStatement: reportIfUnreachable,
  167. DoWhileStatement: reportIfUnreachable,
  168. ExpressionStatement: reportIfUnreachable,
  169. ForInStatement: reportIfUnreachable,
  170. ForOfStatement: reportIfUnreachable,
  171. ForStatement: reportIfUnreachable,
  172. IfStatement: reportIfUnreachable,
  173. ImportDeclaration: reportIfUnreachable,
  174. LabeledStatement: reportIfUnreachable,
  175. ReturnStatement: reportIfUnreachable,
  176. SwitchStatement: reportIfUnreachable,
  177. ThrowStatement: reportIfUnreachable,
  178. TryStatement: reportIfUnreachable,
  179. VariableDeclaration(node) {
  180. if (node.kind !== "var" || node.declarations.some(isInitialized)) {
  181. reportIfUnreachable(node);
  182. }
  183. },
  184. WhileStatement: reportIfUnreachable,
  185. WithStatement: reportIfUnreachable,
  186. ExportNamedDeclaration: reportIfUnreachable,
  187. ExportDefaultDeclaration: reportIfUnreachable,
  188. ExportAllDeclaration: reportIfUnreachable,
  189. "Program:exit"() {
  190. reportIfUnreachable();
  191. },
  192. /*
  193. * Instance fields defined in a subclass are never created if the constructor of the subclass
  194. * doesn't call `super()`, so their definitions are unreachable code.
  195. */
  196. "MethodDefinition[kind='constructor']"() {
  197. constructorInfo = {
  198. upper: constructorInfo,
  199. hasSuperCall: false
  200. };
  201. },
  202. "MethodDefinition[kind='constructor']:exit"(node) {
  203. const { hasSuperCall } = constructorInfo;
  204. constructorInfo = constructorInfo.upper;
  205. // skip typescript constructors without the body
  206. if (!node.value.body) {
  207. return;
  208. }
  209. const classDefinition = node.parent.parent;
  210. if (classDefinition.superClass && !hasSuperCall) {
  211. for (const element of classDefinition.body.body) {
  212. if (element.type === "PropertyDefinition" && !element.static) {
  213. reportIfUnreachable(element);
  214. }
  215. }
  216. }
  217. },
  218. "CallExpression > Super.callee"() {
  219. if (constructorInfo) {
  220. constructorInfo.hasSuperCall = true;
  221. }
  222. }
  223. };
  224. }
  225. };