]>
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 | ||
34eeec05 | 37 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
38 | module.exports = { |
39 | meta: { | |
40 | type: "problem", | |
41 | ||
42 | docs: { | |
8f9d1d4d | 43 | description: "Disallow `this`/`super` before calling `super()` in constructors", |
eb39fafa | 44 | recommended: true, |
f2a92ac6 | 45 | url: "https://eslint.org/docs/latest/rules/no-this-before-super" |
eb39fafa DC |
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 | * | |
456be15e | 174 | * And this traverses all segments of this code path then reports every |
eb39fafa DC |
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 | }; |