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("./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 | */
|
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 | 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 | };
|