minor adjustment to readme
[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 "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 };