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