| /** |
| * @fileoverview Checks for unreachable code due to return, throws, break, and continue. |
| * @author Joel Feenstra |
| */ |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| /** |
| * @typedef {Object} ConstructorInfo |
| * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor. |
| * @property {boolean} hasSuperCall The flag about having `super()` expressions. |
| */ |
| |
| /** |
| * Checks whether or not a given variable declarator has the initializer. |
| * @param {ASTNode} node A VariableDeclarator node to check. |
| * @returns {boolean} `true` if the node has the initializer. |
| */ |
| function isInitialized(node) { |
| return Boolean(node.init); |
| } |
| |
| /** |
| * Checks whether or not a given code path segment is unreachable. |
| * @param {CodePathSegment} segment A CodePathSegment to check. |
| * @returns {boolean} `true` if the segment is unreachable. |
| */ |
| function isUnreachable(segment) { |
| return !segment.reachable; |
| } |
| |
| /** |
| * The class to distinguish consecutive unreachable statements. |
| */ |
| class ConsecutiveRange { |
| constructor(sourceCode) { |
| this.sourceCode = sourceCode; |
| this.startNode = null; |
| this.endNode = null; |
| } |
| |
| /** |
| * The location object of this range. |
| * @type {Object} |
| */ |
| get location() { |
| return { |
| start: this.startNode.loc.start, |
| end: this.endNode.loc.end |
| }; |
| } |
| |
| /** |
| * `true` if this range is empty. |
| * @type {boolean} |
| */ |
| get isEmpty() { |
| return !(this.startNode && this.endNode); |
| } |
| |
| /** |
| * Checks whether the given node is inside of this range. |
| * @param {ASTNode|Token} node The node to check. |
| * @returns {boolean} `true` if the node is inside of this range. |
| */ |
| contains(node) { |
| return ( |
| node.range[0] >= this.startNode.range[0] && |
| node.range[1] <= this.endNode.range[1] |
| ); |
| } |
| |
| /** |
| * Checks whether the given node is consecutive to this range. |
| * @param {ASTNode} node The node to check. |
| * @returns {boolean} `true` if the node is consecutive to this range. |
| */ |
| isConsecutive(node) { |
| return this.contains(this.sourceCode.getTokenBefore(node)); |
| } |
| |
| /** |
| * Merges the given node to this range. |
| * @param {ASTNode} node The node to merge. |
| * @returns {void} |
| */ |
| merge(node) { |
| this.endNode = node; |
| } |
| |
| /** |
| * Resets this range by the given node or null. |
| * @param {ASTNode|null} node The node to reset, or null. |
| * @returns {void} |
| */ |
| reset(node) { |
| this.startNode = this.endNode = node; |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../shared/types').Rule} */ |
| module.exports = { |
| meta: { |
| type: "problem", |
| |
| docs: { |
| description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", |
| recommended: true, |
| url: "https://eslint.org/docs/rules/no-unreachable" |
| }, |
| |
| schema: [], |
| |
| messages: { |
| unreachableCode: "Unreachable code." |
| } |
| }, |
| |
| create(context) { |
| let currentCodePath = null; |
| |
| /** @type {ConstructorInfo | null} */ |
| let constructorInfo = null; |
| |
| /** @type {ConsecutiveRange} */ |
| const range = new ConsecutiveRange(context.getSourceCode()); |
| |
| /** |
| * Reports a given node if it's unreachable. |
| * @param {ASTNode} node A statement node to report. |
| * @returns {void} |
| */ |
| function reportIfUnreachable(node) { |
| let nextNode = null; |
| |
| if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) { |
| |
| // Store this statement to distinguish consecutive statements. |
| if (range.isEmpty) { |
| range.reset(node); |
| return; |
| } |
| |
| // Skip if this statement is inside of the current range. |
| if (range.contains(node)) { |
| return; |
| } |
| |
| // Merge if this statement is consecutive to the current range. |
| if (range.isConsecutive(node)) { |
| range.merge(node); |
| return; |
| } |
| |
| nextNode = node; |
| } |
| |
| /* |
| * Report the current range since this statement is reachable or is |
| * not consecutive to the current range. |
| */ |
| if (!range.isEmpty) { |
| context.report({ |
| messageId: "unreachableCode", |
| loc: range.location, |
| node: range.startNode |
| }); |
| } |
| |
| // Update the current range. |
| range.reset(nextNode); |
| } |
| |
| return { |
| |
| // Manages the current code path. |
| onCodePathStart(codePath) { |
| currentCodePath = codePath; |
| }, |
| |
| onCodePathEnd() { |
| currentCodePath = currentCodePath.upper; |
| }, |
| |
| // Registers for all statement nodes (excludes FunctionDeclaration). |
| BlockStatement: reportIfUnreachable, |
| BreakStatement: reportIfUnreachable, |
| ClassDeclaration: reportIfUnreachable, |
| ContinueStatement: reportIfUnreachable, |
| DebuggerStatement: reportIfUnreachable, |
| DoWhileStatement: reportIfUnreachable, |
| ExpressionStatement: reportIfUnreachable, |
| ForInStatement: reportIfUnreachable, |
| ForOfStatement: reportIfUnreachable, |
| ForStatement: reportIfUnreachable, |
| IfStatement: reportIfUnreachable, |
| ImportDeclaration: reportIfUnreachable, |
| LabeledStatement: reportIfUnreachable, |
| ReturnStatement: reportIfUnreachable, |
| SwitchStatement: reportIfUnreachable, |
| ThrowStatement: reportIfUnreachable, |
| TryStatement: reportIfUnreachable, |
| |
| VariableDeclaration(node) { |
| if (node.kind !== "var" || node.declarations.some(isInitialized)) { |
| reportIfUnreachable(node); |
| } |
| }, |
| |
| WhileStatement: reportIfUnreachable, |
| WithStatement: reportIfUnreachable, |
| ExportNamedDeclaration: reportIfUnreachable, |
| ExportDefaultDeclaration: reportIfUnreachable, |
| ExportAllDeclaration: reportIfUnreachable, |
| |
| "Program:exit"() { |
| reportIfUnreachable(); |
| }, |
| |
| /* |
| * Instance fields defined in a subclass are never created if the constructor of the subclass |
| * doesn't call `super()`, so their definitions are unreachable code. |
| */ |
| "MethodDefinition[kind='constructor']"() { |
| constructorInfo = { |
| upper: constructorInfo, |
| hasSuperCall: false |
| }; |
| }, |
| "MethodDefinition[kind='constructor']:exit"(node) { |
| const { hasSuperCall } = constructorInfo; |
| |
| constructorInfo = constructorInfo.upper; |
| |
| // skip typescript constructors without the body |
| if (!node.value.body) { |
| return; |
| } |
| |
| const classDefinition = node.parent.parent; |
| |
| if (classDefinition.superClass && !hasSuperCall) { |
| for (const element of classDefinition.body.body) { |
| if (element.type === "PropertyDefinition" && !element.static) { |
| reportIfUnreachable(element); |
| } |
| } |
| } |
| }, |
| "CallExpression > Super.callee"() { |
| if (constructorInfo) { |
| constructorInfo.hasSuperCall = true; |
| } |
| } |
| }; |
| } |
| }; |