UNPKG

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