diff --git a/.changeset/proud-views-hang.md b/.changeset/proud-views-hang.md
new file mode 100644
index 0000000..546adee
--- /dev/null
+++ b/.changeset/proud-views-hang.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/mcp': patch
+---
+
+feat: `read_state_with_dollar` autofixer
diff --git a/packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.test.ts b/packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.test.ts
index 6e455ae..3caf593 100644
--- a/packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.test.ts
+++ b/packages/mcp-server/src/mcp/autofixers/add-autofixers-issues.test.ts
@@ -687,4 +687,56 @@ describe('add_autofixers_issues', () => {
});
});
});
+ describe('read_state_with_dollar', () => {
+ with_possible_inits('($init)', ({ init }) => {
+ it(`should add an issue when reading a stateful variable initialized with ${init} like if it was a store`, () => {
+ const content = run_autofixers_on_code(`
+ `);
+
+ expect(content.issues).toContain(
+ `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"`,
+ );
+ });
+ });
+
+ it(`should not add an issue when reading an imported variable like if it was a store`, () => {
+ const content = run_autofixers_on_code(`
+ `);
+
+ expect(content.issues).not.toContain(
+ `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"`,
+ );
+ });
+
+ it(`should not add an issue when reading a non-stateful variable like if it was a store`, () => {
+ const content = run_autofixers_on_code(`
+ `);
+
+ expect(content.issues).not.toContain(
+ `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"`,
+ );
+ });
+
+ it(`should not add an issue when reading a prop like if it was a store`, () => {
+ const content = run_autofixers_on_code(`
+ `);
+
+ expect(content.issues).not.toContain(
+ `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"`,
+ );
+ });
+ });
});
diff --git a/packages/mcp-server/src/mcp/autofixers/visitors/index.ts b/packages/mcp-server/src/mcp/autofixers/visitors/index.ts
index 6078f3b..9b3d0f6 100644
--- a/packages/mcp-server/src/mcp/autofixers/visitors/index.ts
+++ b/packages/mcp-server/src/mcp/autofixers/visitors/index.ts
@@ -17,3 +17,4 @@ export * from './imported-runes.js';
export * from './derived-with-function.js';
export * from './use-runes-instead-of-store.js';
export * from './suggest-attachments.js';
+export * from './read-state-with-dollar.js';
diff --git a/packages/mcp-server/src/mcp/autofixers/visitors/read-state-with-dollar.ts b/packages/mcp-server/src/mcp/autofixers/visitors/read-state-with-dollar.ts
new file mode 100644
index 0000000..122bcdc
--- /dev/null
+++ b/packages/mcp-server/src/mcp/autofixers/visitors/read-state-with-dollar.ts
@@ -0,0 +1,22 @@
+import type { Autofixer } from './index.js';
+
+export const read_state_with_dollar: Autofixer = {
+ Identifier(node, { state }) {
+ if (node.name.startsWith('$')) {
+ const reference = state.parsed.find_reference_by_id(node);
+ if (reference && reference.resolved && reference.resolved.defs[0]?.node?.init) {
+ const is_state = state.parsed.is_rune(reference.resolved.defs[0].node.init, [
+ '$state',
+ '$state.raw',
+ '$derived',
+ '$derived.by',
+ ]);
+ if (is_state) {
+ state.output.issues.push(
+ `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)}"`,
+ );
+ }
+ }
+ }
+ },
+};
diff --git a/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts b/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts
index 778b826..1d72ac0 100644
--- a/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts
+++ b/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts
@@ -25,7 +25,6 @@ export const suggest_attachments: Autofixer = {
if (id) {
const reference = state.parsed.find_reference_by_id(id);
const definition = reference?.resolved?.defs[0];
- console.log(definition);
if (
definition &&
(definition.type === 'Variable' ||
diff --git a/packages/mcp-server/src/parse/parse.ts b/packages/mcp-server/src/parse/parse.ts
index 53fb9e1..01b814e 100644
--- a/packages/mcp-server/src/parse/parse.ts
+++ b/packages/mcp-server/src/parse/parse.ts
@@ -36,8 +36,18 @@ export function parse(code: string, file_path: string) {
}
return all_scopes;
}
+ // walking the ast will also walk all the tokens if we don't remove them so we return them separately
+ // 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)
+ const {
+ ast: { tokens, ...ast },
+ } = parsed;
+
+ // @ts-expect-error we also have to delete it from the object or the circular reference remains
+ delete parsed.ast.tokens;
+
return {
- ast: parsed.ast,
+ ast,
+ tokens,
scope_manager: parsed.scopeManager as ScopeManager,
visitor_keys: parsed.visitorKeys,
get all_scopes() {