UNPKG

6.51 kBJavaScriptView Raw
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};