]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-unreachable.js
change from CLIEngine to ESLint
[pve-eslint.git] / eslint / lib / rules / no-unreachable.js
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 * @typedef {Object} ConstructorInfo
13 * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor.
14 * @property {boolean} hasSuperCall The flag about having `super()` expressions.
15 */
16
17 /**
18 * Checks whether or not a given variable declarator has the initializer.
19 * @param {ASTNode} node A VariableDeclarator node to check.
20 * @returns {boolean} `true` if the node has the initializer.
21 */
22 function isInitialized(node) {
23 return Boolean(node.init);
24 }
25
26 /**
27 * Checks whether or not a given code path segment is unreachable.
28 * @param {CodePathSegment} segment A CodePathSegment to check.
29 * @returns {boolean} `true` if the segment is unreachable.
30 */
31 function isUnreachable(segment) {
32 return !segment.reachable;
33 }
34
35 /**
36 * The class to distinguish consecutive unreachable statements.
37 */
38 class ConsecutiveRange {
39 constructor(sourceCode) {
40 this.sourceCode = sourceCode;
41 this.startNode = null;
42 this.endNode = null;
43 }
44
45 /**
46 * The location object of this range.
47 * @type {Object}
48 */
49 get location() {
50 return {
51 start: this.startNode.loc.start,
52 end: this.endNode.loc.end
53 };
54 }
55
56 /**
57 * `true` if this range is empty.
58 * @type {boolean}
59 */
60 get isEmpty() {
61 return !(this.startNode && this.endNode);
62 }
63
64 /**
65 * Checks whether the given node is inside of this range.
66 * @param {ASTNode|Token} node The node to check.
67 * @returns {boolean} `true` if the node is inside of this range.
68 */
69 contains(node) {
70 return (
71 node.range[0] >= this.startNode.range[0] &&
72 node.range[1] <= this.endNode.range[1]
73 );
74 }
75
76 /**
77 * Checks whether the given node is consecutive to this range.
78 * @param {ASTNode} node The node to check.
79 * @returns {boolean} `true` if the node is consecutive to this range.
80 */
81 isConsecutive(node) {
82 return this.contains(this.sourceCode.getTokenBefore(node));
83 }
84
85 /**
86 * Merges the given node to this range.
87 * @param {ASTNode} node The node to merge.
88 * @returns {void}
89 */
90 merge(node) {
91 this.endNode = node;
92 }
93
94 /**
95 * Resets this range by the given node or null.
96 * @param {ASTNode|null} node The node to reset, or null.
97 * @returns {void}
98 */
99 reset(node) {
100 this.startNode = this.endNode = node;
101 }
102 }
103
104 //------------------------------------------------------------------------------
105 // Rule Definition
106 //------------------------------------------------------------------------------
107
108 module.exports = {
109 meta: {
110 type: "problem",
111
112 docs: {
113 description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
114 recommended: true,
115 url: "https://eslint.org/docs/rules/no-unreachable"
116 },
117
118 schema: [],
119
120 messages: {
121 unreachableCode: "Unreachable code."
122 }
123 },
124
125 create(context) {
126 let currentCodePath = null;
127
128 /** @type {ConstructorInfo | null} */
129 let constructorInfo = null;
130
131 /** @type {ConsecutiveRange} */
132 const range = new ConsecutiveRange(context.getSourceCode());
133
134 /**
135 * Reports a given node if it's unreachable.
136 * @param {ASTNode} node A statement node to report.
137 * @returns {void}
138 */
139 function reportIfUnreachable(node) {
140 let nextNode = null;
141
142 if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
143
144 // Store this statement to distinguish consecutive statements.
145 if (range.isEmpty) {
146 range.reset(node);
147 return;
148 }
149
150 // Skip if this statement is inside of the current range.
151 if (range.contains(node)) {
152 return;
153 }
154
155 // Merge if this statement is consecutive to the current range.
156 if (range.isConsecutive(node)) {
157 range.merge(node);
158 return;
159 }
160
161 nextNode = node;
162 }
163
164 /*
165 * Report the current range since this statement is reachable or is
166 * not consecutive to the current range.
167 */
168 if (!range.isEmpty) {
169 context.report({
170 messageId: "unreachableCode",
171 loc: range.location,
172 node: range.startNode
173 });
174 }
175
176 // Update the current range.
177 range.reset(nextNode);
178 }
179
180 return {
181
182 // Manages the current code path.
183 onCodePathStart(codePath) {
184 currentCodePath = codePath;
185 },
186
187 onCodePathEnd() {
188 currentCodePath = currentCodePath.upper;
189 },
190
191 // Registers for all statement nodes (excludes FunctionDeclaration).
192 BlockStatement: reportIfUnreachable,
193 BreakStatement: reportIfUnreachable,
194 ClassDeclaration: reportIfUnreachable,
195 ContinueStatement: reportIfUnreachable,
196 DebuggerStatement: reportIfUnreachable,
197 DoWhileStatement: reportIfUnreachable,
198 ExpressionStatement: reportIfUnreachable,
199 ForInStatement: reportIfUnreachable,
200 ForOfStatement: reportIfUnreachable,
201 ForStatement: reportIfUnreachable,
202 IfStatement: reportIfUnreachable,
203 ImportDeclaration: reportIfUnreachable,
204 LabeledStatement: reportIfUnreachable,
205 ReturnStatement: reportIfUnreachable,
206 SwitchStatement: reportIfUnreachable,
207 ThrowStatement: reportIfUnreachable,
208 TryStatement: reportIfUnreachable,
209
210 VariableDeclaration(node) {
211 if (node.kind !== "var" || node.declarations.some(isInitialized)) {
212 reportIfUnreachable(node);
213 }
214 },
215
216 WhileStatement: reportIfUnreachable,
217 WithStatement: reportIfUnreachable,
218 ExportNamedDeclaration: reportIfUnreachable,
219 ExportDefaultDeclaration: reportIfUnreachable,
220 ExportAllDeclaration: reportIfUnreachable,
221
222 "Program:exit"() {
223 reportIfUnreachable();
224 },
225
226 /*
227 * Instance fields defined in a subclass are never created if the constructor of the subclass
228 * doesn't call `super()`, so their definitions are unreachable code.
229 */
230 "MethodDefinition[kind='constructor']"() {
231 constructorInfo = {
232 upper: constructorInfo,
233 hasSuperCall: false
234 };
235 },
236 "MethodDefinition[kind='constructor']:exit"(node) {
237 const { hasSuperCall } = constructorInfo;
238
239 constructorInfo = constructorInfo.upper;
240
241 // skip typescript constructors without the body
242 if (!node.value.body) {
243 return;
244 }
245
246 const classDefinition = node.parent.parent;
247
248 if (classDefinition.superClass && !hasSuperCall) {
249 for (const element of classDefinition.body.body) {
250 if (element.type === "PropertyDefinition" && !element.static) {
251 reportIfUnreachable(element);
252 }
253 }
254 }
255 },
256 "CallExpression > Super.callee"() {
257 if (constructorInfo) {
258 constructorInfo.hasSuperCall = true;
259 }
260 }
261 };
262 }
263 };