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 "YieldExpression":
54 case "TaggedTemplateExpression":
59 return node.name !== "undefined";
61 case "AssignmentExpression":
62 return isPossibleConstructor(node.right);
64 case "LogicalExpression":
66 isPossibleConstructor(node.left) ||
67 isPossibleConstructor(node.right)
70 case "ConditionalExpression":
72 isPossibleConstructor(node.alternate) ||
73 isPossibleConstructor(node.consequent)
76 case "SequenceExpression": {
77 const lastExpression = node.expressions[node.expressions.length - 1];
79 return isPossibleConstructor(lastExpression);
87 //------------------------------------------------------------------------------
89 //------------------------------------------------------------------------------
96 description: "require `super()` calls in constructors",
97 category: "ECMAScript 6",
99 url: "https://eslint.org/docs/rules/constructor-super"
105 missingSome: "Lacked a call of 'super()' in some code paths.",
106 missingAll: "Expected to call 'super()'.",
108 duplicate: "Unexpected duplicate 'super()'.",
109 badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
110 unexpected: "Unexpected 'super()'."
117 * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
118 * Information for each constructor.
119 * - upper: Information of the upper constructor.
120 * - hasExtends: A flag which shows whether own class has a valid `extends`
122 * - scope: The scope of own class.
123 * - codePath: The code path object of the constructor.
128 * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
129 * Information for each code path segment.
130 * - calledInSomePaths: A flag of be called `super()` in some code paths.
131 * - calledInEveryPaths: A flag of be called `super()` in all code paths.
134 let segInfoMap = Object.create(null);
137 * Gets the flag which shows `super()` is called in some paths.
138 * @param {CodePathSegment} segment A code path segment to get.
139 * @returns {boolean} The flag which shows `super()` is called in some paths
141 function isCalledInSomePath(segment) {
142 return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
146 * Gets the flag which shows `super()` is called in all paths.
147 * @param {CodePathSegment} segment A code path segment to get.
148 * @returns {boolean} The flag which shows `super()` is called in all paths.
150 function isCalledInEveryPath(segment) {
153 * If specific segment is the looped segment of the current segment,
155 * If not skipped, this never becomes true after a loop.
157 if (segment.nextSegments.length === 1 &&
158 segment.nextSegments[0].isLoopedPrevSegment(segment)
162 return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
168 * Stacks a constructor information.
169 * @param {CodePath} codePath A code path which was started.
170 * @param {ASTNode} node The current node.
173 onCodePathStart(codePath, node) {
174 if (isConstructorFunction(node)) {
176 // Class > ClassBody > MethodDefinition > FunctionExpression
177 const classNode = node.parent.parent.parent;
178 const superClass = classNode.superClass;
183 hasExtends: Boolean(superClass),
184 superIsConstructor: isPossibleConstructor(superClass),
190 isConstructor: false,
192 superIsConstructor: false,
199 * Pops a constructor information.
200 * And reports if `super()` lacked.
201 * @param {CodePath} codePath A code path which was ended.
202 * @param {ASTNode} node The current node.
205 onCodePathEnd(codePath, node) {
206 const hasExtends = funcInfo.hasExtends;
209 funcInfo = funcInfo.upper;
215 // Reports if `super()` lacked.
216 const segments = codePath.returnedSegments;
217 const calledInEveryPaths = segments.every(isCalledInEveryPath);
218 const calledInSomePaths = segments.some(isCalledInSomePath);
220 if (!calledInEveryPaths) {
222 messageId: calledInSomePaths
231 * Initialize information of a given code path segment.
232 * @param {CodePathSegment} segment A code path segment to initialize.
235 onCodePathSegmentStart(segment) {
236 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
241 const info = segInfoMap[segment.id] = {
242 calledInSomePaths: false,
243 calledInEveryPaths: false,
247 // When there are previous segments, aggregates these.
248 const prevSegments = segment.prevSegments;
250 if (prevSegments.length > 0) {
251 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
252 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
257 * Update information of the code path segment when a code path was
259 * @param {CodePathSegment} fromSegment The code path segment of the
261 * @param {CodePathSegment} toSegment A code path segment of the head
265 onCodePathSegmentLoop(fromSegment, toSegment) {
266 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
270 // Update information inside of the loop.
271 const isRealLoop = toSegment.prevSegments.length >= 2;
273 funcInfo.codePath.traverseSegments(
274 { first: toSegment, last: fromSegment },
276 const info = segInfoMap[segment.id];
277 const prevSegments = segment.prevSegments;
280 info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
281 info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
283 // If flags become true anew, reports the valid nodes.
284 if (info.calledInSomePaths || isRealLoop) {
285 const nodes = info.validNodes;
287 info.validNodes = [];
289 for (let i = 0; i < nodes.length; ++i) {
290 const node = nodes[i];
293 messageId: "duplicate",
303 * Checks for a call of `super()`.
304 * @param {ASTNode} node A CallExpression node to check.
307 "CallExpression:exit"(node) {
308 if (!(funcInfo && funcInfo.isConstructor)) {
312 // Skips except `super()`.
313 if (node.callee.type !== "Super") {
317 // Reports if needed.
318 if (funcInfo.hasExtends) {
319 const segments = funcInfo.codePath.currentSegments;
320 let duplicate = false;
323 for (let i = 0; i < segments.length; ++i) {
324 const segment = segments[i];
326 if (segment.reachable) {
327 info = segInfoMap[segment.id];
329 duplicate = duplicate || info.calledInSomePaths;
330 info.calledInSomePaths = info.calledInEveryPaths = true;
337 messageId: "duplicate",
340 } else if (!funcInfo.superIsConstructor) {
342 messageId: "badSuper",
346 info.validNodes.push(node);
349 } else if (funcInfo.codePath.currentSegments.some(isReachable)) {
351 messageId: "unexpected",
358 * Set the mark to the returned path as `super()` was called.
359 * @param {ASTNode} node A ReturnStatement node to check.
362 ReturnStatement(node) {
363 if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
367 // Skips if no argument.
368 if (!node.argument) {
372 // Returning argument is a substitute of 'super()'.
373 const segments = funcInfo.codePath.currentSegments;
375 for (let i = 0; i < segments.length; ++i) {
376 const segment = segments[i];
378 if (segment.reachable) {
379 const info = segInfoMap[segment.id];
381 info.calledInSomePaths = info.calledInEveryPaths = true;
391 segInfoMap = Object.create(null);