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