]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/constructor-super.js
first commit
[pve-eslint.git] / eslint / lib / rules / constructor-super.js
1 /**
2 * @fileoverview A rule to verify `super()` callings in constructor.
3 * @author Toru Nagashima
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Helpers
10 //------------------------------------------------------------------------------
11
12 /**
13 * Checks whether a given code path segment is reachable or not.
14 * @param {CodePathSegment} segment A code path segment to check.
15 * @returns {boolean} `true` if the segment is reachable.
16 */
17 function isReachable(segment) {
18 return segment.reachable;
19 }
20
21 /**
22 * Checks whether or not a given node is a constructor.
23 * @param {ASTNode} node A node to check. This node type is one of
24 * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
25 * `ArrowFunctionExpression`.
26 * @returns {boolean} `true` if the node is a constructor.
27 */
28 function isConstructorFunction(node) {
29 return (
30 node.type === "FunctionExpression" &&
31 node.parent.type === "MethodDefinition" &&
32 node.parent.kind === "constructor"
33 );
34 }
35
36 /**
37 * Checks whether a given node can be a constructor or not.
38 * @param {ASTNode} node A node to check.
39 * @returns {boolean} `true` if the node can be a constructor.
40 */
41 function isPossibleConstructor(node) {
42 if (!node) {
43 return false;
44 }
45
46 switch (node.type) {
47 case "ClassExpression":
48 case "FunctionExpression":
49 case "ThisExpression":
50 case "MemberExpression":
51 case "CallExpression":
52 case "NewExpression":
53 case "YieldExpression":
54 case "TaggedTemplateExpression":
55 case "MetaProperty":
56 return true;
57
58 case "Identifier":
59 return node.name !== "undefined";
60
61 case "AssignmentExpression":
62 return isPossibleConstructor(node.right);
63
64 case "LogicalExpression":
65 return (
66 isPossibleConstructor(node.left) ||
67 isPossibleConstructor(node.right)
68 );
69
70 case "ConditionalExpression":
71 return (
72 isPossibleConstructor(node.alternate) ||
73 isPossibleConstructor(node.consequent)
74 );
75
76 case "SequenceExpression": {
77 const lastExpression = node.expressions[node.expressions.length - 1];
78
79 return isPossibleConstructor(lastExpression);
80 }
81
82 default:
83 return false;
84 }
85 }
86
87 //------------------------------------------------------------------------------
88 // Rule Definition
89 //------------------------------------------------------------------------------
90
91 module.exports = {
92 meta: {
93 type: "problem",
94
95 docs: {
96 description: "require `super()` calls in constructors",
97 category: "ECMAScript 6",
98 recommended: true,
99 url: "https://eslint.org/docs/rules/constructor-super"
100 },
101
102 schema: [],
103
104 messages: {
105 missingSome: "Lacked a call of 'super()' in some code paths.",
106 missingAll: "Expected to call 'super()'.",
107
108 duplicate: "Unexpected duplicate 'super()'.",
109 badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
110 unexpected: "Unexpected 'super()'."
111 }
112 },
113
114 create(context) {
115
116 /*
117 * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
118 * Information for each constructor.
119 * - upper: Information of the upper constructor.
120 * - hasExtends: A flag which shows whether own class has a valid `extends`
121 * part.
122 * - scope: The scope of own class.
123 * - codePath: The code path object of the constructor.
124 */
125 let funcInfo = null;
126
127 /*
128 * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
129 * Information for each code path segment.
130 * - calledInSomePaths: A flag of be called `super()` in some code paths.
131 * - calledInEveryPaths: A flag of be called `super()` in all code paths.
132 * - validNodes:
133 */
134 let segInfoMap = Object.create(null);
135
136 /**
137 * Gets the flag which shows `super()` is called in some paths.
138 * @param {CodePathSegment} segment A code path segment to get.
139 * @returns {boolean} The flag which shows `super()` is called in some paths
140 */
141 function isCalledInSomePath(segment) {
142 return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
143 }
144
145 /**
146 * Gets the flag which shows `super()` is called in all paths.
147 * @param {CodePathSegment} segment A code path segment to get.
148 * @returns {boolean} The flag which shows `super()` is called in all paths.
149 */
150 function isCalledInEveryPath(segment) {
151
152 /*
153 * If specific segment is the looped segment of the current segment,
154 * skip the segment.
155 * If not skipped, this never becomes true after a loop.
156 */
157 if (segment.nextSegments.length === 1 &&
158 segment.nextSegments[0].isLoopedPrevSegment(segment)
159 ) {
160 return true;
161 }
162 return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
163 }
164
165 return {
166
167 /**
168 * Stacks a constructor information.
169 * @param {CodePath} codePath A code path which was started.
170 * @param {ASTNode} node The current node.
171 * @returns {void}
172 */
173 onCodePathStart(codePath, node) {
174 if (isConstructorFunction(node)) {
175
176 // Class > ClassBody > MethodDefinition > FunctionExpression
177 const classNode = node.parent.parent.parent;
178 const superClass = classNode.superClass;
179
180 funcInfo = {
181 upper: funcInfo,
182 isConstructor: true,
183 hasExtends: Boolean(superClass),
184 superIsConstructor: isPossibleConstructor(superClass),
185 codePath
186 };
187 } else {
188 funcInfo = {
189 upper: funcInfo,
190 isConstructor: false,
191 hasExtends: false,
192 superIsConstructor: false,
193 codePath
194 };
195 }
196 },
197
198 /**
199 * Pops a constructor information.
200 * And reports if `super()` lacked.
201 * @param {CodePath} codePath A code path which was ended.
202 * @param {ASTNode} node The current node.
203 * @returns {void}
204 */
205 onCodePathEnd(codePath, node) {
206 const hasExtends = funcInfo.hasExtends;
207
208 // Pop.
209 funcInfo = funcInfo.upper;
210
211 if (!hasExtends) {
212 return;
213 }
214
215 // Reports if `super()` lacked.
216 const segments = codePath.returnedSegments;
217 const calledInEveryPaths = segments.every(isCalledInEveryPath);
218 const calledInSomePaths = segments.some(isCalledInSomePath);
219
220 if (!calledInEveryPaths) {
221 context.report({
222 messageId: calledInSomePaths
223 ? "missingSome"
224 : "missingAll",
225 node: node.parent
226 });
227 }
228 },
229
230 /**
231 * Initialize information of a given code path segment.
232 * @param {CodePathSegment} segment A code path segment to initialize.
233 * @returns {void}
234 */
235 onCodePathSegmentStart(segment) {
236 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
237 return;
238 }
239
240 // Initialize info.
241 const info = segInfoMap[segment.id] = {
242 calledInSomePaths: false,
243 calledInEveryPaths: false,
244 validNodes: []
245 };
246
247 // When there are previous segments, aggregates these.
248 const prevSegments = segment.prevSegments;
249
250 if (prevSegments.length > 0) {
251 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
252 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
253 }
254 },
255
256 /**
257 * Update information of the code path segment when a code path was
258 * looped.
259 * @param {CodePathSegment} fromSegment The code path segment of the
260 * end of a loop.
261 * @param {CodePathSegment} toSegment A code path segment of the head
262 * of a loop.
263 * @returns {void}
264 */
265 onCodePathSegmentLoop(fromSegment, toSegment) {
266 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
267 return;
268 }
269
270 // Update information inside of the loop.
271 const isRealLoop = toSegment.prevSegments.length >= 2;
272
273 funcInfo.codePath.traverseSegments(
274 { first: toSegment, last: fromSegment },
275 segment => {
276 const info = segInfoMap[segment.id];
277 const prevSegments = segment.prevSegments;
278
279 // Updates flags.
280 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
281 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
282
283 // If flags become true anew, reports the valid nodes.
284 if (info.calledInSomePaths || isRealLoop) {
285 const nodes = info.validNodes;
286
287 info.validNodes = [];
288
289 for (let i = 0; i < nodes.length; ++i) {
290 const node = nodes[i];
291
292 context.report({
293 messageId: "duplicate",
294 node
295 });
296 }
297 }
298 }
299 );
300 },
301
302 /**
303 * Checks for a call of `super()`.
304 * @param {ASTNode} node A CallExpression node to check.
305 * @returns {void}
306 */
307 "CallExpression:exit"(node) {
308 if (!(funcInfo && funcInfo.isConstructor)) {
309 return;
310 }
311
312 // Skips except `super()`.
313 if (node.callee.type !== "Super") {
314 return;
315 }
316
317 // Reports if needed.
318 if (funcInfo.hasExtends) {
319 const segments = funcInfo.codePath.currentSegments;
320 let duplicate = false;
321 let info = null;
322
323 for (let i = 0; i < segments.length; ++i) {
324 const segment = segments[i];
325
326 if (segment.reachable) {
327 info = segInfoMap[segment.id];
328
329 duplicate = duplicate || info.calledInSomePaths;
330 info.calledInSomePaths = info.calledInEveryPaths = true;
331 }
332 }
333
334 if (info) {
335 if (duplicate) {
336 context.report({
337 messageId: "duplicate",
338 node
339 });
340 } else if (!funcInfo.superIsConstructor) {
341 context.report({
342 messageId: "badSuper",
343 node
344 });
345 } else {
346 info.validNodes.push(node);
347 }
348 }
349 } else if (funcInfo.codePath.currentSegments.some(isReachable)) {
350 context.report({
351 messageId: "unexpected",
352 node
353 });
354 }
355 },
356
357 /**
358 * Set the mark to the returned path as `super()` was called.
359 * @param {ASTNode} node A ReturnStatement node to check.
360 * @returns {void}
361 */
362 ReturnStatement(node) {
363 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
364 return;
365 }
366
367 // Skips if no argument.
368 if (!node.argument) {
369 return;
370 }
371
372 // Returning argument is a substitute of 'super()'.
373 const segments = funcInfo.codePath.currentSegments;
374
375 for (let i = 0; i < segments.length; ++i) {
376 const segment = segments[i];
377
378 if (segment.reachable) {
379 const info = segInfoMap[segment.id];
380
381 info.calledInSomePaths = info.calledInEveryPaths = true;
382 }
383 }
384 },
385
386 /**
387 * Resets state.
388 * @returns {void}
389 */
390 "Program:exit"() {
391 segInfoMap = Object.create(null);
392 }
393 };
394 }
395 };