Skip to content

Commit 13719d0

Browse files
committed
feat: add derived with functions suggestions
1 parent 4c8cadd commit 13719d0

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

src/lib/mcp/autofixers/add-autofixers-issues.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,75 @@ describe('add_autofixers_issues', () => {
273273
},
274274
);
275275
});
276+
277+
describe('derived_with_function', () => {
278+
it(`should add suggestions when using a function as the first argument to $derived`, () => {
279+
const content = run_autofixers_on_code(`
280+
<script>
281+
const value = $derived(() => {
282+
return 43;
283+
});
284+
</script>`);
285+
286+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
287+
expect(content.suggestions).toContain(
288+
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
289+
);
290+
});
291+
292+
it(`should add suggestions when using a function as the first argument to $derived in classes`, () => {
293+
const content = run_autofixers_on_code(`
294+
<script>
295+
class Double {
296+
value = $derived(() => 43);
297+
}
298+
</script>`);
299+
300+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
301+
expect(content.suggestions).toContain(
302+
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
303+
);
304+
});
305+
306+
it(`should add suggestions when using a function as the first argument to $derived in classes constructors`, () => {
307+
const content = run_autofixers_on_code(`
308+
<script>
309+
class Double {
310+
value;
311+
312+
constructor(){
313+
this.value = $derived(function() { return 44; });
314+
}
315+
}
316+
</script>`);
317+
318+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
319+
expect(content.suggestions).toContain(
320+
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
321+
);
322+
});
323+
324+
it(`should add suggestions when using a function as the first argument to $derived without the declaring part if it's not an identifier`, () => {
325+
const content = run_autofixers_on_code(`
326+
<script>
327+
const { destructured } = $derived(() => 43);
328+
</script>`);
329+
330+
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
331+
expect(content.suggestions).toContain(
332+
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
333+
);
334+
});
335+
336+
it(`should add suggestions when using a function as the first argument to $derived.by`, () => {
337+
const content = run_autofixers_on_code(`
338+
<script>
339+
const { destructured } = $derived.by(() => 43);
340+
</script>`);
341+
342+
expect(content.suggestions).not.toContain(
343+
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
344+
);
345+
});
346+
});
276347
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Identifier, PrivateIdentifier } from 'estree';
2+
import type { Autofixer } from '.';
3+
4+
export const derived_with_function: Autofixer = {
5+
CallExpression(node, { state, path }) {
6+
if (
7+
node.callee.type === 'Identifier' &&
8+
node.callee.name === '$derived' &&
9+
state.parsed.is_rune(node, ['$derived']) &&
10+
(node.arguments[0].type === 'ArrowFunctionExpression' ||
11+
node.arguments[0].type === 'FunctionExpression')
12+
) {
13+
const parent = path[path.length - 1];
14+
let variable_id: Identifier | PrivateIdentifier | undefined;
15+
if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
16+
// const something = $derived(...)
17+
variable_id = parent.id;
18+
} else if (parent.type === 'PropertyDefinition') {
19+
// class X { something = $derived(...) }
20+
variable_id =
21+
parent.key.type === 'Identifier'
22+
? parent.key
23+
: parent.key.type === 'PrivateIdentifier'
24+
? parent.key
25+
: undefined;
26+
} else if (parent.type === 'AssignmentExpression') {
27+
// this.something = $derived(...)
28+
variable_id =
29+
parent.left.type === 'MemberExpression'
30+
? parent.left.property.type === 'Identifier'
31+
? parent.left.property
32+
: undefined
33+
: undefined;
34+
}
35+
36+
state.output.suggestions.push(
37+
`You are passing a function to $derived ${variable_id ? `when declaring "${variable_id.name}" ` : ''}but $derived expects an expression. You can use $derived.by instead.`,
38+
);
39+
}
40+
},
41+
};

src/lib/mcp/autofixers/visitors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
1414
export * from './assign-in-effect.js';
1515
export * from './set-or-update-state.js';
1616
export * from './imported-runes.js';
17+
export * from './derived-with-function.js';

0 commit comments

Comments
 (0)