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