@@ -60,45 +60,35 @@ 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 = {
64
- chains : Map <
65
- string ,
66
- {
67
- count : number ; // Number of times this chain is accessed
68
- nodes : TSESTree . MemberExpression [ ] ; // AST nodes where this chain appears
69
- modified : boolean ; // Whether this chain is modified (written to)
70
- }
71
- > ;
72
- } ;
73
-
63
+ type ScopeData = Map <
64
+ string ,
65
+ {
66
+ count : number ; // Number of times this chain is accessed
67
+ node : TSESTree . MemberExpression ; // AST nodes where this chain appears
68
+ modified : boolean ; // Whether this chain is modified (written to)
69
+ }
70
+ > ;
74
71
// Stores data for each scope using WeakMap to avoid memory leaks
75
72
const scopeDataMap = new WeakMap < Scope . Scope , ScopeData > ( ) ;
76
73
77
74
function getScopeData ( scope : Scope . Scope ) : ScopeData {
78
- // Creates new scope data if none exists
79
- // Example: First time seeing the function foo() { obj.prop.val; }
80
- // we create a new ScopeData for this function
81
75
if ( ! scopeDataMap . has ( scope ) ) {
82
- // Create new scope data
83
- const newScopeData = {
84
- chains : new Map <
85
- string ,
86
- {
87
- count : number ;
88
- nodes : TSESTree . MemberExpression [ ] ;
89
- modified : boolean ;
90
- }
91
- > ( ) ,
92
- } ;
93
-
76
+ // Create new scope data if not already present
77
+ const newScopeData = new Map <
78
+ string ,
79
+ {
80
+ count : number ;
81
+ node : TSESTree . MemberExpression ;
82
+ modified : boolean ;
83
+ }
84
+ > ( ) ;
94
85
scopeDataMap . set ( scope , newScopeData ) ;
95
86
}
96
-
97
87
return scopeDataMap . get ( scope ) ! ;
98
88
}
99
89
100
90
function analyzeChain ( node : TSESTree . MemberExpression ) {
101
- const parts : string [ ] = [ ] ; // AST is iterated in reverse order
91
+ const properties : string [ ] = [ ] ; // AST is iterated in reverse order
102
92
let current : TSESTree . Node = node ; // Current node in traversal
103
93
104
94
// Collect property chain (reverse order)
@@ -109,7 +99,7 @@ const noRepeatedMemberAccess = createRule({
109
99
break ;
110
100
} else {
111
101
// Handle dot notation like obj.prop
112
- parts . push ( current . property . name ) ;
102
+ properties . push ( current . property . name ) ;
113
103
}
114
104
115
105
current = current . object ; // Move to parent object
@@ -123,33 +113,34 @@ const noRepeatedMemberAccess = createRule({
123
113
// Handle base object (the root of the chain)
124
114
// Example: For a.b.c, the base object is "a"
125
115
if ( current . type === AST_NODE_TYPES . Identifier ) {
126
- parts . push ( current . name ) ; // Add base object name
116
+ properties . push ( current . name ) ; // Add base object name
127
117
} else if ( current . type === AST_NODE_TYPES . ThisExpression ) {
128
- parts . push ( "this" ) ;
118
+ properties . push ( "this" ) ;
129
119
} // ignore other patterns
130
120
131
121
// Generate hierarchy chain (forward order)
132
- // Example: For parts ["c", "b", "a"], we reverse to ["a", "b", "c"]
133
- // and build hierarchy ["a", "a.b", "a.b.c"]
134
- parts . reverse ( ) ;
122
+ // Example:
123
+ // Input is "a.b.c"
124
+ // For property ["c", "b", "a"], we reverse it to ["a", "b", "c"]
125
+ properties . reverse ( ) ;
135
126
127
+ // and build chain of object ["a", "a.b", "a.b.c"]
136
128
const result : string [ ] = [ ] ;
137
129
let currentChain = "" ;
138
- for ( let i = 0 ; i < parts . length ; i ++ ) {
139
- currentChain = i === 0 ? parts [ 0 ] : `${ currentChain } .${ parts [ i ] } ` ;
130
+ for ( let i = 0 ; i < properties . length ; i ++ ) {
131
+ currentChain =
132
+ i === 0 ? properties [ 0 ] : `${ currentChain } .${ properties [ i ] } ` ;
140
133
result . push ( currentChain ) ;
141
134
}
142
135
143
136
return result ;
144
137
}
145
138
146
- function trackModification ( chain : string , node : TSESTree . Node ) {
139
+ function setModifiedFlag ( chain : string , node : TSESTree . Node ) {
147
140
const scope = sourceCode . getScope ( node ) ;
148
141
const scopeData = getScopeData ( scope ) ;
149
142
150
- // Mark the modified chain and all its sub-chains as modified
151
- // Example: If "a.b" is modified, then "a.b.c", "a.b.c.d" etc. should also be considered invalid
152
- for ( const [ existingChain , record ] of scopeData . chains ) {
143
+ for ( const [ existingChain , record ] of scopeData ) {
153
144
// Check if the existing chain starts with the modified chain followed by a dot or bracket
154
145
// This handles cases where modifying "a.b" should invalidate "a.b.c", "a.b.d", etc.
155
146
if (
@@ -160,12 +151,10 @@ const noRepeatedMemberAccess = createRule({
160
151
record . modified = true ;
161
152
}
162
153
}
163
- if ( scopeData . chains . has ( chain ) ) {
164
- scopeData . chains . get ( chain ) ! . modified = true ;
165
- } else {
166
- scopeData . chains . set ( chain , {
154
+ if ( ! scopeData . has ( chain ) ) {
155
+ scopeData . set ( chain , {
167
156
count : 0 ,
168
- nodes : [ ] ,
157
+ node : node as TSESTree . MemberExpression , // to do: check this conversion!!
169
158
modified : true ,
170
159
} ) ;
171
160
}
@@ -177,45 +166,20 @@ const noRepeatedMemberAccess = createRule({
177
166
// not the sub-expressions a.b or a
178
167
if ( node . parent ?. type === AST_NODE_TYPES . MemberExpression ) return ;
179
168
180
- const chainInfo = analyzeChain ( node ) ;
181
- if ( ! chainInfo ) return ;
182
-
183
169
const scope = sourceCode . getScope ( node ) ;
184
170
const scopeData = getScopeData ( scope ) ;
185
171
186
- // keeps record of the longest valid chain, and only report it instead of shorter ones (to avoid repeated reports)
187
- let longestValidChain = "" ;
188
-
189
- // Update chain statistics for each part of the hierarchy
190
- for ( const chain of chainInfo ) {
191
- // Skip single-level chains
192
- if ( ! chain . includes ( "." ) ) continue ;
193
-
194
- const record = scopeData . chains . get ( chain ) || {
195
- count : 0 ,
196
- nodes : [ ] ,
197
- modified : false ,
198
- } ;
199
- if ( record . modified ) continue ;
200
-
201
- record . count ++ ;
202
- record . nodes . push ( node ) ;
203
- scopeData . chains . set ( chain , record ) ;
204
-
205
- // record longest chain
206
- if (
207
- record . count >= minOccurrences &&
208
- chain . length > longestValidChain . length
209
- ) {
210
- longestValidChain = chain ;
211
- }
212
- }
172
+ const chainInfo = analyzeChain ( node ) ;
173
+ if ( ! chainInfo ) return ;
213
174
214
- // report the longest chain
215
- if ( longestValidChain && ! reportedChains . has ( longestValidChain ) ) {
216
- const record = scopeData . chains . get ( longestValidChain ) ! ;
175
+ const longestValidChain = chainInfo [ - 1 ] ;
176
+ const record = scopeData . get ( longestValidChain ) ! ;
177
+ if (
178
+ record . count >= minOccurrences &&
179
+ ! reportedChains . has ( longestValidChain )
180
+ ) {
217
181
context . report ( {
218
- node : record . nodes [ 0 ] ,
182
+ node : record . node ,
219
183
messageId : "repeatedAccess" ,
220
184
data : { chain : longestValidChain , count : record . count } ,
221
185
} ) ;
@@ -232,7 +196,7 @@ const noRepeatedMemberAccess = createRule({
232
196
const chainInfo = analyzeChain ( node . left ) ;
233
197
if ( chainInfo ) {
234
198
for ( const chain of chainInfo ) {
235
- trackModification ( chain , node ) ;
199
+ setModifiedFlag ( chain , node ) ;
236
200
}
237
201
}
238
202
}
@@ -245,7 +209,7 @@ const noRepeatedMemberAccess = createRule({
245
209
const chainInfo = analyzeChain ( node . argument ) ;
246
210
if ( chainInfo ) {
247
211
for ( const chain of chainInfo ) {
248
- trackModification ( chain , node ) ;
212
+ setModifiedFlag ( chain , node ) ;
249
213
}
250
214
}
251
215
}
@@ -258,7 +222,7 @@ const noRepeatedMemberAccess = createRule({
258
222
const chainInfo = analyzeChain ( node . callee ) ;
259
223
if ( chainInfo ) {
260
224
for ( const chain of chainInfo ) {
261
- trackModification ( chain , node ) ;
225
+ setModifiedFlag ( chain , node ) ;
262
226
}
263
227
}
264
228
}
0 commit comments