Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1c921c6

Browse files
mysticateailyavolodin
authored andcommitted
New: add no-import-assign (fixes#12237) (#12252)
* New: add no-import-assign (fixes#12237)* add destructuring and for-in/of syntax* add mutation functions* add a more test* allow seal and preventExtensions* add a test
1 parent3be04fd commit1c921c6

File tree

5 files changed

+599
-0
lines changed

5 files changed

+599
-0
lines changed

‎docs/rules/no-import-assign.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#disallow assigning to imported bindings (no-import-assign)
2+
3+
The updates of imported bindings by ES Modules cause runtime errors.
4+
5+
##Rule Details
6+
7+
This rule warns the assignments, increments, and decrements of imported bindings.
8+
9+
Examples of**incorrect** code for this rule:
10+
11+
```js
12+
/*eslint no-import-assign: "error"*/
13+
14+
importmod, {named }from"./mod.mjs"
15+
import*asmod_nsfrom"./mod.mjs"
16+
17+
mod=1// ERROR: 'mod' is readonly.
18+
named=2// ERROR: 'named' is readonly.
19+
mod_ns.named=3// ERROR: the members of 'mod_ns' is readonly.
20+
mod_ns= {}// ERROR: 'mod_ns' is readonly.
21+
```
22+
23+
Examples of**correct** code for this rule:
24+
25+
```js
26+
/*eslint no-import-assign: "error"*/
27+
28+
importmod, {named }from"./mod.mjs"
29+
import*asmod_nsfrom"./mod.mjs"
30+
31+
mod.prop=1
32+
named.prop=2
33+
mod_ns.named.prop=3
34+
35+
// Known Limitation
36+
functiontest(obj) {
37+
obj.named=4// Not errored because 'obj' is not namespace objects.
38+
}
39+
test(mod_ns)// Not errored because it doesn't know that 'test' updates the member of the argument.
40+
```
41+
42+
##When Not To Use It
43+
44+
If you don't want to be notified about modifying imported bindings, you can disable this rule.

‎lib/rules/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
132132
"no-implicit-coercion":()=>require("./no-implicit-coercion"),
133133
"no-implicit-globals":()=>require("./no-implicit-globals"),
134134
"no-implied-eval":()=>require("./no-implied-eval"),
135+
"no-import-assign":()=>require("./no-import-assign"),
135136
"no-inline-comments":()=>require("./no-inline-comments"),
136137
"no-inner-declarations":()=>require("./no-inner-declarations"),
137138
"no-invalid-regexp":()=>require("./no-invalid-regexp"),

‎lib/rules/no-import-assign.js‎

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/**
2+
*@fileoverview Rule to flag updates of imported bindings.
3+
*@author Toru Nagashima <https://github.com/mysticatea>
4+
*/
5+
6+
"use strict";
7+
8+
//------------------------------------------------------------------------------
9+
// Helpers
10+
//------------------------------------------------------------------------------
11+
12+
const{ findVariable, getPropertyName}=require("eslint-utils");
13+
14+
constMutationMethods={
15+
Object:newSet([
16+
"assign","defineProperties","defineProperty","freeze",
17+
"setPrototypeOf"
18+
]),
19+
Reflect:newSet([
20+
"defineProperty","deleteProperty","set","setPrototypeOf"
21+
])
22+
};
23+
24+
/**
25+
* Check if a given node is LHS of an assignment node.
26+
*@param {ASTNode} node The node to check.
27+
*@returns {boolean} `true` if the node is LHS.
28+
*/
29+
functionisAssignmentLeft(node){
30+
const{ parent}=node;
31+
32+
return(
33+
(
34+
parent.type==="AssignmentExpression"&&
35+
parent.left===node
36+
)||
37+
38+
// Destructuring assignments
39+
parent.type==="ArrayPattern"||
40+
(
41+
parent.type==="Property"&&
42+
parent.value===node&&
43+
parent.parent.type==="ObjectPattern"
44+
)||
45+
parent.type==="RestElement"||
46+
(
47+
parent.type==="AssignmentPattern"&&
48+
parent.left===node
49+
)
50+
);
51+
}
52+
53+
/**
54+
* Check if a given node is the operand of mutation unary operator.
55+
*@param {ASTNode} node The node to check.
56+
*@returns {boolean} `true` if the node is the operand of mutation unary operator.
57+
*/
58+
functionisOperandOfMutationUnaryOperator(node){
59+
const{ parent}=node;
60+
61+
return(
62+
(
63+
parent.type==="UpdateExpression"&&
64+
parent.argument===node
65+
)||
66+
(
67+
parent.type==="UnaryExpression"&&
68+
parent.operator==="delete"&&
69+
parent.argument===node
70+
)
71+
);
72+
}
73+
74+
/**
75+
* Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
76+
*@param {ASTNode} node The node to check.
77+
*@returns {boolean} `true` if the node is the iteration variable.
78+
*/
79+
functionisIterationVariable(node){
80+
const{ parent}=node;
81+
82+
return(
83+
(
84+
parent.type==="ForInStatement"&&
85+
parent.left===node
86+
)||
87+
(
88+
parent.type==="ForOfStatement"&&
89+
parent.left===node
90+
)
91+
);
92+
}
93+
94+
/**
95+
* Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
96+
*@param {ASTNode} node The node to check.
97+
*@param {Scope} scope A `escope.Scope` object to find variable (whichever).
98+
*@returns {boolean} `true` if the node is the iteration variable.
99+
*/
100+
functionisArgumentOfWellKnownMutationFunction(node,scope){
101+
const{ parent}=node;
102+
103+
if(
104+
parent.type==="CallExpression"&&
105+
parent.arguments[0]===node&&
106+
parent.callee.type==="MemberExpression"&&
107+
parent.callee.object.type==="Identifier"
108+
){
109+
const{ callee}=parent;
110+
const{ object}=callee;
111+
112+
if(Object.keys(MutationMethods).includes(object.name)){
113+
constvariable=findVariable(scope,object);
114+
115+
return(
116+
variable!==null&&
117+
variable.scope.type==="global"&&
118+
MutationMethods[object.name].has(getPropertyName(callee,scope))
119+
);
120+
}
121+
}
122+
123+
returnfalse;
124+
}
125+
126+
/**
127+
* Check if the identifier node is placed at to update members.
128+
*@param {ASTNode} id The Identifier node to check.
129+
*@param {Scope} scope A `escope.Scope` object to find variable (whichever).
130+
*@returns {boolean} `true` if the member of `id` was updated.
131+
*/
132+
functionisMemberWrite(id,scope){
133+
const{ parent}=id;
134+
135+
return(
136+
(
137+
parent.type==="MemberExpression"&&
138+
parent.object===id&&
139+
(
140+
isAssignmentLeft(parent)||
141+
isOperandOfMutationUnaryOperator(parent)||
142+
isIterationVariable(parent)
143+
)
144+
)||
145+
isArgumentOfWellKnownMutationFunction(id,scope)
146+
);
147+
}
148+
149+
/**
150+
* Get the mutation node.
151+
*@param {ASTNode} id The Identifier node to get.
152+
*@returns {ASTNode} The mutation node.
153+
*/
154+
functiongetWriteNode(id){
155+
letnode=id.parent;
156+
157+
while(
158+
node&&
159+
node.type!=="AssignmentExpression"&&
160+
node.type!=="UpdateExpression"&&
161+
node.type!=="UnaryExpression"&&
162+
node.type!=="CallExpression"&&
163+
node.type!=="ForInStatement"&&
164+
node.type!=="ForOfStatement"
165+
){
166+
node=node.parent;
167+
}
168+
169+
returnnode||id;
170+
}
171+
172+
//------------------------------------------------------------------------------
173+
// Rule Definition
174+
//------------------------------------------------------------------------------
175+
176+
module.exports={
177+
meta:{
178+
type:"problem",
179+
180+
docs:{
181+
description:"disallow assigning to imported bindings",
182+
category:"Possible Errors",
183+
recommended:false,
184+
url:"https://eslint.org/docs/rules/no-import-assign"
185+
},
186+
187+
schema:[],
188+
189+
messages:{
190+
readonly:"'{{name}}' is read-only.",
191+
readonlyMember:"The members of '{{name}}' are read-only."
192+
}
193+
},
194+
195+
create(context){
196+
return{
197+
ImportDeclaration(node){
198+
constscope=context.getScope();
199+
200+
for(constvariableofcontext.getDeclaredVariables(node)){
201+
constshouldCheckMembers=variable.defs.some(
202+
d=>d.node.type==="ImportNamespaceSpecifier"
203+
);
204+
letprevIdNode=null;
205+
206+
for(constreferenceofvariable.references){
207+
constidNode=reference.identifier;
208+
209+
/*
210+
* AssignmentPattern (e.g. `[a = 0] = b`) makes two write
211+
* references for the same identifier. This should skip
212+
* the one of the two in order to prevent redundant reports.
213+
*/
214+
if(idNode===prevIdNode){
215+
continue;
216+
}
217+
prevIdNode=idNode;
218+
219+
if(reference.isWrite()){
220+
context.report({
221+
node:getWriteNode(idNode),
222+
messageId:"readonly",
223+
data:{name:idNode.name}
224+
});
225+
}elseif(shouldCheckMembers&&isMemberWrite(idNode,scope)){
226+
context.report({
227+
node:getWriteNode(idNode),
228+
messageId:"readonlyMember",
229+
data:{name:idNode.name}
230+
});
231+
}
232+
}
233+
}
234+
}
235+
};
236+
237+
}
238+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp