@@ -24,12 +24,51 @@ function createReferenceMap(scope, outReferenceMap = new Map()) {
24
24
return outReferenceMap ;
25
25
}
26
26
27
+ /**
28
+ * Get `reference.writeExpr` of a given reference.
29
+ * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a`
30
+ * @param {escope.Reference } reference The reference to get.
31
+ * @returns {Expression|null } The `reference.writeExpr`.
32
+ */
33
+ function getWriteExpr ( reference ) {
34
+ if ( reference . writeExpr ) {
35
+ return reference . writeExpr ;
36
+ }
37
+ let node = reference . identifier ;
38
+
39
+ while ( node ) {
40
+ const t = node . parent . type ;
41
+
42
+ if ( t === "AssignmentExpression" && node . parent . left === node ) {
43
+ return node . parent . right ;
44
+ }
45
+ if ( t === "MemberExpression" && node . parent . object === node ) {
46
+ node = node . parent ;
47
+ continue ;
48
+ }
49
+
50
+ break ;
51
+ }
52
+
53
+ return null ;
54
+ }
55
+
27
56
/**
28
57
* Checks if an expression is a variable that can only be observed within the given function.
29
- * @param {escope.Variable } variable The variable to check
58
+ * @param {Variable|null } variable The variable to check
59
+ * @param {boolean } isMemberAccess If `true` then this is a member access.
30
60
* @returns {boolean } `true` if the variable is local to the given function, and is never referenced in a closure.
31
61
*/
32
- function isLocalVariableWithoutEscape ( variable ) {
62
+ function isLocalVariableWithoutEscape ( variable , isMemberAccess ) {
63
+ if ( ! variable ) {
64
+ return false ; // A global variable which was not defined.
65
+ }
66
+
67
+ // If the reference is a property access and the variable is a parameter, it handles the variable is not local.
68
+ if ( isMemberAccess && variable . defs . some ( d => d . type === "Parameter" ) ) {
69
+ return false ;
70
+ }
71
+
33
72
const functionScope = variable . scope . variableScope ;
34
73
35
74
return variable . references . every ( reference =>
@@ -47,67 +86,64 @@ class SegmentInfo {
47
86
* @returns {void }
48
87
*/
49
88
initialize ( segment ) {
50
- const outdatedReadVariables = new Set ( ) ;
51
- const freshReadVariables = new Set ( ) ;
89
+ const outdatedReadVariableNames = new Set ( ) ;
90
+ const freshReadVariableNames = new Set ( ) ;
52
91
53
92
for ( const prevSegment of segment . prevSegments ) {
54
93
const info = this . info . get ( prevSegment ) ;
55
94
56
95
if ( info ) {
57
- info . outdatedReadVariables . forEach ( Set . prototype . add , outdatedReadVariables ) ;
58
- info . freshReadVariables . forEach ( Set . prototype . add , freshReadVariables ) ;
96
+ info . outdatedReadVariableNames . forEach ( Set . prototype . add , outdatedReadVariableNames ) ;
97
+ info . freshReadVariableNames . forEach ( Set . prototype . add , freshReadVariableNames ) ;
59
98
}
60
99
}
61
100
62
- this . info . set ( segment , { outdatedReadVariables , freshReadVariables } ) ;
101
+ this . info . set ( segment , { outdatedReadVariableNames , freshReadVariableNames } ) ;
63
102
}
64
103
65
104
/**
66
105
* Mark a given variable as read on given segments.
67
106
* @param {PathSegment[] } segments The segments that it read the variable on.
68
- * @param {escope.Variable } variable The variable to be read.
107
+ * @param {string } variableName The variable name to be read.
69
108
* @returns {void }
70
109
*/
71
- markAsRead ( segments , variable ) {
110
+ markAsRead ( segments , variableName ) {
72
111
for ( const segment of segments ) {
73
112
const info = this . info . get ( segment ) ;
74
113
75
114
if ( info ) {
76
- info . freshReadVariables . add ( variable ) ;
115
+ info . freshReadVariableNames . add ( variableName ) ;
77
116
}
78
117
}
79
118
}
80
119
81
120
/**
82
- * Move `freshReadVariables ` to `outdatedReadVariables `.
121
+ * Move `freshReadVariableNames ` to `outdatedReadVariableNames `.
83
122
* @param {PathSegment[] } segments The segments to process.
84
123
* @returns {void }
85
124
*/
86
125
makeOutdated ( segments ) {
87
- const vars = new Set ( ) ;
88
-
89
126
for ( const segment of segments ) {
90
127
const info = this . info . get ( segment ) ;
91
128
92
129
if ( info ) {
93
- info . freshReadVariables . forEach ( Set . prototype . add , info . outdatedReadVariables ) ;
94
- info . freshReadVariables . forEach ( Set . prototype . add , vars ) ;
95
- info . freshReadVariables . clear ( ) ;
130
+ info . freshReadVariableNames . forEach ( Set . prototype . add , info . outdatedReadVariableNames ) ;
131
+ info . freshReadVariableNames . clear ( ) ;
96
132
}
97
133
}
98
134
}
99
135
100
136
/**
101
137
* Check if a given variable is outdated on the current segments.
102
138
* @param {PathSegment[] } segments The current segments.
103
- * @param {escope.Variable } variable The variable to check.
139
+ * @param {string } variableName The variable name to check.
104
140
* @returns {boolean } `true` if the variable is outdated on the segments.
105
141
*/
106
- isOutdated ( segments , variable ) {
142
+ isOutdated ( segments , variableName ) {
107
143
for ( const segment of segments ) {
108
144
const info = this . info . get ( segment ) ;
109
145
110
- if ( info && info . outdatedReadVariables . has ( variable ) ) {
146
+ if ( info && info . outdatedReadVariableNames . has ( variableName ) ) {
111
147
return true ;
112
148
}
113
149
}
@@ -140,69 +176,10 @@ module.exports = {
140
176
141
177
create ( context ) {
142
178
const sourceCode = context . getSourceCode ( ) ;
143
- const globalScope = context . getScope ( ) ;
144
- const dummyVariables = new Map ( ) ;
145
179
const assignmentReferences = new Map ( ) ;
146
180
const segmentInfo = new SegmentInfo ( ) ;
147
181
let stack = null ;
148
182
149
- /**
150
- * Get the variable of a given reference.
151
- * If it's not defined, returns a dummy object.
152
- * @param {escope.Reference } reference The reference to get.
153
- * @returns {escope.Variable } The variable of the reference.
154
- */
155
- function getVariable ( reference ) {
156
- if ( reference . resolved ) {
157
- return reference . resolved ;
158
- }
159
-
160
- // Get or create a dummy.
161
- const name = reference . identifier . name ;
162
- let variable = dummyVariables . get ( name ) ;
163
-
164
- if ( ! variable ) {
165
- variable = {
166
- name,
167
- scope : globalScope ,
168
- references : [ ]
169
- } ;
170
- dummyVariables . set ( name , variable ) ;
171
- }
172
- variable . references . push ( reference ) ;
173
-
174
- return variable ;
175
- }
176
-
177
- /**
178
- * Get `reference.writeExpr` of a given reference.
179
- * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a`
180
- * @param {escope.Reference } reference The reference to get.
181
- * @returns {Expression|null } The `reference.writeExpr`.
182
- */
183
- function getWriteExpr ( reference ) {
184
- if ( reference . writeExpr ) {
185
- return reference . writeExpr ;
186
- }
187
- let node = reference . identifier ;
188
-
189
- while ( node ) {
190
- const t = node . parent . type ;
191
-
192
- if ( t === "AssignmentExpression" && node . parent . left === node ) {
193
- return node . parent . right ;
194
- }
195
- if ( t === "MemberExpression" && node . parent . object === node ) {
196
- node = node . parent ;
197
- continue ;
198
- }
199
-
200
- break ;
201
- }
202
-
203
- return null ;
204
- }
205
-
206
183
return {
207
184
onCodePathStart ( codePath ) {
208
185
const scope = context . getScope ( ) ;
@@ -234,12 +211,14 @@ module.exports = {
234
211
if ( ! reference ) {
235
212
return ;
236
213
}
237
- const variable = getVariable ( reference ) ;
214
+ const name = reference . identifier . name ;
215
+ const variable = reference . resolved ;
238
216
const writeExpr = getWriteExpr ( reference ) ;
217
+ const isMemberAccess = reference . identifier . parent . type === "MemberExpression" ;
239
218
240
219
// Add a fresh read variable.
241
220
if ( reference . isRead ( ) && ! ( writeExpr && writeExpr . parent . operator === "=" ) ) {
242
- segmentInfo . markAsRead ( codePath . currentSegments , variable ) ;
221
+ segmentInfo . markAsRead ( codePath . currentSegments , name ) ;
243
222
}
244
223
245
224
/*
@@ -248,7 +227,7 @@ module.exports = {
248
227
*/
249
228
if ( writeExpr &&
250
229
writeExpr . parent . right === writeExpr && // ← exclude variable declarations.
251
- ! isLocalVariableWithoutEscape ( variable )
230
+ ! isLocalVariableWithoutEscape ( variable , isMemberAccess )
252
231
) {
253
232
let refs = assignmentReferences . get ( writeExpr ) ;
254
233
@@ -263,7 +242,7 @@ module.exports = {
263
242
264
243
/*
265
244
* Verify assignments.
266
- * If the reference exists in `outdatedReadVariables ` list, report it.
245
+ * If the reference exists in `outdatedReadVariableNames ` list, report it.
267
246
*/
268
247
":expression:exit" ( node ) {
269
248
const { codePath, referenceMap } = stack ;
@@ -285,9 +264,9 @@ module.exports = {
285
264
assignmentReferences . delete ( node ) ;
286
265
287
266
for ( const reference of references ) {
288
- const variable = getVariable ( reference ) ;
267
+ const name = reference . identifier . name ;
289
268
290
- if ( segmentInfo . isOutdated ( codePath . currentSegments , variable ) ) {
269
+ if ( segmentInfo . isOutdated ( codePath . currentSegments , name ) ) {
291
270
context . report ( {
292
271
node : node . parent ,
293
272
messageId : "nonAtomicUpdate" ,
0 commit comments