Skip to content

Commit 500b0ca

Browse files
committed
wip 2: refactor old version
1 parent defaea0 commit 500b0ca

File tree

3 files changed

+42
-119
lines changed

3 files changed

+42
-119
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
],
3434
"scripts": {
3535
"build": "npx tsc --build ./tsconfig.json",
36-
"test": "mocha --timeout 10000 'dist/tests/**/**.test.js'",
36+
"test": "mocha --timeout 10000 'dist/tests/**/noRepeatedMemberAccess.test.js'",
3737
"test:coverage": "c8 npm test",
3838
"lint": "npx eslint plugins/ tests/",
3939
"watch": "npx tsc --build ./tsconfig.json --watch",

plugins/rules/memberAccess.ts

Lines changed: 14 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -97,38 +97,15 @@ const noRepeatedMemberAccess = createRule({
9797
return scopeDataMap.get(scope)!;
9898
}
9999

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
123102
let current: TSESTree.Node = node; // Current node in traversal
124-
let isValid = true; // Whether this chain is valid for optimization
125103

126104
// Collect property chain (reverse order)
127105
// Example: For a.b.c, we'd collect ["c", "b", "a"] initially
128106
while (current.type === AST_NODE_TYPES.MemberExpression) {
129107
if (current.computed) {
130108
// skip computed properties like obj["prop"] or arr[0] or obj[getKey()]
131-
isValid = false;
132109
break;
133110
} else {
134111
// Handle dot notation like obj.prop
@@ -149,57 +126,23 @@ const noRepeatedMemberAccess = createRule({
149126
parts.push(current.name); // Add base object name
150127
} else if (current.type === AST_NODE_TYPES.ThisExpression) {
151128
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
163130

164131
// Generate hierarchy chain (forward order)
165132
// Example: For parts ["c", "b", "a"], we reverse to ["a", "b", "c"]
166133
// and build hierarchy ["a", "a.b", "a.b.c"]
167134
parts.reverse();
168135

169-
const hierarchy: string[] = [];
136+
const result: string[] = [];
137+
let currentChain = "";
170138
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);
184141
}
185142

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);
193143
return result;
194144
}
195145

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-
203146
function trackModification(chain: string, node: TSESTree.Node) {
204147
const scope = sourceCode.getScope(node);
205148
const scopeData = getScopeData(scope);
@@ -217,12 +160,6 @@ const noRepeatedMemberAccess = createRule({
217160
record.modified = true;
218161
}
219162
}
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());
226163
if (scopeData.chains.has(chain)) {
227164
scopeData.chains.get(chain)!.modified = true;
228165
} else {
@@ -234,11 +171,6 @@ const noRepeatedMemberAccess = createRule({
234171
}
235172
}
236173

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
242174
function processMemberExpression(node: TSESTree.MemberExpression) {
243175
// Skip nodes that are part of larger member expressions
244176
// Example: In a.b.c, we process the top-level MemberExpression only,
@@ -255,7 +187,7 @@ const noRepeatedMemberAccess = createRule({
255187
let longestValidChain = "";
256188

257189
// Update chain statistics for each part of the hierarchy
258-
for (const chain of chainInfo.hierarchy) {
190+
for (const chain of chainInfo) {
259191
// Skip single-level chains
260192
if (!chain.includes(".")) continue;
261193

@@ -291,16 +223,6 @@ const noRepeatedMemberAccess = createRule({
291223
}
292224
}
293225

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()
304226
return {
305227
// Track assignments that modify member chains
306228
// Example: obj.prop.val = 5 modifies the "obj.prop.val" chain
@@ -309,11 +231,9 @@ const noRepeatedMemberAccess = createRule({
309231
if (node.left.type === AST_NODE_TYPES.MemberExpression) {
310232
const chainInfo = analyzeChain(node.left);
311233
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) {
316235
trackModification(chain, node);
236+
}
317237
}
318238
}
319239
},
@@ -324,11 +244,9 @@ const noRepeatedMemberAccess = createRule({
324244
if (node.argument.type === AST_NODE_TYPES.MemberExpression) {
325245
const chainInfo = analyzeChain(node.argument);
326246
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) {
331248
trackModification(chain, node);
249+
}
332250
}
333251
}
334252
},
@@ -339,10 +257,9 @@ const noRepeatedMemberAccess = createRule({
339257
if (node.callee.type === AST_NODE_TYPES.MemberExpression) {
340258
const chainInfo = analyzeChain(node.callee);
341259
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) {
345261
trackModification(chain, node);
262+
}
346263
}
347264
}
348265
},

plugins/rules/rewriteMember.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,41 @@ const noRepeatedMemberAccess = createRule({
2727
defaultOptions: [{ minOccurrences: 3 }],
2828

2929
create(context, [options]) {
30-
// consider:
31-
// 1. a map to store [[scope, memberExpression[]], { count: number, modified: boolean }]
32-
// 2. process every statement we have
33-
// if it is a memberExpression, go through all its nodes and increase their count in the map
34-
// in case of assignment, update the modified flag for all nodes in the chain
35-
// 3. at the end of the scope, check if any of the chains have count >= minOccurrences
36-
// if so, report the issue and provide a fix to extract the chain into a variable
3730
const sourceCode = context.sourceCode;
3831
const minOccurrences = options.minOccurrences;
3932

40-
// type scopeKey = [Scope.Scope, TSESTree.MemberExpression[]];
41-
type scopeValue = { count: number; modified: boolean };
42-
const scopeMap = new WeakMap<TSESTree.MemberExpression[], scopeValue>();
33+
type modifiedMap = Map<string, boolean>;
34+
type countMap = Map<string, number>;
35+
type nodeMap = Map<string, TSESTree.Node>;
36+
37+
const scopeToModifiedMap = new Map<Scope.Scope, modifiedMap>();
38+
const scopeToCountMap = new Map<Scope.Scope, countMap>();
39+
const scopeToNodeMap = new Map<Scope.Scope, nodeMap>();
40+
41+
function analyzeChain(node: TSESTree.MemberExpression) {
42+
let currentNode = node;
43+
let parts = [];
44+
let valid = true;
45+
while (currentNode.type === AST_NODE_TYPES.MemberExpression) {
46+
parts.push
47+
48+
}
49+
50+
}
4351

4452
function trackModification(node: TSESTree.MemberExpression) {
45-
scopeMap[node].modified = true;
53+
const currentScope = sourceCode.getScope(node);
54+
if (!scopeToModifiedMap.has(currentScope)) {
55+
const newModifiedMap = new Map<string, boolean>();
56+
scopeToModifiedMap.set(currentScope, newModifiedMap);
57+
}
58+
const currentModifiedMap = scopeToModifiedMap.get(currentScope)!;
59+
60+
// scopeMap[node].modified = true;
4661
}
62+
4763
function processMemberExpression(node: TSESTree.MemberExpression) {}
4864

49-
// ======================
50-
// Rule Listeners
51-
// ======================
52-
// These event handlers process different AST node types and track chain usage
53-
//
54-
// Examples of what each listener detects:
55-
// - AssignmentExpression: obj.prop.val = 5
56-
// - UpdateExpression: obj.prop.val++
57-
// - CallExpression: obj.prop.method()
58-
// - MemberExpression: obj.prop.val
5965
return {
6066
// Track assignment expression
6167
// Example: obj.prop.val = 5

0 commit comments

Comments
 (0)