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