Skip to content

Commit 54b3f44

Browse files
committed
add fixer, more thorough tests are required!!
1 parent dcc1cdb commit 54b3f44

File tree

4 files changed

+119
-76
lines changed

4 files changed

+119
-76
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
代码部分请仅使用英文,其他部分使用中文,尽量在说明的同时也给一些实例方便理解
2+
在回答时不要使用类似于... rest of the code ...的方式省略代码

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@types/node": "^22.14.1",
3838
"@typescript-eslint/rule-tester": "^8.30.1",
3939
"ts-node": "^10.9.2",
40+
"tsx": "^4.19.3",
4041
"typescript-eslint": "^8.30.1"
4142
}
4243
}

plugins/perf-plugin.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ const noRepeatedMemberAccess: ESLintUtils.RuleModule<
133133
],
134134
},
135135
create(context) {
136+
// Store nodes for each object chain in each scope for auto-fixing
137+
const chainNodesMap = new Map<string, TSESTree.MemberExpression[]>();
138+
136139
function getObjectChain(node: TSESTree.Node) {
137140
// node is the outermost MemberExpression, e.g. ctx.data.v1
138141
let current = node;
@@ -300,24 +303,75 @@ const noRepeatedMemberAccess: ESLintUtils.RuleModule<
300303

301304
const key = `${scope.block.range.join("-")}-${objectChain}`;
302305

306+
// Store node for auto-fixing
307+
if (!chainNodesMap.has(key)) {
308+
chainNodesMap.set(key, []);
309+
}
310+
chainNodesMap.get(key)?.push(node as TSESTree.MemberExpression);
311+
303312
const count = (occurrences.get(key) || 0) + 1;
304313
occurrences.set(key, count);
305314

306-
if (count === minOccurrences) {
315+
if (count >= minOccurrences) {
307316
context.report({
308317
node,
309318
messageId: "repeatedAccess",
310319
data: {
311320
path: objectChain,
312-
count: minOccurrences,
321+
count: count,
322+
},
323+
*fix(fixer) {
324+
const nodes = chainNodesMap.get(key);
325+
if (!nodes || nodes.length < minOccurrences) return;
326+
327+
// Create a safe variable name based on the object chain
328+
const safeVarName = `_${objectChain.replace(
329+
/[^a-zA-Z0-9_]/g,
330+
"_"
331+
)}`;
332+
333+
// Find the first statement containing the first instance
334+
let statement: TSESTree.Node = nodes[0];
335+
while (
336+
statement.parent &&
337+
![
338+
"Program",
339+
"BlockStatement",
340+
"StaticBlock",
341+
"SwitchCase",
342+
].includes(statement.parent.type)
343+
) {
344+
statement = statement.parent;
345+
}
346+
347+
// Check if the variable already exists in this scope
348+
const scope = context.sourceCode.getScope(nodes[0]);
349+
const variableExists = scope.variables.some(
350+
(v) => v.name === safeVarName
351+
);
352+
353+
// Only insert declaration if variable doesn't exist
354+
if (!variableExists) {
355+
yield fixer.insertTextBefore(
356+
statement,
357+
`const ${safeVarName} = ${objectChain};\n`
358+
);
359+
}
360+
361+
// Replace ALL occurrences, not just the current node
362+
for (const memberNode of nodes) {
363+
const objText = context.sourceCode.getText(memberNode.object);
364+
if (objText === objectChain) {
365+
yield fixer.replaceText(memberNode.object, safeVarName);
366+
}
367+
}
313368
},
314369
});
315370
}
316371
},
317372
};
318373
},
319374
});
320-
321375
export default {
322376
rules: {
323377
"array-init-style": arrayInitStyle,

tests/perf-plugin.test.ts

Lines changed: 60 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,6 @@ RuleTester.itOnly = test.it.only;
1010

1111
const ruleTester = new RuleTester();
1212

13-
// ruleTester.run(
14-
// "no-repeated-member-access",
15-
// perfPlugin.rules["no-repeated-member-access"],
16-
// {
17-
// valid: [
18-
// `
19-
// switch (reason) {
20-
// case Test.STARTUP: {
21-
// return "STARTUP"
22-
// }
23-
// case Test.DEBOUNCE: {
24-
// return "DEBOUNCE"
25-
// }
26-
// case Test.INSTANT: {
27-
// return "INSTANT"
28-
// }
29-
// case Test.SHUTDOWN: {
30-
// return "SHUTDOWN"
31-
// }
32-
// default: {
33-
// return "UNKNOWN"
34-
// }
35-
// }
36-
// `,
37-
// ],
38-
39-
// invalid: [
40-
// {
41-
// code: `
42-
// this.vehicleSys!.automobile = new TransportCore(new TransportBlueprint());
43-
// this.vehicleSys!.automobile!.underframe = new ChassisAssembly(new ChassisSchema());
44-
// this.vehicleSys!.automobile!.underframe!.propulsionCover = new EngineEnclosure(new EnclosureSpec());
45-
// this.vehicleSys!.automobile!.underframe!.logisticsBay = new CargoModule(new ModuleTemplate());
46-
// `,
47-
// errors: [{ messageId: "repeatedAccess" }],
48-
// },
49-
// ],
50-
// }
51-
// );
52-
5313
// Throws error if the tests in ruleTester.run() do not pass
5414
ruleTester.run(
5515
"array-init-style", // rule name
@@ -124,15 +84,15 @@ ruleTester.run(
12484
// Basic invalid case
12585
{
12686
code: `
127-
const v1 = ctx.data.v1;
128-
const v2 = ctx.data.v2;
129-
`,
87+
const v1 = ctx.data.v1;
88+
const v2 = ctx.data.v2;
89+
`,
13090
errors: [{ messageId: "repeatedAccess" }],
131-
// output: `
132-
// const temp1 = ctx.data;
133-
// const v1 = temp1.v1;
134-
// const v2 = temp1.v2;
135-
// `
91+
output: `
92+
const _ctx_data = ctx.data;
93+
const v1 = _ctx_data.v1;
94+
const v2 = _ctx_data.v2;
95+
`,
13696
},
13797
{
13898
code: `
@@ -141,18 +101,17 @@ ruleTester.run(
141101
this.profile = service.user.profile
142102
this.log = service.user.logger
143103
}
144-
}
145-
`,
104+
}`,
146105
errors: [{ messageId: "repeatedAccess" }],
147-
// output: `
148-
// class User {
149-
// constructor() {
150-
// const temp1 = service.user;
151-
// this.profile = temp1.profile
152-
// this.log = temp1.logger
153-
// }
154-
// }
155-
// `
106+
output:
107+
"\n" +
108+
" class User {\n" +
109+
" constructor() {\n" +
110+
" const _service_user = service.user;\n" +
111+
"this.profile = _service_user.profile\n" +
112+
" this.log = _service_user.logger\n" +
113+
" }\n" +
114+
" }",
156115
},
157116
// Nested scope case
158117
{
@@ -163,13 +122,14 @@ ruleTester.run(
163122
}
164123
`,
165124
errors: [{ messageId: "repeatedAccess" }],
166-
// output: `
167-
// function demo() {
168-
// const temp1 = obj.a.b;
169-
// console.log(temp1.c);
170-
// return temp1.d;
171-
// }
172-
// `
125+
output:
126+
"\n" +
127+
" function demo() {\n" +
128+
" const _obj_a_b = obj.a.b;\n" +
129+
"console.log(_obj_a_b.c);\n" +
130+
" return _obj_a_b.d;\n" +
131+
" }\n" +
132+
" ",
173133
},
174134

175135
// Array index case
@@ -179,13 +139,17 @@ ruleTester.run(
179139
data[0].count++;
180140
send(data[0].id);
181141
`,
182-
errors: [{ messageId: "repeatedAccess" }],
183-
// output: `
184-
// const temp1 = data[0];
185-
// const x = temp1.value;
186-
// temp1.count++;
187-
// send(temp1.id);
188-
// `
142+
errors: [
143+
{ messageId: "repeatedAccess" },
144+
{ messageId: "repeatedAccess" },
145+
],
146+
output:
147+
"\n" +
148+
" const _data_0_ = data[0];\n" +
149+
"const x = _data_0_.value;\n" +
150+
" _data_0_.count++;\n" +
151+
" send(_data_0_.id);\n" +
152+
" ",
189153
},
190154
{
191155
code: `
@@ -198,6 +162,29 @@ ruleTester.run(
198162
{ messageId: "repeatedAccess" },
199163
{ messageId: "repeatedAccess" },
200164
{ messageId: "repeatedAccess" },
165+
{ messageId: "repeatedAccess" },
166+
{ messageId: "repeatedAccess" },
167+
{ messageId: "repeatedAccess" },
168+
],
169+
output: [
170+
"\n" +
171+
" const _this_vehicleSys = this.vehicleSys;\n" +
172+
"this.vehicleSys!.automobile = new TransportCore(new TransportBlueprint());\n" +
173+
" const _this_vehicleSys_automobile = this.vehicleSys.automobile;\n" +
174+
"this.vehicleSys!.automobile!.underframe = new ChassisAssembly(new ChassisSchema());\n" +
175+
" const _this_vehicleSys_automobile_underframe = this.vehicleSys.automobile.underframe;\n" +
176+
"this.vehicleSys!.automobile!.underframe!.propulsionCover = new EngineEnclosure(new EnclosureSpec());\n" +
177+
" this.vehicleSys!.automobile!.underframe!.logisticsBay = new CargoModule(new ModuleTemplate());\n" +
178+
" ",
179+
"\n" +
180+
" const _this_vehicleSys = this.vehicleSys;\n" +
181+
"this.vehicleSys!.automobile = new TransportCore(new TransportBlueprint());\n" +
182+
" const _this_vehicleSys_automobile = _this_vehicleSys.automobile;\n" +
183+
"this.vehicleSys!.automobile!.underframe = new ChassisAssembly(new ChassisSchema());\n" +
184+
" const _this_vehicleSys_automobile_underframe = _this_vehicleSys_automobile.underframe;\n" +
185+
"this.vehicleSys!.automobile!.underframe!.propulsionCover = new EngineEnclosure(new EnclosureSpec());\n" +
186+
" this.vehicleSys!.automobile!.underframe!.logisticsBay = new CargoModule(new ModuleTemplate());\n" +
187+
" ",
201188
],
202189
},
203190
],

0 commit comments

Comments
 (0)