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 | 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 | };
|