2 * @fileoverview A rule to verify `super()` callings in constructor.
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
13 * Checks whether a given code path segment is reachable or not.
14 * @param {CodePathSegment} segment A code path segment to check.
15 * @returns {boolean} `true` if the segment is reachable.
17 function isReachable(segment) {
18 return segment.reachable;
22 * Checks whether or not a given node is a constructor.
23 * @param {ASTNode} node A node to check. This node type is one of
24 * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
25 * `ArrowFunctionExpression`.
26 * @returns {boolean} `true` if the node is a constructor.
28 function isConstructorFunction(node) {
30 node.type === "FunctionExpression" &&
31 node.parent.type === "MethodDefinition" &&
32 node.parent.kind === "constructor"
37 * Checks whether a given node can be a constructor or not.
38 * @param {ASTNode} node A node to check.
39 * @returns {boolean} `true` if the node can be a constructor.
41 function isPossibleConstructor(node) {
47 case "ClassExpression":
48 case "FunctionExpression":
49 case "ThisExpression":
50 case "MemberExpression":
51 case "CallExpression":
53 case "ChainExpression":
54 case "YieldExpression":
55 case "TaggedTemplateExpression":
60 return node.name !== "undefined";
62 case "AssignmentExpression":
63 if (["=", "&&="].includes(node.operator)) {
64 return isPossibleConstructor(node.right);
67 if (["||=", "??="].includes(node.operator)) {
69 isPossibleConstructor(node.left) ||
70 isPossibleConstructor(node.right)
75 * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
76 * An assignment expression with a mathematical operator can either evaluate to a primitive value,
77 * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
81 case "LogicalExpression":
84 * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
85 * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
86 * possible constructor. A future improvement could verify that the left side could be truthy by
87 * excluding falsy literals.
89 if (node.operator === "&&") {
90 return isPossibleConstructor(node.right);
94 isPossibleConstructor(node.left) ||
95 isPossibleConstructor(node.right)
98 case "ConditionalExpression":
100 isPossibleConstructor(node.alternate) ||
101 isPossibleConstructor(node.consequent)
104 case "SequenceExpression": {
105 const lastExpression = node.expressions[node.expressions.length - 1];
107 return isPossibleConstructor(lastExpression);
115 //------------------------------------------------------------------------------
117 //------------------------------------------------------------------------------
124 description: "require `super()` calls in constructors",
125 category: "ECMAScript 6",
127 url: "https://eslint.org/docs/rules/constructor-super"
133 missingSome: "Lacked a call of 'super()' in some code paths.",
134 missingAll: "Expected to call 'super()'.",
136 duplicate: "Unexpected duplicate 'super()'.",
137 badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
138 unexpected: "Unexpected 'super()'."
145 * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
146 * Information for each constructor.
147 * - upper: Information of the upper constructor.
148 * - hasExtends: A flag which shows whether own class has a valid `extends`
150 * - scope: The scope of own class.
151 * - codePath: The code path object of the constructor.
156 * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
157 * Information for each code path segment.
158 * - calledInSomePaths: A flag of be called `super()` in some code paths.
159 * - calledInEveryPaths: A flag of be called `super()` in all code paths.
162 let segInfoMap = Object.create(null);
165 * Gets the flag which shows `super()` is called in some paths.
166 * @param {CodePathSegment} segment A code path segment to get.
167 * @returns {boolean} The flag which shows `super()` is called in some paths
169 function isCalledInSomePath(segment) {
170 return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
174 * Gets the flag which shows `super()` is called in all paths.
175 * @param {CodePathSegment} segment A code path segment to get.
176 * @returns {boolean} The flag which shows `super()` is called in all paths.
178 function isCalledInEveryPath(segment) {
181 * If specific segment is the looped segment of the current segment,
183 * If not skipped, this never becomes true after a loop.
185 if (segment.nextSegments.length === 1 &&
186 segment.nextSegments[0].isLoopedPrevSegment(segment)
190 return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
196 * Stacks a constructor information.
197 * @param {CodePath} codePath A code path which was started.
198 * @param {ASTNode} node The current node.
201 onCodePathStart(codePath, node) {
202 if (isConstructorFunction(node)) {
204 // Class > ClassBody > MethodDefinition > FunctionExpression
205 const classNode = node.parent.parent.parent;
206 const superClass = classNode.superClass;
211 hasExtends: Boolean(superClass),
212 superIsConstructor: isPossibleConstructor(superClass),
218 isConstructor: false,
220 superIsConstructor: false,
227 * Pops a constructor information.
228 * And reports if `super()` lacked.
229 * @param {CodePath} codePath A code path which was ended.
230 * @param {ASTNode} node The current node.
233 onCodePathEnd(codePath, node) {
234 const hasExtends = funcInfo.hasExtends;
237 funcInfo = funcInfo.upper;
243 // Reports if `super()` lacked.
244 const segments = codePath.returnedSegments;
245 const calledInEveryPaths = segments.every(isCalledInEveryPath);
246 const calledInSomePaths = segments.some(isCalledInSomePath);
248 if (!calledInEveryPaths) {
250 messageId: calledInSomePaths
259 * Initialize information of a given code path segment.
260 * @param {CodePathSegment} segment A code path segment to initialize.
263 onCodePathSegmentStart(segment) {
264 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
269 const info = segInfoMap[segment.id] = {
270 calledInSomePaths: false,
271 calledInEveryPaths: false,
275 // When there are previous segments, aggregates these.
276 const prevSegments = segment.prevSegments;
278 if (prevSegments.length > 0) {
279 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
280 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
285 * Update information of the code path segment when a code path was
287 * @param {CodePathSegment} fromSegment The code path segment of the
289 * @param {CodePathSegment} toSegment A code path segment of the head
293 onCodePathSegmentLoop(fromSegment, toSegment) {
294 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
298 // Update information inside of the loop.
299 const isRealLoop = toSegment.prevSegments.length >= 2;
301 funcInfo.codePath.traverseSegments(
302 { first: toSegment, last: fromSegment },
304 const info = segInfoMap[segment.id];
305 const prevSegments = segment.prevSegments;
308 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
309 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
311 // If flags become true anew, reports the valid nodes.
312 if (info.calledInSomePaths || isRealLoop) {
313 const nodes = info.validNodes;
315 info.validNodes = [];
317 for (let i = 0; i < nodes.length; ++i) {
318 const node = nodes[i];
321 messageId: "duplicate",
331 * Checks for a call of `super()`.
332 * @param {ASTNode} node A CallExpression node to check.
335 "CallExpression:exit"(node) {
336 if (!(funcInfo && funcInfo.isConstructor)) {
340 // Skips except `super()`.
341 if (node.callee.type !== "Super") {
345 // Reports if needed.
346 if (funcInfo.hasExtends) {
347 const segments = funcInfo.codePath.currentSegments;
348 let duplicate = false;
351 for (let i = 0; i < segments.length; ++i) {
352 const segment = segments[i];
354 if (segment.reachable) {
355 info = segInfoMap[segment.id];
357 duplicate = duplicate || info.calledInSomePaths;
358 info.calledInSomePaths = info.calledInEveryPaths = true;
365 messageId: "duplicate",
368 } else if (!funcInfo.superIsConstructor) {
370 messageId: "badSuper",
374 info.validNodes.push(node);
377 } else if (funcInfo.codePath.currentSegments.some(isReachable)) {
379 messageId: "unexpected",
386 * Set the mark to the returned path as `super()` was called.
387 * @param {ASTNode} node A ReturnStatement node to check.
390 ReturnStatement(node) {
391 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
395 // Skips if no argument.
396 if (!node.argument) {
400 // Returning argument is a substitute of 'super()'.
401 const segments = funcInfo.codePath.currentSegments;
403 for (let i = 0; i < segments.length; ++i) {
404 const segment = segments[i];
406 if (segment.reachable) {
407 const info = segInfoMap[segment.id];
409 info.calledInSomePaths = info.calledInEveryPaths = true;
419 segInfoMap = Object.create(null);