|                                                                                                                                                                                                                     |  | /** * @fileoverview Rule to flag unnecessary bind calls * @author Bence Dányi <bence@danyi.me> */"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]);
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Disallow unnecessary calls to `.bind()`",            recommended: false,            url: "https://eslint.org/docs/latest/rules/no-extra-bind"        },
        schema: [],        fixable: "code",
        messages: {            unexpected: "The function binding is unnecessary."        }    },
    create(context) {        const sourceCode = context.sourceCode;        let scopeInfo = null;
        /**         * Checks if a node is free of side effects.         *         * This check is stricter than it needs to be, in order to keep the implementation simple.         * @param {ASTNode} node A node to check.         * @returns {boolean} True if the node is known to be side-effect free, false otherwise.         */        function isSideEffectFree(node) {            return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type);        }
        /**         * Reports a given function node.         * @param {ASTNode} node A node to report. This is a FunctionExpression or         *      an ArrowFunctionExpression.         * @returns {void}         */        function report(node) {            const memberNode = node.parent;            const callNode = memberNode.parent.type === "ChainExpression"                ? memberNode.parent.parent                : memberNode.parent;
            context.report({                node: callNode,                messageId: "unexpected",                loc: memberNode.property.loc,
                fix(fixer) {                    if (!isSideEffectFree(callNode.arguments[0])) {                        return null;                    }
                    /*                     * The list of the first/last token pair of a removal range.                     * This is two parts because closing parentheses may exist between the method name and arguments.                     * E.g. `(function(){}.bind ) (obj)`                     *                    ^^^^^   ^^^^^ < removal ranges                     * E.g. `(function(){}?.['bind'] ) ?.(obj)`                     *                    ^^^^^^^^^^   ^^^^^^^ < removal ranges                     */                    const tokenPairs = [                        [
                            // `.`, `?.`, or `[` token.
                            sourceCode.getTokenAfter(                                memberNode.object,                                astUtils.isNotClosingParenToken                            ),
                            // property name or `]` token.
                            sourceCode.getLastToken(memberNode)                        ],                        [
                            // `?.` or `(` token of arguments.
                            sourceCode.getTokenAfter(                                memberNode,                                astUtils.isNotClosingParenToken                            ),
                            // `)` token of arguments.
                            sourceCode.getLastToken(callNode)                        ]                    ];                    const firstTokenToRemove = tokenPairs[0][0];                    const lastTokenToRemove = tokenPairs[1][1];
                    if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {                        return null;                    }
                    return tokenPairs.map(([start, end]) =>                        fixer.removeRange([start.range[0], end.range[1]]));                }            });        }
        /**         * Checks whether or not a given function node is the callee of `.bind()`         * method.         *         * e.g. `(function() {}.bind(foo))`         * @param {ASTNode} node A node to report. This is a FunctionExpression or         *      an ArrowFunctionExpression.         * @returns {boolean} `true` if the node is the callee of `.bind()` method.         */        function isCalleeOfBindMethod(node) {            if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {                return false;            }
            // The node of `*.bind` member access.
            const bindNode = node.parent.parent.type === "ChainExpression"                ? node.parent.parent                : node.parent;
            return (                bindNode.parent.type === "CallExpression" &&                bindNode.parent.callee === bindNode &&                bindNode.parent.arguments.length === 1 &&                bindNode.parent.arguments[0].type !== "SpreadElement"            );        }
        /**         * Adds a scope information object to the stack.         * @param {ASTNode} node A node to add. This node is a FunctionExpression         *      or a FunctionDeclaration node.         * @returns {void}         */        function enterFunction(node) {            scopeInfo = {                isBound: isCalleeOfBindMethod(node),                thisFound: false,                upper: scopeInfo            };        }
        /**         * Removes the scope information object from the top of the stack.         * At the same time, this reports the function node if the function has         * `.bind()` and the `this` keywords found.         * @param {ASTNode} node A node to remove. This node is a         *      FunctionExpression or a FunctionDeclaration node.         * @returns {void}         */        function exitFunction(node) {            if (scopeInfo.isBound && !scopeInfo.thisFound) {                report(node);            }
            scopeInfo = scopeInfo.upper;        }
        /**         * Reports a given arrow function if the function is callee of `.bind()`         * method.         * @param {ASTNode} node A node to report. This node is an         *      ArrowFunctionExpression.         * @returns {void}         */        function exitArrowFunction(node) {            if (isCalleeOfBindMethod(node)) {                report(node);            }        }
        /**         * Set the mark as the `this` keyword was found in this scope.         * @returns {void}         */        function markAsThisFound() {            if (scopeInfo) {                scopeInfo.thisFound = true;            }        }
        return {            "ArrowFunctionExpression:exit": exitArrowFunction,            FunctionDeclaration: enterFunction,            "FunctionDeclaration:exit": exitFunction,            FunctionExpression: enterFunction,            "FunctionExpression:exit": exitFunction,            ThisExpression: markAsThisFound        };    }};
 |