UNPKG

6.46 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 docs: {
105 description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
106 category: "Possible Errors",
107 recommended: true,
108 url: "https://eslint.org/docs/rules/no-unreachable"
109 },
110
111 schema: []
112 },
113
114 create(context) {
115 let currentCodePath = null;
116
117 const range = new ConsecutiveRange(context.getSourceCode());
118
119 /**
120 * Reports a given node if it's unreachable.
121 * @param {ASTNode} node - A statement node to report.
122 * @returns {void}
123 */
124 function reportIfUnreachable(node) {
125 let nextNode = null;
126
127 if (node && currentCodePath.currentSegments.every(isUnreachable)) {
128
129 // Store this statement to distinguish consecutive statements.
130 if (range.isEmpty) {
131 range.reset(node);
132 return;
133 }
134
135 // Skip if this statement is inside of the current range.
136 if (range.contains(node)) {
137 return;
138 }
139
140 // Merge if this statement is consecutive to the current range.
141 if (range.isConsecutive(node)) {
142 range.merge(node);
143 return;
144 }
145
146 nextNode = node;
147 }
148
149 /*
150 * Report the current range since this statement is reachable or is
151 * not consecutive to the current range.
152 */
153 if (!range.isEmpty) {
154 context.report({
155 message: "Unreachable code.",
156 loc: range.location,
157 node: range.startNode
158 });
159 }
160
161 // Update the current range.
162 range.reset(nextNode);
163 }
164
165 return {
166
167 // Manages the current code path.
168 onCodePathStart(codePath) {
169 currentCodePath = codePath;
170 },
171
172 onCodePathEnd() {
173 currentCodePath = currentCodePath.upper;
174 },
175
176 // Registers for all statement nodes (excludes FunctionDeclaration).
177 BlockStatement: reportIfUnreachable,
178 BreakStatement: reportIfUnreachable,
179 ClassDeclaration: reportIfUnreachable,
180 ContinueStatement: reportIfUnreachable,
181 DebuggerStatement: reportIfUnreachable,
182 DoWhileStatement: reportIfUnreachable,
183 EmptyStatement: reportIfUnreachable,
184 ExpressionStatement: reportIfUnreachable,
185 ForInStatement: reportIfUnreachable,
186 ForOfStatement: reportIfUnreachable,
187 ForStatement: reportIfUnreachable,
188 IfStatement: reportIfUnreachable,
189 ImportDeclaration: reportIfUnreachable,
190 LabeledStatement: reportIfUnreachable,
191 ReturnStatement: reportIfUnreachable,
192 SwitchStatement: reportIfUnreachable,
193 ThrowStatement: reportIfUnreachable,
194 TryStatement: reportIfUnreachable,
195
196 VariableDeclaration(node) {
197 if (node.kind !== "var" || node.declarations.some(isInitialized)) {
198 reportIfUnreachable(node);
199 }
200 },
201
202 WhileStatement: reportIfUnreachable,
203 WithStatement: reportIfUnreachable,
204 ExportNamedDeclaration: reportIfUnreachable,
205 ExportDefaultDeclaration: reportIfUnreachable,
206 ExportAllDeclaration: reportIfUnreachable,
207
208 "Program:exit"() {
209 reportIfUnreachable();
210 }
211 };
212 }
213};