Skip to content

Commit 6b15eb0

Browse files
committed
feat: add .$ property access autofixer
1 parent a7041a4 commit 6b15eb0

File tree

3 files changed

+94
-9
lines changed

3 files changed

+94
-9
lines changed

packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.test.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('add_autofixers_issues', () => {
109109
});
110110

111111
describe.each([{ method: 'set' }, { method: 'update' }])(
112-
'set_or_update_state ($method)',
112+
'wrong_property_access_state ($method)',
113113
({ method }) => {
114114
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
115115
const content = run_autofixers_on_code(`
@@ -203,6 +203,87 @@ describe('add_autofixers_issues', () => {
203203
},
204204
);
205205

206+
describe.each([{ property: '$' }])(
207+
'wrong_property_access_state property ($property)',
208+
async ({ property }) => {
209+
it(`should add suggestions when reading .${property} on a stateful variable with a literal init`, () => {
210+
const content = run_autofixers_on_code(`
211+
<script>
212+
const count = $state(0);
213+
function read_count() {
214+
count.${property};
215+
}
216+
</script>`);
217+
218+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
219+
expect(content.suggestions).toContain(
220+
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
221+
);
222+
});
223+
224+
it(`should add suggestions when reading .${property} on a stateful variable with an array init`, () => {
225+
const content = run_autofixers_on_code(`
226+
<script>
227+
const count = $state([1]);
228+
function read_count() {
229+
count.${property};
230+
}
231+
</script>`);
232+
233+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
234+
expect(content.suggestions).toContain(
235+
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
236+
);
237+
});
238+
239+
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state({}))`, () => {
240+
const content = run_autofixers_on_code(`
241+
<script>
242+
const count = $state({ value: 0 });
243+
function read_count() {
244+
count.${property};
245+
}
246+
</script>`);
247+
248+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
249+
expect(content.suggestions).toContain(
250+
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
251+
);
252+
});
253+
254+
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state(new Class()))`, () => {
255+
const content = run_autofixers_on_code(`
256+
<script>
257+
const count = $state(new Class());
258+
function read_count() {
259+
count.${property};
260+
}
261+
</script>`);
262+
263+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
264+
expect(content.suggestions).toContain(
265+
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
266+
);
267+
});
268+
269+
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable ($state(variable_name))`, () => {
270+
const content = run_autofixers_on_code(`
271+
<script>
272+
const { init } = $props();
273+
const count = $state(init);
274+
function read_count() {
275+
count.${property};
276+
}
277+
</script>`);
278+
279+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
280+
expect(content.suggestions).toContain(
281+
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
282+
);
283+
});
284+
},
285+
);
286+
206287
describe('imported_runes', () => {
207288
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
208289
'from "$source"',

packages/mcp-server/src/mcp/autofixers/visitors/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type AutofixerState = {
1212
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
1313

1414
export * from './assign-in-effect.js';
15-
export * from './set-or-update-state.js';
15+
export * from './wrong-property-access-state.js';
1616
export * from './imported-runes.js';
1717
export * from './derived-with-function.js';
1818
export * from './use-runes-instead-of-store.js';

packages/mcp-server/src/mcp/autofixers/visitors/set-or-update-state.ts renamed to packages/mcp-server/src/mcp/autofixers/visitors/wrong-property-access-state.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import type { Autofixer } from './index.js';
22
import { left_most_id } from '../ast/utils.js';
33

4-
const UPDATE_PROPERTIES = ['set', 'update'];
4+
const UPDATE_PROPERTIES = new Set(['set', 'update', '$']);
5+
const METHODS = new Set(['set', 'update']);
56

6-
export const set_or_update_state: Autofixer = {
7+
export const wrong_property_access_state: Autofixer = {
78
MemberExpression(node, { state, next, path }) {
89
const parent = path[path.length - 1];
10+
let is_property = false;
911
if (
10-
parent?.type === 'CallExpression' &&
11-
parent.callee === node &&
1212
node.property.type === 'Identifier' &&
13-
UPDATE_PROPERTIES.includes(node.property.name)
13+
((is_property = !METHODS.has(node.property.name)) ||
14+
(parent?.type === 'CallExpression' && parent.callee === node)) &&
15+
UPDATE_PROPERTIES.has(node.property.name)
1416
) {
1517
const id = left_most_id(node);
1618
if (id) {
@@ -22,10 +24,12 @@ export const set_or_update_state: Autofixer = {
2224
init?.type === 'CallExpression' &&
2325
state.parsed.is_rune(init, ['$state', '$state.raw'])
2426
) {
25-
let suggestion = `You are trying to update the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`;
27+
let suggestion = is_property
28+
? `You are trying to read the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`
29+
: `You are trying to update the stateful variable "${id.name}" using "${node.property.name}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`;
2630
const argument = init.arguments[0];
2731
if (!argument || (argument.type !== 'Literal' && argument.type !== 'ArrayExpression')) {
28-
suggestion += ` However I can't verify if "${id.name}" is a state variable of an object or a class with a "${node.property.name}" method on it. Please verify that before updating the code to use a normal assignment`;
32+
suggestion += ` However I can't verify if "${id.name}" is a state variable of an object or a class with a "${node.property.name}" ${is_property ? 'property' : 'method'} on it. Please verify that before updating the code to use a normal ${is_property ? 'access' : 'assignment'}`;
2933
}
3034
state.output.suggestions.push(suggestion);
3135
}

0 commit comments

Comments
 (0)