]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/constructor-super.js
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);