1 | /**
|
2 | * @fileoverview A rule to disallow using `this`/`super` before `super()`.
|
3 | * @author Toru Nagashima
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | const 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 | */
|
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 |
|
37 | module.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 | };
|