]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-unreachable.js
dea86815aac5b98180b4f95dc76a6d5732f9658f
[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 /** @type {import('../shared/types').Rule} */
109 module.exports = {
110 meta: {
111 type: "problem",
112
113 docs: {
114 description: "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
115 recommended: true,
116 url: "https://eslint.org/docs/rules/no-unreachable"
117 },
118
119 schema: [],
120
121 messages: {
122 unreachableCode: "Unreachable code."
123 }
124 },
125
126 create(context) {
127 let currentCodePath = null;
128
129 /** @type {ConstructorInfo | null} */
130 let constructorInfo = null;
131
132 /** @type {ConsecutiveRange} */
133 const range = new ConsecutiveRange(context.getSourceCode());
134
135 /**
136 * Reports a given node if it's unreachable.
137 * @param {ASTNode} node A statement node to report.
138 * @returns {void}
139 */
140 function reportIfUnreachable(node) {
141 let nextNode = null;
142
143 if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
144
145 // Store this statement to distinguish consecutive statements.
146 if (range.isEmpty) {
147 range.reset(node);
148 return;
149 }
150
151 // Skip if this statement is inside of the current range.
152 if (range.contains(node)) {
153 return;
154 }
155
156 // Merge if this statement is consecutive to the current range.
157 if (range.isConsecutive(node)) {
158 range.merge(node);
159 return;
160 }
161
162 nextNode = node;
163 }
164
165 /*
166 * Report the current range since this statement is reachable or is
167 * not consecutive to the current range.
168 */
169 if (!range.isEmpty) {
170 context.report({
171 messageId: "unreachableCode",
172 loc: range.location,
173 node: range.startNode
174 });
175 }
176
177 // Update the current range.
178 range.reset(nextNode);
179 }
180
181 return {
182
183 // Manages the current code path.
184 onCodePathStart(codePath) {
185 currentCodePath = codePath;
186 },
187
188 onCodePathEnd() {
189 currentCodePath = currentCodePath.upper;
190 },
191
192 // Registers for all statement nodes (excludes FunctionDeclaration).
193 BlockStatement: reportIfUnreachable,
194 BreakStatement: reportIfUnreachable,
195 ClassDeclaration: reportIfUnreachable,
196 ContinueStatement: reportIfUnreachable,
197 DebuggerStatement: reportIfUnreachable,
198 DoWhileStatement: reportIfUnreachable,
199 ExpressionStatement: reportIfUnreachable,
200 ForInStatement: reportIfUnreachable,
201 ForOfStatement: reportIfUnreachable,
202 ForStatement: reportIfUnreachable,
203 IfStatement: reportIfUnreachable,
204 ImportDeclaration: reportIfUnreachable,
205 LabeledStatement: reportIfUnreachable,
206 ReturnStatement: reportIfUnreachable,
207 SwitchStatement: reportIfUnreachable,
208 ThrowStatement: reportIfUnreachable,
209 TryStatement: reportIfUnreachable,
210
211 VariableDeclaration(node) {
212 if (node.kind !== "var" || node.declarations.some(isInitialized)) {
213 reportIfUnreachable(node);
214 }
215 },
216
217 WhileStatement: reportIfUnreachable,
218 WithStatement: reportIfUnreachable,
219 ExportNamedDeclaration: reportIfUnreachable,
220 ExportDefaultDeclaration: reportIfUnreachable,
221 ExportAllDeclaration: reportIfUnreachable,
222
223 "Program:exit"() {
224 reportIfUnreachable();
225 },
226
227 /*
228 * Instance fields defined in a subclass are never created if the constructor of the subclass
229 * doesn't call `super()`, so their definitions are unreachable code.
230 */
231 "MethodDefinition[kind='constructor']"() {
232 constructorInfo = {
233 upper: constructorInfo,
234 hasSuperCall: false
235 };
236 },
237 "MethodDefinition[kind='constructor']:exit"(node) {
238 const { hasSuperCall } = constructorInfo;
239
240 constructorInfo = constructorInfo.upper;
241
242 // skip typescript constructors without the body
243 if (!node.value.body) {
244 return;
245 }
246
247 const classDefinition = node.parent.parent;
248
249 if (classDefinition.superClass && !hasSuperCall) {
250 for (const element of classDefinition.body.body) {
251 if (element.type === "PropertyDefinition" && !element.static) {
252 reportIfUnreachable(element);
253 }
254 }
255 }
256 },
257 "CallExpression > Super.callee"() {
258 if (constructorInfo) {
259 constructorInfo.hasSuperCall = true;
260 }
261 }
262 };
263 }
264 };