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