|                                                                                                                                                                                                                                                                                                                                           |  | /** * @fileoverview A rule to disallow using `this`/`super` before `super()`. * @author Toru Nagashima */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/** * 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"    );}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "problem",
        docs: {            description: "Disallow `this`/`super` before calling `super()` in constructors",            recommended: true,            url: "https://eslint.org/docs/latest/rules/no-this-before-super"        },
        schema: [],
        messages: {            noBeforeSuper: "'{{kind}}' is not allowed before 'super()'."        }    },
    create(context) {
        /*         * Information for each constructor.         * - upper:      Information of the upper constructor.         * - hasExtends: A flag which shows whether the owner class has a valid         *   `extends` part.         * - scope:      The scope of the owner class.         * - codePath:   The code path of this constructor.         */        let funcInfo = null;
        /*         * Information for each code path segment.         * Each key is the id of a code path segment.         * Each value is an object:         * - superCalled:  The flag which shows `super()` called in all code paths.         * - invalidNodes: The array of invalid ThisExpression and Super nodes.         */        let segInfoMap = Object.create(null);
        /**         * Gets whether or not `super()` is called in a given code path segment.         * @param {CodePathSegment} segment A code path segment to get.         * @returns {boolean} `true` if `super()` is called.         */        function isCalled(segment) {            return !segment.reachable || segInfoMap[segment.id].superCalled;        }
        /**         * Checks whether or not this is in a constructor.         * @returns {boolean} `true` if this is in a constructor.         */        function isInConstructorOfDerivedClass() {            return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);        }
        /**         * Determines if every segment in a set has been called.         * @param {Set<CodePathSegment>} segments The segments to search.         * @returns {boolean} True if every segment has been called; false otherwise.         */        function isEverySegmentCalled(segments) {            for (const segment of segments) {                if (!isCalled(segment)) {                    return false;                }            }
            return true;        }
        /**         * Checks whether or not this is before `super()` is called.         * @returns {boolean} `true` if this is before `super()` is called.         */        function isBeforeCallOfSuper() {            return (                isInConstructorOfDerivedClass() &&                !isEverySegmentCalled(funcInfo.currentSegments)            );        }
        /**         * Sets a given node as invalid.         * @param {ASTNode} node A node to set as invalid. This is one of         *      a ThisExpression and a Super.         * @returns {void}         */        function setInvalid(node) {            const segments = funcInfo.currentSegments;
            for (const segment of segments) {                if (segment.reachable) {                    segInfoMap[segment.id].invalidNodes.push(node);                }            }        }
        /**         * Sets the current segment as `super` was called.         * @returns {void}         */        function setSuperCalled() {            const segments = funcInfo.currentSegments;
            for (const segment of segments) {                if (segment.reachable) {                    segInfoMap[segment.id].superCalled = true;                }            }        }
        return {
            /**             * Adds information of a constructor into the stack.             * @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;
                    funcInfo = {                        upper: funcInfo,                        isConstructor: true,                        hasExtends: Boolean(                            classNode.superClass &&                            !astUtils.isNullOrUndefined(classNode.superClass)                        ),                        codePath,                        currentSegments: new Set()                    };                } else {                    funcInfo = {                        upper: funcInfo,                        isConstructor: false,                        hasExtends: false,                        codePath,                        currentSegments: new Set()                    };                }            },
            /**             * Removes the top of stack item.             *             * And this traverses all segments of this code path then reports every             * invalid node.             * @param {CodePath} codePath A code path which was ended.             * @returns {void}             */            onCodePathEnd(codePath) {                const isDerivedClass = funcInfo.hasExtends;
                funcInfo = funcInfo.upper;                if (!isDerivedClass) {                    return;                }
                codePath.traverseSegments((segment, controller) => {                    const info = segInfoMap[segment.id];
                    for (let i = 0; i < info.invalidNodes.length; ++i) {                        const invalidNode = info.invalidNodes[i];
                        context.report({                            messageId: "noBeforeSuper",                            node: invalidNode,                            data: {                                kind: invalidNode.type === "Super" ? "super" : "this"                            }                        });                    }
                    if (info.superCalled) {                        controller.skip();                    }                });            },
            /**             * 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 (!isInConstructorOfDerivedClass()) {                    return;                }
                // Initialize info.
                segInfoMap[segment.id] = {                    superCalled: (                        segment.prevSegments.length > 0 &&                        segment.prevSegments.every(isCalled)                    ),                    invalidNodes: []                };            },
            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 (!isInConstructorOfDerivedClass()) {                    return;                }
                // Update information inside of the loop.
                funcInfo.codePath.traverseSegments(                    { first: toSegment, last: fromSegment },                    (segment, controller) => {                        const info = segInfoMap[segment.id];
                        if (info.superCalled) {                            info.invalidNodes = [];                            controller.skip();                        } else if (                            segment.prevSegments.length > 0 &&                            segment.prevSegments.every(isCalled)                        ) {                            info.superCalled = true;                            info.invalidNodes = [];                        }                    }                );            },
            /**             * Reports if this is before `super()`.             * @param {ASTNode} node A target node.             * @returns {void}             */            ThisExpression(node) {                if (isBeforeCallOfSuper()) {                    setInvalid(node);                }            },
            /**             * Reports if this is before `super()`.             * @param {ASTNode} node A target node.             * @returns {void}             */            Super(node) {                if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {                    setInvalid(node);                }            },
            /**             * Marks `super()` called.             * @param {ASTNode} node A target node.             * @returns {void}             */            "CallExpression:exit"(node) {                if (node.callee.type === "Super" && isBeforeCallOfSuper()) {                    setSuperCalled();                }            },
            /**             * Resets state.             * @returns {void}             */            "Program:exit"() {                segInfoMap = Object.create(null);            }        };    }};
 |