|                                                                                                                                                                                                                                                 |  | /** * @fileoverview Rule to flag updates of imported bindings. * @author Toru Nagashima <https://github.com/mysticatea>
 */
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const { findVariable } = require("@eslint-community/eslint-utils");const astUtils = require("./utils/ast-utils");
const WellKnownMutationFunctions = {    Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,    Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u};
/** * Check if a given node is LHS of an assignment node. * @param {ASTNode} node The node to check. * @returns {boolean} `true` if the node is LHS. */function isAssignmentLeft(node) {    const { parent } = node;
    return (        (            parent.type === "AssignmentExpression" &&            parent.left === node        ) ||
        // Destructuring assignments
        parent.type === "ArrayPattern" ||        (            parent.type === "Property" &&            parent.value === node &&            parent.parent.type === "ObjectPattern"        ) ||        parent.type === "RestElement" ||        (            parent.type === "AssignmentPattern" &&            parent.left === node        )    );}
/** * Check if a given node is the operand of mutation unary operator. * @param {ASTNode} node The node to check. * @returns {boolean} `true` if the node is the operand of mutation unary operator. */function isOperandOfMutationUnaryOperator(node) {    const argumentNode = node.parent.type === "ChainExpression"        ? node.parent        : node;    const { parent } = argumentNode;
    return (        (            parent.type === "UpdateExpression" &&            parent.argument === argumentNode        ) ||        (            parent.type === "UnaryExpression" &&            parent.operator === "delete" &&            parent.argument === argumentNode        )    );}
/** * Check if a given node is the iteration variable of `for-in`/`for-of` syntax. * @param {ASTNode} node The node to check. * @returns {boolean} `true` if the node is the iteration variable. */function isIterationVariable(node) {    const { parent } = node;
    return (        (            parent.type === "ForInStatement" &&            parent.left === node        ) ||        (            parent.type === "ForOfStatement" &&            parent.left === node        )    );}
/** * Check if a given node is at the first argument of a well-known mutation function. * - `Object.assign` * - `Object.defineProperty` * - `Object.defineProperties` * - `Object.freeze` * - `Object.setPrototypeOf` * - `Reflect.defineProperty` * - `Reflect.deleteProperty` * - `Reflect.set` * - `Reflect.setPrototypeOf` * @param {ASTNode} node The node to check. * @param {Scope} scope A `escope.Scope` object to find variable (whichever). * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function. */function isArgumentOfWellKnownMutationFunction(node, scope) {    const { parent } = node;
    if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {        return false;    }    const callee = astUtils.skipChainExpression(parent.callee);
    if (        !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&        !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)    ) {        return false;    }    const variable = findVariable(scope, callee.object);
    return variable !== null && variable.scope.type === "global";}
/** * Check if the identifier node is placed at to update members. * @param {ASTNode} id The Identifier node to check. * @param {Scope} scope A `escope.Scope` object to find variable (whichever). * @returns {boolean} `true` if the member of `id` was updated. */function isMemberWrite(id, scope) {    const { parent } = id;
    return (        (            parent.type === "MemberExpression" &&            parent.object === id &&            (                isAssignmentLeft(parent) ||                isOperandOfMutationUnaryOperator(parent) ||                isIterationVariable(parent)            )        ) ||        isArgumentOfWellKnownMutationFunction(id, scope)    );}
/** * Get the mutation node. * @param {ASTNode} id The Identifier node to get. * @returns {ASTNode} The mutation node. */function getWriteNode(id) {    let node = id.parent;
    while (        node &&        node.type !== "AssignmentExpression" &&        node.type !== "UpdateExpression" &&        node.type !== "UnaryExpression" &&        node.type !== "CallExpression" &&        node.type !== "ForInStatement" &&        node.type !== "ForOfStatement"    ) {        node = node.parent;    }
    return node || id;}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "problem",
        docs: {            description: "Disallow assigning to imported bindings",            recommended: true,            url: "https://eslint.org/docs/latest/rules/no-import-assign"        },
        schema: [],
        messages: {            readonly: "'{{name}}' is read-only.",            readonlyMember: "The members of '{{name}}' are read-only."        }    },
    create(context) {        const sourceCode = context.sourceCode;
        return {            ImportDeclaration(node) {                const scope = sourceCode.getScope(node);
                for (const variable of sourceCode.getDeclaredVariables(node)) {                    const shouldCheckMembers = variable.defs.some(                        d => d.node.type === "ImportNamespaceSpecifier"                    );                    let prevIdNode = null;
                    for (const reference of variable.references) {                        const idNode = reference.identifier;
                        /*                         * AssignmentPattern (e.g. `[a = 0] = b`) makes two write                         * references for the same identifier. This should skip                         * the one of the two in order to prevent redundant reports.                         */                        if (idNode === prevIdNode) {                            continue;                        }                        prevIdNode = idNode;
                        if (reference.isWrite()) {                            context.report({                                node: getWriteNode(idNode),                                messageId: "readonly",                                data: { name: idNode.name }                            });                        } else if (shouldCheckMembers && isMemberWrite(idNode, scope)) {                            context.report({                                node: getWriteNode(idNode),                                messageId: "readonlyMember",                                data: { name: idNode.name }                            });                        }                    }                }            }        };
    }};
 |