From 7086e8e55f973a65efcde7ad737d4cb0f5d10389 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Tue, 23 Sep 2025 00:17:36 +0200 Subject: [PATCH 1/3] feat: add `bind:this` -> attachment autofixer --- .../autofixers/add-autofixers-issues.test.ts | 63 +++++++++++++++++++ .../visitors/bind-this-attachment.ts | 18 ++++++ src/lib/mcp/autofixers/visitors/index.ts | 1 + 3 files changed, 82 insertions(+) create mode 100644 src/lib/mcp/autofixers/visitors/bind-this-attachment.ts diff --git a/src/lib/mcp/autofixers/add-autofixers-issues.test.ts b/src/lib/mcp/autofixers/add-autofixers-issues.test.ts index cceaf19..4ff7ef6 100644 --- a/src/lib/mcp/autofixers/add-autofixers-issues.test.ts +++ b/src/lib/mcp/autofixers/add-autofixers-issues.test.ts @@ -374,4 +374,67 @@ describe('add_autofixers_issues', () => { ); }); }); + + describe('bind_this_attachment', () => { + it('should add suggestions when using bind:this on an element', () => { + const content = run_autofixers_on_code(` + + + `); + + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.', + ); + }); + + it('should not add suggestions when using bind:this on a component', () => { + const content = run_autofixers_on_code(` + + + `); + + expect(content.suggestions).not.toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.', + ); + }); + + it('should not add suggestions when using bind:this on a component nested in an element', () => { + const content = run_autofixers_on_code(` + + +
+ +
`); + + expect(content.suggestions).not.toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.', + ); + }); + + it('should add suggestions but not suggest attachments when using bind:this on an element and the desired svelte version is 4', () => { + const content = run_autofixers_on_code( + ` + + +
`, + 4, + ); + + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.', + ); + }); + }); }); diff --git a/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts b/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts new file mode 100644 index 0000000..ede1c4c --- /dev/null +++ b/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts @@ -0,0 +1,18 @@ +import type { Autofixer } from '.'; +export const bind_this_attachment: Autofixer = { + SvelteDirective(node, { state, next, path }) { + if (node.kind === 'Binding' && node.key.name.name === 'this') { + const parent_element = path.findLast((p) => p.type === 'SvelteElement'); + if (parent_element?.kind === 'html' && parent_element.startTag.attributes.includes(node)) { + let better_an_attachment = ` or even better an \`attachment\``; + if (state.desired_svelte_version === 4) { + better_an_attachment = ``; + } + state.output.suggestions.push( + `The usage of \`bind:this\` can often be replaced with an easier to read \`action\`${better_an_attachment}. Consider using the latter if possible.`, + ); + } + } + next(); + }, +}; diff --git a/src/lib/mcp/autofixers/visitors/index.ts b/src/lib/mcp/autofixers/visitors/index.ts index b29f303..d2db0dd 100644 --- a/src/lib/mcp/autofixers/visitors/index.ts +++ b/src/lib/mcp/autofixers/visitors/index.ts @@ -16,3 +16,4 @@ export * from './set-or-update-state.js'; export * from './imported-runes.js'; export * from './derived-with-function.js'; export * from './use-runes-instead-of-store.js'; +export * from './bind-this-attachment.js'; From 9504e6bac9378403d7c897b4b88c7b523f1ff5e2 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Tue, 23 Sep 2025 00:49:09 +0200 Subject: [PATCH 2/3] feat: autofixer `action` -> `attachment` --- .../autofixers/add-autofixers-issues.test.ts | 168 +++++++++++++++--- .../visitors/bind-this-attachment.ts | 18 -- src/lib/mcp/autofixers/visitors/index.ts | 2 +- .../visitors/suggest-attachments.ts | 47 +++++ 4 files changed, 187 insertions(+), 48 deletions(-) delete mode 100644 src/lib/mcp/autofixers/visitors/bind-this-attachment.ts create mode 100644 src/lib/mcp/autofixers/visitors/suggest-attachments.ts diff --git a/src/lib/mcp/autofixers/add-autofixers-issues.test.ts b/src/lib/mcp/autofixers/add-autofixers-issues.test.ts index 4ff7ef6..1703f47 100644 --- a/src/lib/mcp/autofixers/add-autofixers-issues.test.ts +++ b/src/lib/mcp/autofixers/add-autofixers-issues.test.ts @@ -375,23 +375,24 @@ describe('add_autofixers_issues', () => { }); }); - describe('bind_this_attachment', () => { - it('should add suggestions when using bind:this on an element', () => { - const content = run_autofixers_on_code(` + describe('suggest_attachments', () => { + describe('bind:this', () => { + it('should add suggestions when using bind:this on an element', () => { + const content = run_autofixers_on_code(` `); - expect(content.suggestions.length).toBeGreaterThanOrEqual(1); - expect(content.suggestions).toContain( - 'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.', - ); - }); + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.', + ); + }); - it('should not add suggestions when using bind:this on a component', () => { - const content = run_autofixers_on_code(` + it('should not add suggestions when using bind:this on a component', () => { + const content = run_autofixers_on_code(` `, - 4, - ); + 4, + ); - expect(content.suggestions.length).toBeGreaterThanOrEqual(1); - expect(content.suggestions).toContain( - 'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.', - ); + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.', + ); + }); + }); + + describe('use:', () => { + it('should add suggestions when using use: on an element and the action is declared as a function', () => { + const content = run_autofixers_on_code( + ` + + `, + ); + + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should add suggestions when using use: on an element and the action is declared as a variable', () => { + const content = run_autofixers_on_code( + ` + + `, + ); + + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should add suggestions when using use: on an element and the action is declared as an object', () => { + const content = run_autofixers_on_code( + ` + + `, + ); + + expect(content.suggestions.length).toBeGreaterThanOrEqual(1); + expect(content.suggestions).toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should not add suggestions when using use: on an element and the desired svelte version is 4', () => { + const content = run_autofixers_on_code( + ` + + `, + 4, + ); + + expect(content.suggestions).not.toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should not add suggestions when using use: on an element and the action comes from an import', () => { + const content = run_autofixers_on_code( + ` + + `, + ); + + expect(content.suggestions).not.toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should not add suggestions when using use: on an element and the action comes from the props', () => { + const content = run_autofixers_on_code( + ` + + `, + ); + + expect(content.suggestions).not.toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); + + it('should not add suggestions when using use: on an element and the action comes from a global variable', () => { + const content = run_autofixers_on_code(``); + + expect(content.suggestions).not.toContain( + 'Consider using an `attachment` instead of an `action` for "my_action".', + ); + }); }); }); }); diff --git a/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts b/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts deleted file mode 100644 index ede1c4c..0000000 --- a/src/lib/mcp/autofixers/visitors/bind-this-attachment.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Autofixer } from '.'; -export const bind_this_attachment: Autofixer = { - SvelteDirective(node, { state, next, path }) { - if (node.kind === 'Binding' && node.key.name.name === 'this') { - const parent_element = path.findLast((p) => p.type === 'SvelteElement'); - if (parent_element?.kind === 'html' && parent_element.startTag.attributes.includes(node)) { - let better_an_attachment = ` or even better an \`attachment\``; - if (state.desired_svelte_version === 4) { - better_an_attachment = ``; - } - state.output.suggestions.push( - `The usage of \`bind:this\` can often be replaced with an easier to read \`action\`${better_an_attachment}. Consider using the latter if possible.`, - ); - } - } - next(); - }, -}; diff --git a/src/lib/mcp/autofixers/visitors/index.ts b/src/lib/mcp/autofixers/visitors/index.ts index d2db0dd..3b0c598 100644 --- a/src/lib/mcp/autofixers/visitors/index.ts +++ b/src/lib/mcp/autofixers/visitors/index.ts @@ -16,4 +16,4 @@ export * from './set-or-update-state.js'; export * from './imported-runes.js'; export * from './derived-with-function.js'; export * from './use-runes-instead-of-store.js'; -export * from './bind-this-attachment.js'; +export * from './suggest-attachments.js'; diff --git a/src/lib/mcp/autofixers/visitors/suggest-attachments.ts b/src/lib/mcp/autofixers/visitors/suggest-attachments.ts new file mode 100644 index 0000000..fe127e0 --- /dev/null +++ b/src/lib/mcp/autofixers/visitors/suggest-attachments.ts @@ -0,0 +1,47 @@ +import type { Identifier } from 'estree'; +import type { Autofixer } from '.'; +import { left_most_id } from '../ast/utils.js'; + +export const suggest_attachments: Autofixer = { + SvelteDirective(node, { state, next, path }) { + if (node.kind === 'Binding' && node.key.name.name === 'this') { + const parent_element = path.findLast((p) => p.type === 'SvelteElement'); + if (parent_element?.kind === 'html' && parent_element.startTag.attributes.includes(node)) { + let better_an_attachment = ` or even better an \`attachment\``; + if (state.desired_svelte_version === 4) { + better_an_attachment = ``; + } + state.output.suggestions.push( + `The usage of \`bind:this\` can often be replaced with an easier to read \`action\`${better_an_attachment}. Consider using the latter if possible.`, + ); + } + } else if (node.kind === 'Action' && state.desired_svelte_version === 5) { + let id: Identifier | null = null; + if (node.key.name.type === 'Identifier') { + id = node.key.name; + } else if (node.key.name.type === 'MemberExpression') { + id = left_most_id(node.key.name); + } + 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' || + !(definition.type === 'ImportBinding' || definition.type === 'Parameter')) && + !( + definition.type === 'Variable' && + definition.node.init?.type === 'CallExpression' && + state.parsed.is_rune(definition.node.init, ['$props']) + ) + ) { + state.output.suggestions.push( + `Consider using an \`attachment\` instead of an \`action\` for "${id.name}".`, + ); + } + } + } + next(); + }, +}; From 8414ffbcc8e56e16376c5c8339e4dd1d85701613 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Tue, 23 Sep 2025 23:05:53 +0200 Subject: [PATCH 3/3] fix: move attachments fixer --- .../src/mcp/autofixers/visitors/suggest-attachments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fe127e0..778b826 100644 --- a/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts +++ b/packages/mcp-server/src/mcp/autofixers/visitors/suggest-attachments.ts @@ -1,5 +1,5 @@ import type { Identifier } from 'estree'; -import type { Autofixer } from '.'; +import type { Autofixer } from './index.js'; import { left_most_id } from '../ast/utils.js'; export const suggest_attachments: Autofixer = {