@@ -24,12 +24,51 @@ function createReferenceMap(scope, outReferenceMap = new Map()) {
2424 return outReferenceMap ;
2525}
2626
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+
2756/**
2857 * 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.
3060 * @returns {boolean } `true` if the variable is local to the given function, and is never referenced in a closure.
3161 */
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+
3372 const functionScope = variable . scope . variableScope ;
3473
3574 return variable . references . every ( reference =>
@@ -47,67 +86,64 @@ class SegmentInfo {
4786 * @returns {void }
4887 */
4988 initialize ( segment ) {
50- const outdatedReadVariables = new Set ( ) ;
51- const freshReadVariables = new Set ( ) ;
89+ const outdatedReadVariableNames = new Set ( ) ;
90+ const freshReadVariableNames = new Set ( ) ;
5291
5392 for ( const prevSegment of segment . prevSegments ) {
5493 const info = this . info . get ( prevSegment ) ;
5594
5695 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 ) ;
5998 }
6099 }
61100
62- this . info . set ( segment , { outdatedReadVariables , freshReadVariables } ) ;
101+ this . info . set ( segment , { outdatedReadVariableNames , freshReadVariableNames } ) ;
63102 }
64103
65104 /**
66105 * Mark a given variable as read on given segments.
67106 * @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.
69108 * @returns {void }
70109 */
71- markAsRead ( segments , variable ) {
110+ markAsRead ( segments , variableName ) {
72111 for ( const segment of segments ) {
73112 const info = this . info . get ( segment ) ;
74113
75114 if ( info ) {
76- info . freshReadVariables . add ( variable ) ;
115+ info . freshReadVariableNames . add ( variableName ) ;
77116 }
78117 }
79118 }
80119
81120 /**
82- * Move `freshReadVariables ` to `outdatedReadVariables `.
121+ * Move `freshReadVariableNames ` to `outdatedReadVariableNames `.
83122 * @param {PathSegment[] } segments The segments to process.
84123 * @returns {void }
85124 */
86125 makeOutdated ( segments ) {
87- const vars = new Set ( ) ;
88-
89126 for ( const segment of segments ) {
90127 const info = this . info . get ( segment ) ;
91128
92129 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 ( ) ;
96132 }
97133 }
98134 }
99135
100136 /**
101137 * Check if a given variable is outdated on the current segments.
102138 * @param {PathSegment[] } segments The current segments.
103- * @param {escope.Variable } variable The variable to check.
139+ * @param {string } variableName The variable name to check.
104140 * @returns {boolean } `true` if the variable is outdated on the segments.
105141 */
106- isOutdated ( segments , variable ) {
142+ isOutdated ( segments , variableName ) {
107143 for ( const segment of segments ) {
108144 const info = this . info . get ( segment ) ;
109145
110- if ( info && info . outdatedReadVariables . has ( variable ) ) {
146+ if ( info && info . outdatedReadVariableNames . has ( variableName ) ) {
111147 return true ;
112148 }
113149 }
@@ -140,69 +176,10 @@ module.exports = {
140176
141177 create ( context ) {
142178 const sourceCode = context . getSourceCode ( ) ;
143- const globalScope = context . getScope ( ) ;
144- const dummyVariables = new Map ( ) ;
145179 const assignmentReferences = new Map ( ) ;
146180 const segmentInfo = new SegmentInfo ( ) ;
147181 let stack = null ;
148182
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-
206183 return {
207184 onCodePathStart ( codePath ) {
208185 const scope = context . getScope ( ) ;
@@ -234,12 +211,14 @@ module.exports = {
234211 if ( ! reference ) {
235212 return ;
236213 }
237- const variable = getVariable ( reference ) ;
214+ const name = reference . identifier . name ;
215+ const variable = reference . resolved ;
238216 const writeExpr = getWriteExpr ( reference ) ;
217+ const isMemberAccess = reference . identifier . parent . type === "MemberExpression" ;
239218
240219 // Add a fresh read variable.
241220 if ( reference . isRead ( ) && ! ( writeExpr && writeExpr . parent . operator === "=" ) ) {
242- segmentInfo . markAsRead ( codePath . currentSegments , variable ) ;
221+ segmentInfo . markAsRead ( codePath . currentSegments , name ) ;
243222 }
244223
245224 /*
@@ -248,7 +227,7 @@ module.exports = {
248227 */
249228 if ( writeExpr &&
250229 writeExpr . parent . right === writeExpr && // ← exclude variable declarations.
251- ! isLocalVariableWithoutEscape ( variable )
230+ ! isLocalVariableWithoutEscape ( variable , isMemberAccess )
252231 ) {
253232 let refs = assignmentReferences . get ( writeExpr ) ;
254233
@@ -263,7 +242,7 @@ module.exports = {
263242
264243 /*
265244 * Verify assignments.
266- * If the reference exists in `outdatedReadVariables ` list, report it.
245+ * If the reference exists in `outdatedReadVariableNames ` list, report it.
267246 */
268247 ":expression:exit" ( node ) {
269248 const { codePath, referenceMap } = stack ;
@@ -285,9 +264,9 @@ module.exports = {
285264 assignmentReferences . delete ( node ) ;
286265
287266 for ( const reference of references ) {
288- const variable = getVariable ( reference ) ;
267+ const name = reference . identifier . name ;
289268
290- if ( segmentInfo . isOutdated ( codePath . currentSegments , variable ) ) {
269+ if ( segmentInfo . isOutdated ( codePath . currentSegments , name ) ) {
291270 context . report ( {
292271 node : node . parent ,
293272 messageId : "nonAtomicUpdate" ,
0 commit comments