.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-this-before-super.js
1 /**
2  * @fileoverview A rule to disallow using `this`/`super` before `super()`.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 /**
19  * Checks whether or not a given node is a constructor.
20  * @param {ASTNode} node A node to check. This node type is one of
21  *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and
22  *   `ArrowFunctionExpression`.
23  * @returns {boolean} `true` if the node is a constructor.
24  */
25 function isConstructorFunction(node) {
26     return (
27         node.type === "FunctionExpression" &&
28         node.parent.type === "MethodDefinition" &&
29         node.parent.kind === "constructor"
30     );
31 }
32
33 //------------------------------------------------------------------------------
34 // Rule Definition
35 //------------------------------------------------------------------------------
36
37 module.exports = {
38     meta: {
39         type: "problem",
40
41         docs: {
42             description: "disallow `this`/`super` before calling `super()` in constructors",
43             category: "ECMAScript 6",
44             recommended: true,
45             url: "https://eslint.org/docs/rules/no-this-before-super"
46         },
47
48         schema: [],
49
50         messages: {
51             noBeforeSuper: "'{{kind}}' is not allowed before 'super()'."
52         }
53     },
54
55     create(context) {
56
57         /*
58          * Information for each constructor.
59          * - upper:      Information of the upper constructor.
60          * - hasExtends: A flag which shows whether the owner class has a valid
61          *   `extends` part.
62          * - scope:      The scope of the owner class.
63          * - codePath:   The code path of this constructor.
64          */
65         let funcInfo = null;
66
67         /*
68          * Information for each code path segment.
69          * Each key is the id of a code path segment.
70          * Each value is an object:
71          * - superCalled:  The flag which shows `super()` called in all code paths.
72          * - invalidNodes: The array of invalid ThisExpression and Super nodes.
73          */
74         let segInfoMap = Object.create(null);
75
76         /**
77          * Gets whether or not `super()` is called in a given code path segment.
78          * @param {CodePathSegment} segment A code path segment to get.
79          * @returns {boolean} `true` if `super()` is called.
80          */
81         function isCalled(segment) {
82             return !segment.reachable || segInfoMap[segment.id].superCalled;
83         }
84
85         /**
86          * Checks whether or not this is in a constructor.
87          * @returns {boolean} `true` if this is in a constructor.
88          */
89         function isInConstructorOfDerivedClass() {
90             return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
91         }
92
93         /**
94          * Checks whether or not this is before `super()` is called.
95          * @returns {boolean} `true` if this is before `super()` is called.
96          */
97         function isBeforeCallOfSuper() {
98             return (
99                 isInConstructorOfDerivedClass() &&
100                 !funcInfo.codePath.currentSegments.every(isCalled)
101             );
102         }
103
104         /**
105          * Sets a given node as invalid.
106          * @param {ASTNode} node A node to set as invalid. This is one of
107          *      a ThisExpression and a Super.
108          * @returns {void}
109          */
110         function setInvalid(node) {
111             const segments = funcInfo.codePath.currentSegments;
112
113             for (let i = 0; i < segments.length; ++i) {
114                 const segment = segments[i];
115
116                 if (segment.reachable) {
117                     segInfoMap[segment.id].invalidNodes.push(node);
118                 }
119             }
120         }
121
122         /**
123          * Sets the current segment as `super` was called.
124          * @returns {void}
125          */
126         function setSuperCalled() {
127             const segments = funcInfo.codePath.currentSegments;
128
129             for (let i = 0; i < segments.length; ++i) {
130                 const segment = segments[i];
131
132                 if (segment.reachable) {
133                     segInfoMap[segment.id].superCalled = true;
134                 }
135             }
136         }
137
138         return {
139
140             /**
141              * Adds information of a constructor into the stack.
142              * @param {CodePath} codePath A code path which was started.
143              * @param {ASTNode} node The current node.
144              * @returns {void}
145              */
146             onCodePathStart(codePath, node) {
147                 if (isConstructorFunction(node)) {
148
149                     // Class > ClassBody > MethodDefinition > FunctionExpression
150                     const classNode = node.parent.parent.parent;
151
152                     funcInfo = {
153                         upper: funcInfo,
154                         isConstructor: true,
155                         hasExtends: Boolean(
156                             classNode.superClass &&
157                             !astUtils.isNullOrUndefined(classNode.superClass)
158                         ),
159                         codePath
160                     };
161                 } else {
162                     funcInfo = {
163                         upper: funcInfo,
164                         isConstructor: false,
165                         hasExtends: false,
166                         codePath
167                     };
168                 }
169             },
170
171             /**
172              * Removes the top of stack item.
173              *
174              * And this traverses all segments of this code path then reports every
175              * invalid node.
176              * @param {CodePath} codePath A code path which was ended.
177              * @returns {void}
178              */
179             onCodePathEnd(codePath) {
180                 const isDerivedClass = funcInfo.hasExtends;
181
182                 funcInfo = funcInfo.upper;
183                 if (!isDerivedClass) {
184                     return;
185                 }
186
187                 codePath.traverseSegments((segment, controller) => {
188                     const info = segInfoMap[segment.id];
189
190                     for (let i = 0; i < info.invalidNodes.length; ++i) {
191                         const invalidNode = info.invalidNodes[i];
192
193                         context.report({
194                             messageId: "noBeforeSuper",
195                             node: invalidNode,
196                             data: {
197                                 kind: invalidNode.type === "Super" ? "super" : "this"
198                             }
199                         });
200                     }
201
202                     if (info.superCalled) {
203                         controller.skip();
204                     }
205                 });
206             },
207
208             /**
209              * Initialize information of a given code path segment.
210              * @param {CodePathSegment} segment A code path segment to initialize.
211              * @returns {void}
212              */
213             onCodePathSegmentStart(segment) {
214                 if (!isInConstructorOfDerivedClass()) {
215                     return;
216                 }
217
218                 // Initialize info.
219                 segInfoMap[segment.id] = {
220                     superCalled: (
221                         segment.prevSegments.length > 0 &&
222                         segment.prevSegments.every(isCalled)
223                     ),
224                     invalidNodes: []
225                 };
226             },
227
228             /**
229              * Update information of the code path segment when a code path was
230              * looped.
231              * @param {CodePathSegment} fromSegment The code path segment of the
232              *      end of a loop.
233              * @param {CodePathSegment} toSegment A code path segment of the head
234              *      of a loop.
235              * @returns {void}
236              */
237             onCodePathSegmentLoop(fromSegment, toSegment) {
238                 if (!isInConstructorOfDerivedClass()) {
239                     return;
240                 }
241
242                 // Update information inside of the loop.
243                 funcInfo.codePath.traverseSegments(
244                     { first: toSegment, last: fromSegment },
245                     (segment, controller) => {
246                         const info = segInfoMap[segment.id];
247
248                         if (info.superCalled) {
249                             info.invalidNodes = [];
250                             controller.skip();
251                         } else if (
252                             segment.prevSegments.length > 0 &&
253                             segment.prevSegments.every(isCalled)
254                         ) {
255                             info.superCalled = true;
256                             info.invalidNodes = [];
257                         }
258                     }
259                 );
260             },
261
262             /**
263              * Reports if this is before `super()`.
264              * @param {ASTNode} node A target node.
265              * @returns {void}
266              */
267             ThisExpression(node) {
268                 if (isBeforeCallOfSuper()) {
269                     setInvalid(node);
270                 }
271             },
272
273             /**
274              * Reports if this is before `super()`.
275              * @param {ASTNode} node A target node.
276              * @returns {void}
277              */
278             Super(node) {
279                 if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
280                     setInvalid(node);
281                 }
282             },
283
284             /**
285              * Marks `super()` called.
286              * @param {ASTNode} node A target node.
287              * @returns {void}
288              */
289             "CallExpression:exit"(node) {
290                 if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
291                     setSuperCalled();
292                 }
293             },
294
295             /**
296              * Resets state.
297              * @returns {void}
298              */
299             "Program:exit"() {
300                 segInfoMap = Object.create(null);
301             }
302         };
303     }
304 };