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