2 * @fileoverview Rule to flag updates of imported bindings.
3 * @author Toru Nagashima <https://github.com/mysticatea>
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const { findVariable
} = require("eslint-utils");
13 const astUtils
= require("./utils/ast-utils");
15 const WellKnownMutationFunctions
= {
16 Object
: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
17 Reflect
: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
21 * Check if a given node is LHS of an assignment node.
22 * @param {ASTNode} node The node to check.
23 * @returns {boolean} `true` if the node is LHS.
25 function isAssignmentLeft(node
) {
26 const { parent
} = node
;
30 parent
.type
=== "AssignmentExpression" &&
34 // Destructuring assignments
35 parent
.type
=== "ArrayPattern" ||
37 parent
.type
=== "Property" &&
38 parent
.value
=== node
&&
39 parent
.parent
.type
=== "ObjectPattern"
41 parent
.type
=== "RestElement" ||
43 parent
.type
=== "AssignmentPattern" &&
50 * Check if a given node is the operand of mutation unary operator.
51 * @param {ASTNode} node The node to check.
52 * @returns {boolean} `true` if the node is the operand of mutation unary operator.
54 function isOperandOfMutationUnaryOperator(node
) {
55 const argumentNode
= node
.parent
.type
=== "ChainExpression"
58 const { parent
} = argumentNode
;
62 parent
.type
=== "UpdateExpression" &&
63 parent
.argument
=== argumentNode
66 parent
.type
=== "UnaryExpression" &&
67 parent
.operator
=== "delete" &&
68 parent
.argument
=== argumentNode
74 * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
75 * @param {ASTNode} node The node to check.
76 * @returns {boolean} `true` if the node is the iteration variable.
78 function isIterationVariable(node
) {
79 const { parent
} = node
;
83 parent
.type
=== "ForInStatement" &&
87 parent
.type
=== "ForOfStatement" &&
94 * Check if a given node is at the first argument of a well-known mutation function.
96 * - `Object.defineProperty`
97 * - `Object.defineProperties`
99 * - `Object.setPrototypeOf`
100 * - `Reflect.defineProperty`
101 * - `Reflect.deleteProperty`
103 * - `Reflect.setPrototypeOf`
104 * @param {ASTNode} node The node to check.
105 * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
106 * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
108 function isArgumentOfWellKnownMutationFunction(node
, scope
) {
109 const { parent
} = node
;
111 if (parent
.type
!== "CallExpression" || parent
.arguments
[0] !== node
) {
114 const callee
= astUtils
.skipChainExpression(parent
.callee
);
117 !astUtils
.isSpecificMemberAccess(callee
, "Object", WellKnownMutationFunctions
.Object
) &&
118 !astUtils
.isSpecificMemberAccess(callee
, "Reflect", WellKnownMutationFunctions
.Reflect
)
122 const variable
= findVariable(scope
, callee
.object
);
124 return variable
!== null && variable
.scope
.type
=== "global";
128 * Check if the identifier node is placed at to update members.
129 * @param {ASTNode} id The Identifier node to check.
130 * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
131 * @returns {boolean} `true` if the member of `id` was updated.
133 function isMemberWrite(id
, scope
) {
134 const { parent
} = id
;
138 parent
.type
=== "MemberExpression" &&
139 parent
.object
=== id
&&
141 isAssignmentLeft(parent
) ||
142 isOperandOfMutationUnaryOperator(parent
) ||
143 isIterationVariable(parent
)
146 isArgumentOfWellKnownMutationFunction(id
, scope
)
151 * Get the mutation node.
152 * @param {ASTNode} id The Identifier node to get.
153 * @returns {ASTNode} The mutation node.
155 function getWriteNode(id
) {
156 let node
= id
.parent
;
160 node
.type
!== "AssignmentExpression" &&
161 node
.type
!== "UpdateExpression" &&
162 node
.type
!== "UnaryExpression" &&
163 node
.type
!== "CallExpression" &&
164 node
.type
!== "ForInStatement" &&
165 node
.type
!== "ForOfStatement"
173 //------------------------------------------------------------------------------
175 //------------------------------------------------------------------------------
182 description
: "disallow assigning to imported bindings",
183 category
: "Possible Errors",
185 url
: "https://eslint.org/docs/rules/no-import-assign"
191 readonly
: "'{{name}}' is read-only.",
192 readonlyMember
: "The members of '{{name}}' are read-only."
198 ImportDeclaration(node
) {
199 const scope
= context
.getScope();
201 for (const variable
of context
.getDeclaredVariables(node
)) {
202 const shouldCheckMembers
= variable
.defs
.some(
203 d
=> d
.node
.type
=== "ImportNamespaceSpecifier"
205 let prevIdNode
= null;
207 for (const reference
of variable
.references
) {
208 const idNode
= reference
.identifier
;
211 * AssignmentPattern (e.g. `[a = 0] = b`) makes two write
212 * references for the same identifier. This should skip
213 * the one of the two in order to prevent redundant reports.
215 if (idNode
=== prevIdNode
) {
220 if (reference
.isWrite()) {
222 node
: getWriteNode(idNode
),
223 messageId
: "readonly",
224 data
: { name
: idNode
.name
}
226 } else if (shouldCheckMembers
&& isMemberWrite(idNode
, scope
)) {
228 node
: getWriteNode(idNode
),
229 messageId
: "readonlyMember",
230 data
: { name
: idNode
.name
}