|
|
/** * @fileoverview Checks for unreachable code due to return, throws, break, and continue. * @author Joel Feenstra */ "use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/** * 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
//------------------------------------------------------------------------------
module.exports = { meta: { type: "problem",
docs: { description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-unreachable" },
schema: [],
messages: { unreachableCode: "Unreachable code." } },
create(context) { let currentCodePath = null;
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 && 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(); } }; } };
|