@@ -60,34 +60,39 @@ const noRepeatedMemberAccess = createRule({
60
60
// Track which chains have already been reported to avoid duplicate reports
61
61
const reportedChains = new Set < string > ( ) ;
62
62
63
- type ScopeData = Map <
63
+ // We have got two map types, chainMap and scopeDataMap
64
+ // it works like: scopeDataMap -> chainMap -> chainInfo
65
+
66
+ // Stores info to decide if a extraction is necessary
67
+ type ChainMap = Map <
64
68
string ,
65
69
{
66
70
count : number ; // Number of times this chain is accessed
67
- node : TSESTree . MemberExpression ; // AST nodes where this chain appears
68
71
modified : boolean ; // Whether this chain is modified (written to)
69
72
}
70
73
> ;
71
- // Stores data for each scope using WeakMap to avoid memory leaks
72
- const scopeDataMap = new WeakMap < Scope . Scope , ScopeData > ( ) ;
74
+ // Stores mapping of scope to ChainMap
75
+ const scopeDataMap = new WeakMap < Scope . Scope , ChainMap > ( ) ;
73
76
74
- function getScopeData ( scope : Scope . Scope ) : ScopeData {
77
+ function getChainMap ( scope : Scope . Scope ) : ChainMap {
75
78
if ( ! scopeDataMap . has ( scope ) ) {
76
- // Create new scope data if not already present
77
- const newScopeData = new Map <
79
+ // Create new info map if not already present
80
+ const newChainMap = new Map <
78
81
string ,
79
82
{
80
83
count : number ;
81
- node : TSESTree . MemberExpression ;
82
84
modified : boolean ;
83
85
}
84
86
> ( ) ;
85
- scopeDataMap . set ( scope , newScopeData ) ;
87
+ scopeDataMap . set ( scope , newChainMap ) ;
86
88
}
87
89
return scopeDataMap . get ( scope ) ! ;
88
90
}
89
91
90
- function analyzeChain ( node : TSESTree . MemberExpression ) {
92
+ // This function generates ["a", "a.b", "a.b.c"] from a.b.c
93
+ // We will further add [count, modified] info to them in ChainMap, and use them as an indication for extraction
94
+ // eslint-disable-next-line unicorn/consistent-function-scoping
95
+ function analyzeChain ( node : TSESTree . MemberExpression ) : string [ ] {
91
96
const properties : string [ ] = [ ] ; // AST is iterated in reverse order
92
97
let current : TSESTree . Node = node ; // Current node in traversal
93
98
@@ -138,11 +143,10 @@ const noRepeatedMemberAccess = createRule({
138
143
139
144
function setModifiedFlag ( chain : string , node : TSESTree . Node ) {
140
145
const scope = sourceCode . getScope ( node ) ;
141
- const scopeData = getScopeData ( scope ) ;
146
+ const scopeData = getChainMap ( scope ) ;
142
147
143
148
for ( const [ existingChain , record ] of scopeData ) {
144
- // Check if the existing chain starts with the modified chain followed by a dot or bracket
145
- // This handles cases where modifying "a.b" should invalidate "a.b.c", "a.b.d", etc.
149
+ // Check if the existing chain starts with the modified chain followed by a dot or bracket, and if so, marks them as modified
146
150
if (
147
151
existingChain === chain ||
148
152
existingChain . startsWith ( chain + "." ) ||
@@ -154,7 +158,6 @@ const noRepeatedMemberAccess = createRule({
154
158
if ( ! scopeData . has ( chain ) ) {
155
159
scopeData . set ( chain , {
156
160
count : 0 ,
157
- node : node as TSESTree . MemberExpression , // to do: check this conversion!!
158
161
modified : true ,
159
162
} ) ;
160
163
}
@@ -166,20 +169,43 @@ const noRepeatedMemberAccess = createRule({
166
169
// not the sub-expressions a.b or a
167
170
if ( node . parent ?. type === AST_NODE_TYPES . MemberExpression ) return ;
168
171
169
- const scope = sourceCode . getScope ( node ) ;
170
- const scopeData = getScopeData ( scope ) ;
171
-
172
172
const chainInfo = analyzeChain ( node ) ;
173
173
if ( ! chainInfo ) return ;
174
174
175
- const longestValidChain = chainInfo [ - 1 ] ;
176
- const record = scopeData . get ( longestValidChain ) ! ;
177
- if (
178
- record . count >= minOccurrences &&
179
- ! reportedChains . has ( longestValidChain )
180
- ) {
175
+ const scope = sourceCode . getScope ( node ) ;
176
+ const infoMap = getChainMap ( scope ) ;
177
+
178
+ // keeps record of the longest valid chain, and only report it instead of shorter ones (to avoid repeated reports)
179
+ let longestValidChain = "" ;
180
+
181
+ // Update chain statistics for each part of the hierarchy
182
+ for ( const chain of chainInfo ) {
183
+ // Skip single-level chains
184
+ if ( ! chain . includes ( "." ) ) continue ;
185
+
186
+ const record = infoMap . get ( chain ) || {
187
+ count : 0 ,
188
+ modified : false ,
189
+ } ;
190
+ if ( record . modified ) break ;
191
+
192
+ record . count ++ ;
193
+ infoMap . set ( chain , record ) ;
194
+
195
+ // record longest extractable chain
196
+ if (
197
+ record . count >= minOccurrences &&
198
+ chain . length > longestValidChain . length
199
+ ) {
200
+ longestValidChain = chain ;
201
+ }
202
+ }
203
+
204
+ // report the longest chain
205
+ if ( longestValidChain && ! reportedChains . has ( longestValidChain ) ) {
206
+ const record = infoMap . get ( longestValidChain ) ! ;
181
207
context . report ( {
182
- node : record . node ,
208
+ node : node ,
183
209
messageId : "repeatedAccess" ,
184
210
data : { chain : longestValidChain , count : record . count } ,
185
211
} ) ;
@@ -194,10 +220,8 @@ const noRepeatedMemberAccess = createRule({
194
220
AssignmentExpression : ( node ) => {
195
221
if ( node . left . type === AST_NODE_TYPES . MemberExpression ) {
196
222
const chainInfo = analyzeChain ( node . left ) ;
197
- if ( chainInfo ) {
198
- for ( const chain of chainInfo ) {
199
- setModifiedFlag ( chain , node ) ;
200
- }
223
+ for ( const chain of chainInfo ) {
224
+ setModifiedFlag ( chain , node ) ;
201
225
}
202
226
}
203
227
} ,
@@ -207,10 +231,8 @@ const noRepeatedMemberAccess = createRule({
207
231
UpdateExpression : ( node ) => {
208
232
if ( node . argument . type === AST_NODE_TYPES . MemberExpression ) {
209
233
const chainInfo = analyzeChain ( node . argument ) ;
210
- if ( chainInfo ) {
211
- for ( const chain of chainInfo ) {
212
- setModifiedFlag ( chain , node ) ;
213
- }
234
+ for ( const chain of chainInfo ) {
235
+ setModifiedFlag ( chain , node ) ;
214
236
}
215
237
}
216
238
} ,
@@ -220,10 +242,8 @@ const noRepeatedMemberAccess = createRule({
220
242
CallExpression : ( node ) => {
221
243
if ( node . callee . type === AST_NODE_TYPES . MemberExpression ) {
222
244
const chainInfo = analyzeChain ( node . callee ) ;
223
- if ( chainInfo ) {
224
- for ( const chain of chainInfo ) {
225
- setModifiedFlag ( chain , node ) ;
226
- }
245
+ for ( const chain of chainInfo ) {
246
+ setModifiedFlag ( chain , node ) ;
227
247
}
228
248
}
229
249
} ,
0 commit comments