no-unreachable.js 7.3 KB

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