source: imaps-frontend/node_modules/eslint/lib/rules/constructor-super.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 15.2 KB
Line 
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 all segments in a set and returns true if any are reachable.
14 * @param {Set<CodePathSegment>} segments The segments to check.
15 * @returns {boolean} True if any segment is reachable; false otherwise.
16 */
17function isAnySegmentReachable(segments) {
18
19 for (const segment of segments) {
20 if (segment.reachable) {
21 return true;
22 }
23 }
24
25 return false;
26}
27
28/**
29 * Checks whether or not a given node is a constructor.
30 * @param {ASTNode} node A node to check. This node type is one of
31 * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
32 * `ArrowFunctionExpression`.
33 * @returns {boolean} `true` if the node is a constructor.
34 */
35function isConstructorFunction(node) {
36 return (
37 node.type === "FunctionExpression" &&
38 node.parent.type === "MethodDefinition" &&
39 node.parent.kind === "constructor"
40 );
41}
42
43/**
44 * Checks whether a given node can be a constructor or not.
45 * @param {ASTNode} node A node to check.
46 * @returns {boolean} `true` if the node can be a constructor.
47 */
48function isPossibleConstructor(node) {
49 if (!node) {
50 return false;
51 }
52
53 switch (node.type) {
54 case "ClassExpression":
55 case "FunctionExpression":
56 case "ThisExpression":
57 case "MemberExpression":
58 case "CallExpression":
59 case "NewExpression":
60 case "ChainExpression":
61 case "YieldExpression":
62 case "TaggedTemplateExpression":
63 case "MetaProperty":
64 return true;
65
66 case "Identifier":
67 return node.name !== "undefined";
68
69 case "AssignmentExpression":
70 if (["=", "&&="].includes(node.operator)) {
71 return isPossibleConstructor(node.right);
72 }
73
74 if (["||=", "??="].includes(node.operator)) {
75 return (
76 isPossibleConstructor(node.left) ||
77 isPossibleConstructor(node.right)
78 );
79 }
80
81 /**
82 * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
83 * An assignment expression with a mathematical operator can either evaluate to a primitive value,
84 * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
85 */
86 return false;
87
88 case "LogicalExpression":
89
90 /*
91 * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
92 * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
93 * possible constructor. A future improvement could verify that the left side could be truthy by
94 * excluding falsy literals.
95 */
96 if (node.operator === "&&") {
97 return isPossibleConstructor(node.right);
98 }
99
100 return (
101 isPossibleConstructor(node.left) ||
102 isPossibleConstructor(node.right)
103 );
104
105 case "ConditionalExpression":
106 return (
107 isPossibleConstructor(node.alternate) ||
108 isPossibleConstructor(node.consequent)
109 );
110
111 case "SequenceExpression": {
112 const lastExpression = node.expressions[node.expressions.length - 1];
113
114 return isPossibleConstructor(lastExpression);
115 }
116
117 default:
118 return false;
119 }
120}
121
122//------------------------------------------------------------------------------
123// Rule Definition
124//------------------------------------------------------------------------------
125
126/** @type {import('../shared/types').Rule} */
127module.exports = {
128 meta: {
129 type: "problem",
130
131 docs: {
132 description: "Require `super()` calls in constructors",
133 recommended: true,
134 url: "https://eslint.org/docs/latest/rules/constructor-super"
135 },
136
137 schema: [],
138
139 messages: {
140 missingSome: "Lacked a call of 'super()' in some code paths.",
141 missingAll: "Expected to call 'super()'.",
142
143 duplicate: "Unexpected duplicate 'super()'.",
144 badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
145 unexpected: "Unexpected 'super()'."
146 }
147 },
148
149 create(context) {
150
151 /*
152 * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
153 * Information for each constructor.
154 * - upper: Information of the upper constructor.
155 * - hasExtends: A flag which shows whether own class has a valid `extends`
156 * part.
157 * - scope: The scope of own class.
158 * - codePath: The code path object of the constructor.
159 */
160 let funcInfo = null;
161
162 /*
163 * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
164 * Information for each code path segment.
165 * - calledInSomePaths: A flag of be called `super()` in some code paths.
166 * - calledInEveryPaths: A flag of be called `super()` in all code paths.
167 * - validNodes:
168 */
169 let segInfoMap = Object.create(null);
170
171 /**
172 * Gets the flag which shows `super()` is called in some paths.
173 * @param {CodePathSegment} segment A code path segment to get.
174 * @returns {boolean} The flag which shows `super()` is called in some paths
175 */
176 function isCalledInSomePath(segment) {
177 return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
178 }
179
180 /**
181 * Gets the flag which shows `super()` is called in all paths.
182 * @param {CodePathSegment} segment A code path segment to get.
183 * @returns {boolean} The flag which shows `super()` is called in all paths.
184 */
185 function isCalledInEveryPath(segment) {
186
187 /*
188 * If specific segment is the looped segment of the current segment,
189 * skip the segment.
190 * If not skipped, this never becomes true after a loop.
191 */
192 if (segment.nextSegments.length === 1 &&
193 segment.nextSegments[0].isLoopedPrevSegment(segment)
194 ) {
195 return true;
196 }
197 return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
198 }
199
200 return {
201
202 /**
203 * Stacks a constructor information.
204 * @param {CodePath} codePath A code path which was started.
205 * @param {ASTNode} node The current node.
206 * @returns {void}
207 */
208 onCodePathStart(codePath, node) {
209 if (isConstructorFunction(node)) {
210
211 // Class > ClassBody > MethodDefinition > FunctionExpression
212 const classNode = node.parent.parent.parent;
213 const superClass = classNode.superClass;
214
215 funcInfo = {
216 upper: funcInfo,
217 isConstructor: true,
218 hasExtends: Boolean(superClass),
219 superIsConstructor: isPossibleConstructor(superClass),
220 codePath,
221 currentSegments: new Set()
222 };
223 } else {
224 funcInfo = {
225 upper: funcInfo,
226 isConstructor: false,
227 hasExtends: false,
228 superIsConstructor: false,
229 codePath,
230 currentSegments: new Set()
231 };
232 }
233 },
234
235 /**
236 * Pops a constructor information.
237 * And reports if `super()` lacked.
238 * @param {CodePath} codePath A code path which was ended.
239 * @param {ASTNode} node The current node.
240 * @returns {void}
241 */
242 onCodePathEnd(codePath, node) {
243 const hasExtends = funcInfo.hasExtends;
244
245 // Pop.
246 funcInfo = funcInfo.upper;
247
248 if (!hasExtends) {
249 return;
250 }
251
252 // Reports if `super()` lacked.
253 const segments = codePath.returnedSegments;
254 const calledInEveryPaths = segments.every(isCalledInEveryPath);
255 const calledInSomePaths = segments.some(isCalledInSomePath);
256
257 if (!calledInEveryPaths) {
258 context.report({
259 messageId: calledInSomePaths
260 ? "missingSome"
261 : "missingAll",
262 node: node.parent
263 });
264 }
265 },
266
267 /**
268 * Initialize information of a given code path segment.
269 * @param {CodePathSegment} segment A code path segment to initialize.
270 * @returns {void}
271 */
272 onCodePathSegmentStart(segment) {
273
274 funcInfo.currentSegments.add(segment);
275
276 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
277 return;
278 }
279
280 // Initialize info.
281 const info = segInfoMap[segment.id] = {
282 calledInSomePaths: false,
283 calledInEveryPaths: false,
284 validNodes: []
285 };
286
287 // When there are previous segments, aggregates these.
288 const prevSegments = segment.prevSegments;
289
290 if (prevSegments.length > 0) {
291 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
292 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
293 }
294 },
295
296 onUnreachableCodePathSegmentStart(segment) {
297 funcInfo.currentSegments.add(segment);
298 },
299
300 onUnreachableCodePathSegmentEnd(segment) {
301 funcInfo.currentSegments.delete(segment);
302 },
303
304 onCodePathSegmentEnd(segment) {
305 funcInfo.currentSegments.delete(segment);
306 },
307
308
309 /**
310 * Update information of the code path segment when a code path was
311 * looped.
312 * @param {CodePathSegment} fromSegment The code path segment of the
313 * end of a loop.
314 * @param {CodePathSegment} toSegment A code path segment of the head
315 * of a loop.
316 * @returns {void}
317 */
318 onCodePathSegmentLoop(fromSegment, toSegment) {
319 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
320 return;
321 }
322
323 // Update information inside of the loop.
324 const isRealLoop = toSegment.prevSegments.length >= 2;
325
326 funcInfo.codePath.traverseSegments(
327 { first: toSegment, last: fromSegment },
328 segment => {
329 const info = segInfoMap[segment.id];
330 const prevSegments = segment.prevSegments;
331
332 // Updates flags.
333 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
334 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
335
336 // If flags become true anew, reports the valid nodes.
337 if (info.calledInSomePaths || isRealLoop) {
338 const nodes = info.validNodes;
339
340 info.validNodes = [];
341
342 for (let i = 0; i < nodes.length; ++i) {
343 const node = nodes[i];
344
345 context.report({
346 messageId: "duplicate",
347 node
348 });
349 }
350 }
351 }
352 );
353 },
354
355 /**
356 * Checks for a call of `super()`.
357 * @param {ASTNode} node A CallExpression node to check.
358 * @returns {void}
359 */
360 "CallExpression:exit"(node) {
361 if (!(funcInfo && funcInfo.isConstructor)) {
362 return;
363 }
364
365 // Skips except `super()`.
366 if (node.callee.type !== "Super") {
367 return;
368 }
369
370 // Reports if needed.
371 if (funcInfo.hasExtends) {
372 const segments = funcInfo.currentSegments;
373 let duplicate = false;
374 let info = null;
375
376 for (const segment of segments) {
377
378 if (segment.reachable) {
379 info = segInfoMap[segment.id];
380
381 duplicate = duplicate || info.calledInSomePaths;
382 info.calledInSomePaths = info.calledInEveryPaths = true;
383 }
384 }
385
386 if (info) {
387 if (duplicate) {
388 context.report({
389 messageId: "duplicate",
390 node
391 });
392 } else if (!funcInfo.superIsConstructor) {
393 context.report({
394 messageId: "badSuper",
395 node
396 });
397 } else {
398 info.validNodes.push(node);
399 }
400 }
401 } else if (isAnySegmentReachable(funcInfo.currentSegments)) {
402 context.report({
403 messageId: "unexpected",
404 node
405 });
406 }
407 },
408
409 /**
410 * Set the mark to the returned path as `super()` was called.
411 * @param {ASTNode} node A ReturnStatement node to check.
412 * @returns {void}
413 */
414 ReturnStatement(node) {
415 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
416 return;
417 }
418
419 // Skips if no argument.
420 if (!node.argument) {
421 return;
422 }
423
424 // Returning argument is a substitute of 'super()'.
425 const segments = funcInfo.currentSegments;
426
427 for (const segment of segments) {
428
429 if (segment.reachable) {
430 const info = segInfoMap[segment.id];
431
432 info.calledInSomePaths = info.calledInEveryPaths = true;
433 }
434 }
435 },
436
437 /**
438 * Resets state.
439 * @returns {void}
440 */
441 "Program:exit"() {
442 segInfoMap = Object.create(null);
443 }
444 };
445 }
446};
Note: See TracBrowser for help on using the repository browser.