UNPKG

10.2 kBJavaScriptView Raw
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
12const 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 */
25function 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
37module.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};