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