Skip to content

Commit bf5f31c

Browse files
committed
feat: read_state_with_dollar autofixer
1 parent 62abaac commit bf5f31c

File tree

6 files changed

+91
-2
lines changed

6 files changed

+91
-2
lines changed

.changeset/proud-views-hang.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/mcp': patch
3+
---
4+
5+
feat: `read_state_with_dollar` autofixer

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,4 +687,56 @@ describe('add_autofixers_issues', () => {
687687
});
688688
});
689689
});
690+
describe.only('read_state_with_dollar', () => {
691+
with_possible_inits('($init)', ({ init }) => {
692+
it(`should add an issue when reading a stateful variable initialized with ${init} like if it was a store`, () => {
693+
const content = run_autofixers_on_code(`<script>
694+
let x = ${init}(()=> 43);
695+
$x;
696+
</script>
697+
`);
698+
699+
expect(content.issues).toContain(
700+
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
701+
);
702+
});
703+
});
704+
705+
it(`should not add an issue when reading an imported variable like if it was a store`, () => {
706+
const content = run_autofixers_on_code(`<script>
707+
import { x } from "./my-stores.ts";
708+
$x;
709+
</script>
710+
`);
711+
712+
expect(content.issues).not.toContain(
713+
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
714+
);
715+
});
716+
717+
it(`should not add an issue when reading a non-stateful variable like if it was a store`, () => {
718+
const content = run_autofixers_on_code(`<script>
719+
import { writable } from "svelte/store";
720+
const x = writable(0);
721+
$x;
722+
</script>
723+
`);
724+
725+
expect(content.issues).not.toContain(
726+
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
727+
);
728+
});
729+
730+
it(`should not add an issue when reading a prop like if it was a store`, () => {
731+
const content = run_autofixers_on_code(`<script>
732+
const { x } = $props();
733+
$x;
734+
</script>
735+
`);
736+
737+
expect(content.issues).not.toContain(
738+
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
739+
);
740+
});
741+
});
690742
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './imported-runes.js';
1717
export * from './derived-with-function.js';
1818
export * from './use-runes-instead-of-store.js';
1919
export * from './suggest-attachments.js';
20+
export * from './read-state-with-dollar.js';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Autofixer } from './index.js';
2+
3+
export const read_state_with_dollar: Autofixer = {
4+
Identifier(node, { state }) {
5+
if (node.name.startsWith('$')) {
6+
const reference = state.parsed.find_reference_by_id(node);
7+
if (reference && reference.resolved && reference.resolved.defs[0]?.node?.init) {
8+
const is_state = state.parsed.is_rune(reference.resolved.defs[0].node.init, [
9+
'$state',
10+
'$state.raw',
11+
'$derived',
12+
'$derived.by',
13+
]);
14+
if (is_state) {
15+
state.output.issues.push(
16+
`You are reading the stateful variable "${node.name}" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "${node.name.substring(1)}"`,
17+
);
18+
}
19+
}
20+
}
21+
},
22+
};

packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export const suggest_attachments: Autofixer = {
2525
if (id) {
2626
const reference = state.parsed.find_reference_by_id(id);
2727
const definition = reference?.resolved?.defs[0];
28-
console.log(definition);
2928
if (
3029
definition &&
3130
(definition.type === 'Variable' ||

packages/mcp-server/src/parse/parse.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,18 @@ export function parse(code: string, file_path: string) {
3636
}
3737
return all_scopes;
3838
}
39+
// walking the ast will also walk all the tokens if we don't remove them so we return them separately
40+
// we also remove the parent which as a circular reference to the ast itself (and it's not needed since we use zimmerframe to walk the ast)
41+
const {
42+
ast: { tokens, ...ast },
43+
} = parsed;
44+
45+
// @ts-expect-error we also have to delete it from the object or the circular reference remains
46+
delete parsed.ast.tokens;
47+
3948
return {
40-
ast: parsed.ast,
49+
ast,
50+
tokens,
4151
scope_manager: parsed.scopeManager as ScopeManager,
4252
visitor_keys: parsed.visitorKeys,
4353
get all_scopes() {

0 commit comments

Comments
 (0)