/**
							 | 
						|
								 * @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);
							 | 
						|
								            }
							 | 
						|
								        };
							 | 
						|
								    }
							 | 
						|
								};
							 |