|                                                                                                                                                                                                                                                                                                                                                                                                                                                              |  | /** * @fileoverview A rule to verify `super()` callings in constructor. * @author Toru Nagashima */
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/** * Checks all segments in a set and returns true if any are reachable. * @param {Set<CodePathSegment>} segments The segments to check. * @returns {boolean} True if any segment is reachable; false otherwise. */function isAnySegmentReachable(segments) {
    for (const segment of segments) {        if (segment.reachable) {            return true;        }    }
    return false;}
/** * Checks whether or not a given node is a constructor. * @param {ASTNode} node A node to check. This node type is one of *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and *   `ArrowFunctionExpression`. * @returns {boolean} `true` if the node is a constructor. */function isConstructorFunction(node) {    return (        node.type === "FunctionExpression" &&        node.parent.type === "MethodDefinition" &&        node.parent.kind === "constructor"    );}
/** * Checks whether a given node can be a constructor or not. * @param {ASTNode} node A node to check. * @returns {boolean} `true` if the node can be a constructor. */function isPossibleConstructor(node) {    if (!node) {        return false;    }
    switch (node.type) {        case "ClassExpression":        case "FunctionExpression":        case "ThisExpression":        case "MemberExpression":        case "CallExpression":        case "NewExpression":        case "ChainExpression":        case "YieldExpression":        case "TaggedTemplateExpression":        case "MetaProperty":            return true;
        case "Identifier":            return node.name !== "undefined";
        case "AssignmentExpression":            if (["=", "&&="].includes(node.operator)) {                return isPossibleConstructor(node.right);            }
            if (["||=", "??="].includes(node.operator)) {                return (                    isPossibleConstructor(node.left) ||                    isPossibleConstructor(node.right)                );            }
            /**             * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).             * An assignment expression with a mathematical operator can either evaluate to a primitive value,             * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.             */            return false;
        case "LogicalExpression":
            /*             * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if             * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a             * possible constructor. A future improvement could verify that the left side could be truthy by             * excluding falsy literals.             */            if (node.operator === "&&") {                return isPossibleConstructor(node.right);            }
            return (                isPossibleConstructor(node.left) ||                isPossibleConstructor(node.right)            );
        case "ConditionalExpression":            return (                isPossibleConstructor(node.alternate) ||                isPossibleConstructor(node.consequent)            );
        case "SequenceExpression": {            const lastExpression = node.expressions[node.expressions.length - 1];
            return isPossibleConstructor(lastExpression);        }
        default:            return false;    }}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "problem",
        docs: {            description: "Require `super()` calls in constructors",            recommended: true,            url: "https://eslint.org/docs/latest/rules/constructor-super"        },
        schema: [],
        messages: {            missingSome: "Lacked a call of 'super()' in some code paths.",            missingAll: "Expected to call 'super()'.",
            duplicate: "Unexpected duplicate 'super()'.",            badSuper: "Unexpected 'super()' because 'super' is not a constructor.",            unexpected: "Unexpected 'super()'."        }    },
    create(context) {
        /*         * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}         * Information for each constructor.         * - upper:      Information of the upper constructor.         * - hasExtends: A flag which shows whether own class has a valid `extends`         *               part.         * - scope:      The scope of own class.         * - codePath:   The code path object of the constructor.         */        let funcInfo = null;
        /*         * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}         * Information for each code path segment.         * - calledInSomePaths:  A flag of be called `super()` in some code paths.         * - calledInEveryPaths: A flag of be called `super()` in all code paths.         * - validNodes:         */        let segInfoMap = Object.create(null);
        /**         * Gets the flag which shows `super()` is called in some paths.         * @param {CodePathSegment} segment A code path segment to get.         * @returns {boolean} The flag which shows `super()` is called in some paths         */        function isCalledInSomePath(segment) {            return segment.reachable && segInfoMap[segment.id].calledInSomePaths;        }
        /**         * Gets the flag which shows `super()` is called in all paths.         * @param {CodePathSegment} segment A code path segment to get.         * @returns {boolean} The flag which shows `super()` is called in all paths.         */        function isCalledInEveryPath(segment) {
            /*             * If specific segment is the looped segment of the current segment,             * skip the segment.             * If not skipped, this never becomes true after a loop.             */            if (segment.nextSegments.length === 1 &&                segment.nextSegments[0].isLoopedPrevSegment(segment)            ) {                return true;            }            return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;        }
        return {
            /**             * Stacks a constructor information.             * @param {CodePath} codePath A code path which was started.             * @param {ASTNode} node The current node.             * @returns {void}             */            onCodePathStart(codePath, node) {                if (isConstructorFunction(node)) {
                    // Class > ClassBody > MethodDefinition > FunctionExpression
                    const classNode = node.parent.parent.parent;                    const superClass = classNode.superClass;
                    funcInfo = {                        upper: funcInfo,                        isConstructor: true,                        hasExtends: Boolean(superClass),                        superIsConstructor: isPossibleConstructor(superClass),                        codePath,                        currentSegments: new Set()                    };                } else {                    funcInfo = {                        upper: funcInfo,                        isConstructor: false,                        hasExtends: false,                        superIsConstructor: false,                        codePath,                        currentSegments: new Set()                    };                }            },
            /**             * Pops a constructor information.             * And reports if `super()` lacked.             * @param {CodePath} codePath A code path which was ended.             * @param {ASTNode} node The current node.             * @returns {void}             */            onCodePathEnd(codePath, node) {                const hasExtends = funcInfo.hasExtends;
                // Pop.
                funcInfo = funcInfo.upper;
                if (!hasExtends) {                    return;                }
                // Reports if `super()` lacked.
                const segments = codePath.returnedSegments;                const calledInEveryPaths = segments.every(isCalledInEveryPath);                const calledInSomePaths = segments.some(isCalledInSomePath);
                if (!calledInEveryPaths) {                    context.report({                        messageId: calledInSomePaths                            ? "missingSome"                            : "missingAll",                        node: node.parent                    });                }            },
            /**             * Initialize information of a given code path segment.             * @param {CodePathSegment} segment A code path segment to initialize.             * @returns {void}             */            onCodePathSegmentStart(segment) {
                funcInfo.currentSegments.add(segment);
                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {                    return;                }
                // Initialize info.
                const info = segInfoMap[segment.id] = {                    calledInSomePaths: false,                    calledInEveryPaths: false,                    validNodes: []                };
                // When there are previous segments, aggregates these.
                const prevSegments = segment.prevSegments;
                if (prevSegments.length > 0) {                    info.calledInSomePaths = prevSegments.some(isCalledInSomePath);                    info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);                }            },
            onUnreachableCodePathSegmentStart(segment) {                funcInfo.currentSegments.add(segment);            },
            onUnreachableCodePathSegmentEnd(segment) {                funcInfo.currentSegments.delete(segment);            },
            onCodePathSegmentEnd(segment) {                funcInfo.currentSegments.delete(segment);            },
            /**             * Update information of the code path segment when a code path was             * looped.             * @param {CodePathSegment} fromSegment The code path segment of the             *      end of a loop.             * @param {CodePathSegment} toSegment A code path segment of the head             *      of a loop.             * @returns {void}             */            onCodePathSegmentLoop(fromSegment, toSegment) {                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {                    return;                }
                // Update information inside of the loop.
                const isRealLoop = toSegment.prevSegments.length >= 2;
                funcInfo.codePath.traverseSegments(                    { first: toSegment, last: fromSegment },                    segment => {                        const info = segInfoMap[segment.id];                        const prevSegments = segment.prevSegments;
                        // Updates flags.
                        info.calledInSomePaths = prevSegments.some(isCalledInSomePath);                        info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
                        // If flags become true anew, reports the valid nodes.
                        if (info.calledInSomePaths || isRealLoop) {                            const nodes = info.validNodes;
                            info.validNodes = [];
                            for (let i = 0; i < nodes.length; ++i) {                                const node = nodes[i];
                                context.report({                                    messageId: "duplicate",                                    node                                });                            }                        }                    }                );            },
            /**             * Checks for a call of `super()`.             * @param {ASTNode} node A CallExpression node to check.             * @returns {void}             */            "CallExpression:exit"(node) {                if (!(funcInfo && funcInfo.isConstructor)) {                    return;                }
                // Skips except `super()`.
                if (node.callee.type !== "Super") {                    return;                }
                // Reports if needed.
                if (funcInfo.hasExtends) {                    const segments = funcInfo.currentSegments;                    let duplicate = false;                    let info = null;
                    for (const segment of segments) {
                        if (segment.reachable) {                            info = segInfoMap[segment.id];
                            duplicate = duplicate || info.calledInSomePaths;                            info.calledInSomePaths = info.calledInEveryPaths = true;                        }                    }
                    if (info) {                        if (duplicate) {                            context.report({                                messageId: "duplicate",                                node                            });                        } else if (!funcInfo.superIsConstructor) {                            context.report({                                messageId: "badSuper",                                node                            });                        } else {                            info.validNodes.push(node);                        }                    }                } else if (isAnySegmentReachable(funcInfo.currentSegments)) {                    context.report({                        messageId: "unexpected",                        node                    });                }            },
            /**             * Set the mark to the returned path as `super()` was called.             * @param {ASTNode} node A ReturnStatement node to check.             * @returns {void}             */            ReturnStatement(node) {                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {                    return;                }
                // Skips if no argument.
                if (!node.argument) {                    return;                }
                // Returning argument is a substitute of 'super()'.
                const segments = funcInfo.currentSegments;
                for (const segment of segments) {
                    if (segment.reachable) {                        const info = segInfoMap[segment.id];
                        info.calledInSomePaths = info.calledInEveryPaths = true;                    }                }            },
            /**             * Resets state.             * @returns {void}             */            "Program:exit"() {                segInfoMap = Object.create(null);            }        };    }};
 |