@@ -97,38 +97,15 @@ const noRepeatedMemberAccess = createRule({
97
97
return scopeDataMap . get ( scope ) ! ;
98
98
}
99
99
100
- // This part analyzes and extracts member access chains from AST nodes
101
- //
102
- // Examples of chains:
103
- // - a.b.c → hierarchy: ["a", "a.b", "a.b.c"], fullChain: "a.b.c"
104
- // - foo["bar"].baz → hierarchy: ["foo", "foo[bar]", "foo[bar].baz"], fullChain: "foo[bar].baz"
105
- // - user.profile.settings.theme → hierarchy: ["user", "user.profile", "user.profile.settings"], fullChain: "user.profile.settings"
106
- interface ChainInfo {
107
- hierarchy : string [ ] ; // Chain hierarchy (e.g., ["a", "a.b", "a.b.c"])
108
- fullChain : string ; // Complete path (e.g., "a.b.c")
109
- }
110
- // Cache analyzed expressions to improve performance
111
- const chainCache = new WeakMap <
112
- TSESTree . MemberExpression ,
113
- ChainInfo | null
114
- > ( ) ;
115
-
116
- function analyzeChain ( node : TSESTree . MemberExpression ) : ChainInfo | null {
117
- // Check cache first for performance
118
- // Example: If we've already analyzed a.b.c in a previous call,
119
- // we return the cached result immediately
120
- if ( chainCache . has ( node ) ) return chainCache . get ( node ) ! ;
121
-
122
- const parts : string [ ] = [ ] ; // Stores parts of the chain in reverse order
100
+ function analyzeChain ( node : TSESTree . MemberExpression ) {
101
+ const parts : string [ ] = [ ] ; // AST is iterated in reverse order
123
102
let current : TSESTree . Node = node ; // Current node in traversal
124
- let isValid = true ; // Whether this chain is valid for optimization
125
103
126
104
// Collect property chain (reverse order)
127
105
// Example: For a.b.c, we'd collect ["c", "b", "a"] initially
128
106
while ( current . type === AST_NODE_TYPES . MemberExpression ) {
129
107
if ( current . computed ) {
130
108
// skip computed properties like obj["prop"] or arr[0] or obj[getKey()]
131
- isValid = false ;
132
109
break ;
133
110
} else {
134
111
// Handle dot notation like obj.prop
@@ -149,57 +126,23 @@ const noRepeatedMemberAccess = createRule({
149
126
parts . push ( current . name ) ; // Add base object name
150
127
} else if ( current . type === AST_NODE_TYPES . ThisExpression ) {
151
128
parts . push ( "this" ) ;
152
- } else {
153
- // Skip chains with non-identifier base objects
154
- // Example: (getObject()).prop is not optimized because function call results shouldn't be cached
155
- isValid = false ;
156
- }
157
-
158
- // Validate chain
159
- if ( ! isValid || parts . length < 2 ) {
160
- chainCache . set ( node , null ) ;
161
- return null ;
162
- }
129
+ } // ignore other patterns
163
130
164
131
// Generate hierarchy chain (forward order)
165
132
// Example: For parts ["c", "b", "a"], we reverse to ["a", "b", "c"]
166
133
// and build hierarchy ["a", "a.b", "a.b.c"]
167
134
parts . reverse ( ) ;
168
135
169
- const hierarchy : string [ ] = [ ] ;
136
+ const result : string [ ] = [ ] ;
137
+ let currentChain = "" ;
170
138
for ( let i = 0 ; i < parts . length ; i ++ ) {
171
- let chain ;
172
- // Create chain for each level
173
- // eslint-disable-next-line unicorn/prefer-ternary
174
- if ( i === 0 ) {
175
- // First element is used directly
176
- // Example: For a.b.c, first element is "a"
177
- chain = parts [ 0 ] ;
178
- } else {
179
- // Build based on previous element
180
- // Example: "a" + "." + "b" = "a.b"
181
- chain = hierarchy [ i - 1 ] + "." + parts [ i ] ;
182
- }
183
- hierarchy . push ( chain ) ;
139
+ currentChain = i === 0 ? parts [ 0 ] : `${ currentChain } .${ parts [ i ] } ` ;
140
+ result . push ( currentChain ) ;
184
141
}
185
142
186
- const result = {
187
- hierarchy : hierarchy ,
188
- fullChain : hierarchy . at ( - 1 ) ?? "" , // Use last element or empty string
189
- } ;
190
-
191
- // Cache and return the result
192
- chainCache . set ( node , result ) ;
193
143
return result ;
194
144
}
195
145
196
- // Tracks which chains are modified in code
197
- //
198
- // Examples of modifications:
199
- // 1. obj.prop = value; // Direct assignment
200
- // 2. obj.prop++; // Increment/decrement
201
- // 3. updateValues(obj.prop); // Potential modification through function call
202
-
203
146
function trackModification ( chain : string , node : TSESTree . Node ) {
204
147
const scope = sourceCode . getScope ( node ) ;
205
148
const scopeData = getScopeData ( scope ) ;
@@ -217,12 +160,6 @@ const noRepeatedMemberAccess = createRule({
217
160
record . modified = true ;
218
161
}
219
162
}
220
- // Mark the chain as modified regardless of it has been created or not!! Otherwise properties that get written will be reported in the first time, but they should not be reported.
221
- // Here is a more concrete example:
222
- // "this.vehicleSys!" should not be extracted as it is written later
223
- // this.vehicleSys!.automobile = new TransportCore(new TransportBlueprint()); // THIS line will get reported if we don't mark the chain as modified
224
- // this.vehicleSys!.automobile!.apple = new ChassisAssembly(new ChassisSchema());
225
- // this.vehicleSys!.automobile!.apple!.propulsionCover = new EngineEnclosure(new EnclosureSpec());
226
163
if ( scopeData . chains . has ( chain ) ) {
227
164
scopeData . chains . get ( chain ) ! . modified = true ;
228
165
} else {
@@ -234,11 +171,6 @@ const noRepeatedMemberAccess = createRule({
234
171
}
235
172
}
236
173
237
- // Processing member expressions and identifying optimization opportunities
238
- // Examples:
239
- // - obj.prop.val accessed 3+ times → extract to variable
240
- // - obj.prop.val modified → don't extract
241
- // - obj.prop.val used in different scopes → extract separately in each scope
242
174
function processMemberExpression ( node : TSESTree . MemberExpression ) {
243
175
// Skip nodes that are part of larger member expressions
244
176
// Example: In a.b.c, we process the top-level MemberExpression only,
@@ -255,7 +187,7 @@ const noRepeatedMemberAccess = createRule({
255
187
let longestValidChain = "" ;
256
188
257
189
// Update chain statistics for each part of the hierarchy
258
- for ( const chain of chainInfo . hierarchy ) {
190
+ for ( const chain of chainInfo ) {
259
191
// Skip single-level chains
260
192
if ( ! chain . includes ( "." ) ) continue ;
261
193
@@ -291,16 +223,6 @@ const noRepeatedMemberAccess = createRule({
291
223
}
292
224
}
293
225
294
- // ======================
295
- // Rule Listeners
296
- // ======================
297
- // These event handlers process different AST node types and track chain usage
298
- //
299
- // Examples of what each listener detects:
300
- // - MemberExpression: obj.prop.val
301
- // - AssignmentExpression: obj.prop.val = 5
302
- // - UpdateExpression: obj.prop.val++
303
- // - CallExpression: obj.prop.method()
304
226
return {
305
227
// Track assignments that modify member chains
306
228
// Example: obj.prop.val = 5 modifies the "obj.prop.val" chain
@@ -309,11 +231,9 @@ const noRepeatedMemberAccess = createRule({
309
231
if ( node . left . type === AST_NODE_TYPES . MemberExpression ) {
310
232
const chainInfo = analyzeChain ( node . left ) ;
311
233
if ( chainInfo ) {
312
- // Mark all parts of the chain as modified
313
- // Example: For obj.prop.val = 5, we mark "obj", "obj.prop",
314
- // and "obj.prop.val" as modified
315
- for ( const chain of chainInfo . hierarchy )
234
+ for ( const chain of chainInfo ) {
316
235
trackModification ( chain , node ) ;
236
+ }
317
237
}
318
238
}
319
239
} ,
@@ -324,11 +244,9 @@ const noRepeatedMemberAccess = createRule({
324
244
if ( node . argument . type === AST_NODE_TYPES . MemberExpression ) {
325
245
const chainInfo = analyzeChain ( node . argument ) ;
326
246
if ( chainInfo ) {
327
- // Mark all parts of the chain as modified
328
- // Example: For obj.prop.val++, we mark "obj", "obj.prop",
329
- // and "obj.prop.val" as modified
330
- for ( const chain of chainInfo . hierarchy )
247
+ for ( const chain of chainInfo ) {
331
248
trackModification ( chain , node ) ;
249
+ }
332
250
}
333
251
}
334
252
} ,
@@ -339,10 +257,9 @@ const noRepeatedMemberAccess = createRule({
339
257
if ( node . callee . type === AST_NODE_TYPES . MemberExpression ) {
340
258
const chainInfo = analyzeChain ( node . callee ) ;
341
259
if ( chainInfo ) {
342
- // Mark all parts of the chain as potentially modified
343
- // Example: For obj.methods.update(), we mark "obj", "obj.methods", and "obj.methods.update" as potentially modified
344
- for ( const chain of chainInfo . hierarchy )
260
+ for ( const chain of chainInfo ) {
345
261
trackModification ( chain , node ) ;
262
+ }
346
263
}
347
264
}
348
265
} ,
0 commit comments