Skip to content

Commit edecf9f

Browse files
committed
wip 3; near completion??
1 parent 500b0ca commit edecf9f

File tree

4 files changed

+54
-179
lines changed

4 files changed

+54
-179
lines changed

.vscode/launch.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
"version": "0.2.0",
66
"configurations": [
77
{
8-
"args": ["--no-timeouts", "--colors", "--inspect-brk", "tests/rules/playground.ts"],
8+
"args": [
9+
"--no-timeouts",
10+
"--colors",
11+
"--inspect-brk",
12+
"tests/rules/playground.ts"
13+
],
914
"runtimeArgs": [
1015
"--experimental-specifier-resolution=node",
1116
"--experimental-loader",

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mochaExplorer.files": "dist/tests/**/*.test.js",
3-
}
2+
"mochaExplorer.files": "dist/tests/**/*.test.js"
3+
}

plugins/rules/memberAccess.ts

Lines changed: 46 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -60,45 +60,35 @@ const noRepeatedMemberAccess = createRule({
6060
// Track which chains have already been reported to avoid duplicate reports
6161
const reportedChains = new Set<string>();
6262

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+
>;
7471
// Stores data for each scope using WeakMap to avoid memory leaks
7572
const scopeDataMap = new WeakMap<Scope.Scope, ScopeData>();
7673

7774
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
8175
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+
>();
9485
scopeDataMap.set(scope, newScopeData);
9586
}
96-
9787
return scopeDataMap.get(scope)!;
9888
}
9989

10090
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
10292
let current: TSESTree.Node = node; // Current node in traversal
10393

10494
// Collect property chain (reverse order)
@@ -109,7 +99,7 @@ const noRepeatedMemberAccess = createRule({
10999
break;
110100
} else {
111101
// Handle dot notation like obj.prop
112-
parts.push(current.property.name);
102+
properties.push(current.property.name);
113103
}
114104

115105
current = current.object; // Move to parent object
@@ -123,33 +113,34 @@ const noRepeatedMemberAccess = createRule({
123113
// Handle base object (the root of the chain)
124114
// Example: For a.b.c, the base object is "a"
125115
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
127117
} else if (current.type === AST_NODE_TYPES.ThisExpression) {
128-
parts.push("this");
118+
properties.push("this");
129119
} // ignore other patterns
130120

131121
// 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();
135126

127+
// and build chain of object ["a", "a.b", "a.b.c"]
136128
const result: string[] = [];
137129
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]}`;
140133
result.push(currentChain);
141134
}
142135

143136
return result;
144137
}
145138

146-
function trackModification(chain: string, node: TSESTree.Node) {
139+
function setModifiedFlag(chain: string, node: TSESTree.Node) {
147140
const scope = sourceCode.getScope(node);
148141
const scopeData = getScopeData(scope);
149142

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) {
153144
// Check if the existing chain starts with the modified chain followed by a dot or bracket
154145
// This handles cases where modifying "a.b" should invalidate "a.b.c", "a.b.d", etc.
155146
if (
@@ -160,12 +151,10 @@ const noRepeatedMemberAccess = createRule({
160151
record.modified = true;
161152
}
162153
}
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, {
167156
count: 0,
168-
nodes: [],
157+
node: node as TSESTree.MemberExpression, // to do: check this conversion!!
169158
modified: true,
170159
});
171160
}
@@ -177,45 +166,20 @@ const noRepeatedMemberAccess = createRule({
177166
// not the sub-expressions a.b or a
178167
if (node.parent?.type === AST_NODE_TYPES.MemberExpression) return;
179168

180-
const chainInfo = analyzeChain(node);
181-
if (!chainInfo) return;
182-
183169
const scope = sourceCode.getScope(node);
184170
const scopeData = getScopeData(scope);
185171

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;
213174

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+
) {
217181
context.report({
218-
node: record.nodes[0],
182+
node: record.node,
219183
messageId: "repeatedAccess",
220184
data: { chain: longestValidChain, count: record.count },
221185
});
@@ -232,7 +196,7 @@ const noRepeatedMemberAccess = createRule({
232196
const chainInfo = analyzeChain(node.left);
233197
if (chainInfo) {
234198
for (const chain of chainInfo) {
235-
trackModification(chain, node);
199+
setModifiedFlag(chain, node);
236200
}
237201
}
238202
}
@@ -245,7 +209,7 @@ const noRepeatedMemberAccess = createRule({
245209
const chainInfo = analyzeChain(node.argument);
246210
if (chainInfo) {
247211
for (const chain of chainInfo) {
248-
trackModification(chain, node);
212+
setModifiedFlag(chain, node);
249213
}
250214
}
251215
}
@@ -258,7 +222,7 @@ const noRepeatedMemberAccess = createRule({
258222
const chainInfo = analyzeChain(node.callee);
259223
if (chainInfo) {
260224
for (const chain of chainInfo) {
261-
trackModification(chain, node);
225+
setModifiedFlag(chain, node);
262226
}
263227
}
264228
}

plugins/rules/rewriteMember.ts

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)