mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 03:19:38 +08:00
Compare commits
8 Commits
task-promp
...
tunnel-mod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06b3100600 | ||
|
|
107731b720 | ||
|
|
f04cb139e3 | ||
|
|
fd355e872d | ||
|
|
d4edef1d82 | ||
|
|
13719d0646 | ||
|
|
4c8cadd585 | ||
|
|
72e89af60f |
@@ -1,2 +1,3 @@
|
||||
DATABASE_URL=file:test.db
|
||||
DATABASE_TOKEN=needs_to_be_set_but_it_can_be_anything
|
||||
VOYAGE_API_KEY=your_actual_api_key_here
|
||||
@@ -32,3 +32,9 @@ pnpm run db:studio
|
||||
```
|
||||
|
||||
https://local.drizzle.studio/
|
||||
|
||||
#### Try the MCP server
|
||||
|
||||
To try the mcp server you can run `pnpm build:mcp`...the local `mpc.json` file it's already using the output of that folder so you can use it automatically in VSCode/Cursor.
|
||||
|
||||
To try the MCP over HTTP and/or remotely, you can use Cloudflare tunnels to expose it via `cloudflared tunnel --url http://localhost:[port]` and then running the server with `pnpm tunnel` (pay attention as this will expose your dev server to the world wide web).
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/lib/server/db/schema.ts',
|
||||
dialect: 'sqlite',
|
||||
dbCredentials: { url: process.env.DATABASE_URL },
|
||||
dialect: 'turso',
|
||||
dbCredentials: { url: process.env.DATABASE_URL, authToken: process.env.DATABASE_TOKEN },
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "vite dev",
|
||||
"tunnel": "vite dev -m tunnel",
|
||||
"build": "vite build",
|
||||
"build:mcp": "tsc --project tsconfig.build.json",
|
||||
"prepublishOnly": "pnpm build:mcp",
|
||||
@@ -48,6 +49,7 @@
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/node": "^24.3.1",
|
||||
"@typescript-eslint/types": "^8.43.0",
|
||||
"dotenv": "^17.2.2",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"eslint": "^9.18.0",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -66,6 +66,9 @@ importers:
|
||||
'@typescript-eslint/types':
|
||||
specifier: ^8.43.0
|
||||
version: 8.43.0
|
||||
dotenv:
|
||||
specifier: ^17.2.2
|
||||
version: 17.2.2
|
||||
drizzle-kit:
|
||||
specifier: ^0.30.2
|
||||
version: 0.30.6
|
||||
@@ -1737,6 +1740,10 @@ packages:
|
||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
dotenv@17.2.2:
|
||||
resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
drizzle-kit@0.30.6:
|
||||
resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==}
|
||||
hasBin: true
|
||||
@@ -4566,6 +4573,8 @@ snapshots:
|
||||
|
||||
diff@4.0.2: {}
|
||||
|
||||
dotenv@17.2.2: {}
|
||||
|
||||
drizzle-kit@0.30.6:
|
||||
dependencies:
|
||||
'@drizzle-team/brocli': 0.10.2
|
||||
|
||||
23
src/lib/constants.ts
Normal file
23
src/lib/constants.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const base_runes = [
|
||||
'$state',
|
||||
'$effect',
|
||||
'$derived',
|
||||
'$inspect',
|
||||
'$props',
|
||||
'$bindable',
|
||||
'$host',
|
||||
] as const;
|
||||
|
||||
export const nested_runes = [
|
||||
'$state.raw',
|
||||
'$state.snapshot',
|
||||
'$effect.pre',
|
||||
'$effect.tracking',
|
||||
'$effect.pending',
|
||||
'$effect.root',
|
||||
'$derived.by',
|
||||
'$inspect.trace',
|
||||
'$props.id',
|
||||
] as const;
|
||||
|
||||
export const runes = [...base_runes, ...nested_runes] as const;
|
||||
@@ -1,18 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { add_autofixers_issues } from './add-autofixers-issues';
|
||||
import { add_autofixers_issues } from './add-autofixers-issues.js';
|
||||
import { base_runes } from '../../constants.js';
|
||||
|
||||
const dollarless_runes = base_runes.map((r) => ({ rune: r.replace('$', '') }));
|
||||
|
||||
function run_autofixers_on_code(code: string, desired_svelte_version = 5) {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
add_autofixers_issues(content, code, desired_svelte_version);
|
||||
return content;
|
||||
}
|
||||
|
||||
describe('add_autofixers_issues', () => {
|
||||
describe('assign_in_effect', () => {
|
||||
it('should add suggestions when assigning to a stateful variable inside an effect', () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(0);
|
||||
$effect(() => {
|
||||
count = 43;
|
||||
});
|
||||
</script>`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
@@ -20,9 +27,8 @@ describe('add_autofixers_issues', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a suggestion for each variable assigned within an effect', () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
it(`should add a suggestion for each variable assigned within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(0);
|
||||
const count2 = $state(0);
|
||||
@@ -30,8 +36,7 @@ describe('add_autofixers_issues', () => {
|
||||
count = 43;
|
||||
count2 = 44;
|
||||
});
|
||||
</script>`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
|
||||
expect(content.suggestions).toContain(
|
||||
@@ -41,16 +46,14 @@ describe('add_autofixers_issues', () => {
|
||||
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
it('should not add a suggestion for variables that are not assigned within an effect', () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(0);
|
||||
</script>
|
||||
|
||||
<button onclick={() => count = 43}>Increment</button>
|
||||
`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
@@ -58,25 +61,22 @@ describe('add_autofixers_issues', () => {
|
||||
});
|
||||
|
||||
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = 0;
|
||||
|
||||
$effect(() => {
|
||||
count = 43;
|
||||
});
|
||||
</script>`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a suggestion for variables that are assigned within an effect with an update', () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let count = $state(0);
|
||||
|
||||
@@ -84,17 +84,15 @@ describe('add_autofixers_issues', () => {
|
||||
count++;
|
||||
});
|
||||
</script>
|
||||
`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
`);
|
||||
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a suggestion for variables that are mutated within an effect', () => {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
const code = `
|
||||
it(`should add a suggestion for variables that are mutated within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let count = $state({ value: 0 });
|
||||
|
||||
@@ -102,12 +100,248 @@ describe('add_autofixers_issues', () => {
|
||||
count.value = 42;
|
||||
});
|
||||
</script>
|
||||
`;
|
||||
add_autofixers_issues(content, code, 5);
|
||||
`);
|
||||
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([{ method: 'set' }, { method: 'update' }])(
|
||||
'set_or_update_state ($method)',
|
||||
({ method }) => {
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(0);
|
||||
function update_count() {
|
||||
count.${method}(43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state([0]);
|
||||
function update_count() {
|
||||
count.${method}([1]);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state({}))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state({ value: 0 });
|
||||
function update_count() {
|
||||
count.${method}({ value: 43 });
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(new Class()))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(new Class());
|
||||
function update_count() {
|
||||
count.${method}(new Class());
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable ($state(variable_name))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { init } = $props();
|
||||
const count = $state(init);
|
||||
function update_count() {
|
||||
count.${method}(43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state({});
|
||||
function update_count() {
|
||||
console.log(count.${method});
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('imported_runes', () => {
|
||||
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
|
||||
'from "$source"',
|
||||
({ source }) => {
|
||||
describe.each(dollarless_runes)('single import ($rune)', ({ rune }) => {
|
||||
it(`should add suggestions when importing '${rune}' from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { ${rune} } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing "${rune}" as the default export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import ${rune} from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing '${rune}' as the namespace export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import * as ${rune} from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing multiple runes from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { onMount, state, effect } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "state" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$state" directly.`,
|
||||
);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "effect" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$effect" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add suggestions when importing other identifiers from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { onMount } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "onMount" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$onMount" directly.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('derived_with_function', () => {
|
||||
it(`should add suggestions when using a function as the first argument to $derived`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const value = $derived(() => {
|
||||
return 43;
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived in classes`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
class Double {
|
||||
value = $derived(() => 43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived in classes constructors`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
class Double {
|
||||
value;
|
||||
|
||||
constructor(){
|
||||
this.value = $derived(function() { return 44; });
|
||||
}
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived without the declaring part if it's not an identifier`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { destructured } = $derived(() => 43);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived.by`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { destructured } = $derived.by(() => 43);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
|
||||
import type { Autofixer, AutofixerState } from '.';
|
||||
import { left_most_id } from '../ast/utils';
|
||||
import { left_most_id } from '../ast/utils.js';
|
||||
import type { SvelteNode } from 'svelte-eslint-parser/lib/ast';
|
||||
import type { Context } from 'zimmerframe';
|
||||
|
||||
|
||||
41
src/lib/mcp/autofixers/visitors/derived-with-function.ts
Normal file
41
src/lib/mcp/autofixers/visitors/derived-with-function.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Identifier, PrivateIdentifier } from 'estree';
|
||||
import type { Autofixer } from '.';
|
||||
|
||||
export const derived_with_function: Autofixer = {
|
||||
CallExpression(node, { state, path }) {
|
||||
if (
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === '$derived' &&
|
||||
state.parsed.is_rune(node, ['$derived']) &&
|
||||
(node.arguments[0].type === 'ArrowFunctionExpression' ||
|
||||
node.arguments[0].type === 'FunctionExpression')
|
||||
) {
|
||||
const parent = path[path.length - 1];
|
||||
let variable_id: Identifier | PrivateIdentifier | undefined;
|
||||
if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
|
||||
// const something = $derived(...)
|
||||
variable_id = parent.id;
|
||||
} else if (parent.type === 'PropertyDefinition') {
|
||||
// class X { something = $derived(...) }
|
||||
variable_id =
|
||||
parent.key.type === 'Identifier'
|
||||
? parent.key
|
||||
: parent.key.type === 'PrivateIdentifier'
|
||||
? parent.key
|
||||
: undefined;
|
||||
} else if (parent.type === 'AssignmentExpression') {
|
||||
// this.something = $derived(...)
|
||||
variable_id =
|
||||
parent.left.type === 'MemberExpression'
|
||||
? parent.left.property.type === 'Identifier'
|
||||
? parent.left.property
|
||||
: undefined
|
||||
: undefined;
|
||||
}
|
||||
|
||||
state.output.suggestions.push(
|
||||
`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.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
28
src/lib/mcp/autofixers/visitors/imported-runes.ts
Normal file
28
src/lib/mcp/autofixers/visitors/imported-runes.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { base_runes } from '../../../constants.js';
|
||||
import type { Autofixer } from '.';
|
||||
|
||||
const dollarless_runes = base_runes.map((r) => r.replace('$', ''));
|
||||
|
||||
export const imported_runes: Autofixer = {
|
||||
ImportDeclaration(node, { state, next }) {
|
||||
const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString();
|
||||
if (source && source.startsWith('svelte')) {
|
||||
for (const specifier of node.specifiers) {
|
||||
const id =
|
||||
specifier.type === 'ImportDefaultSpecifier'
|
||||
? specifier.local
|
||||
: specifier.type === 'ImportNamespaceSpecifier'
|
||||
? specifier.local
|
||||
: specifier.type === 'ImportSpecifier'
|
||||
? specifier.imported
|
||||
: null;
|
||||
if (id && id.type === 'Identifier' && dollarless_runes.includes(id.name)) {
|
||||
state.output.suggestions.push(
|
||||
`You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
};
|
||||
@@ -12,3 +12,6 @@ export type AutofixerState = {
|
||||
export type Autofixer = Visitors<Node | AST.SvelteNode, AutofixerState>;
|
||||
|
||||
export * from './assign-in-effect.js';
|
||||
export * from './set-or-update-state.js';
|
||||
export * from './imported-runes.js';
|
||||
export * from './derived-with-function.js';
|
||||
|
||||
37
src/lib/mcp/autofixers/visitors/set-or-update-state.ts
Normal file
37
src/lib/mcp/autofixers/visitors/set-or-update-state.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Autofixer } from '.';
|
||||
import { left_most_id } from '../ast/utils.js';
|
||||
|
||||
const UPDATE_PROPERTIES = ['set', 'update'];
|
||||
|
||||
export const set_or_update_state: Autofixer = {
|
||||
MemberExpression(node, { state, next, path }) {
|
||||
const parent = path[path.length - 1];
|
||||
if (
|
||||
parent.type === 'CallExpression' &&
|
||||
parent.callee === node &&
|
||||
node.property.type === 'Identifier' &&
|
||||
UPDATE_PROPERTIES.includes(node.property.name)
|
||||
) {
|
||||
const id = left_most_id(node);
|
||||
if (id) {
|
||||
const reference = state.parsed.find_reference_by_id(id);
|
||||
const definition = reference?.resolved?.defs[0];
|
||||
if (definition && definition.type === 'Variable') {
|
||||
const init = definition.node.init;
|
||||
if (
|
||||
init?.type === 'CallExpression' &&
|
||||
state.parsed.is_rune(init, ['$state', '$state.raw'])
|
||||
) {
|
||||
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.`;
|
||||
const argument = init.arguments[0];
|
||||
if (!argument || (argument.type !== 'Literal' && argument.type !== 'ArrayExpression')) {
|
||||
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`;
|
||||
}
|
||||
state.output.suggestions.push(suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import ts_parser from '@typescript-eslint/parser';
|
||||
import type { CallExpression, Identifier } from 'estree';
|
||||
import type { Reference, Variable } from 'eslint-scope';
|
||||
import { parseForESLint as svelte_eslint_parse } from 'svelte-eslint-parser';
|
||||
import { runes } from '../constants.js';
|
||||
|
||||
type Scope = {
|
||||
variables?: Variable[];
|
||||
@@ -18,25 +19,6 @@ function collect_scopes(scope: Scope, acc: Scope[] = []) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const runes = [
|
||||
'$state',
|
||||
'$state.raw',
|
||||
'$state.snapshot',
|
||||
'$effect',
|
||||
'$effect.pre',
|
||||
'$effect.tracking',
|
||||
'$effect.pending',
|
||||
'$effect.root',
|
||||
'$derived',
|
||||
'$derived.by',
|
||||
'$inspect',
|
||||
'$inspect.trace',
|
||||
'$props',
|
||||
'$props.id',
|
||||
'$bindable',
|
||||
'$host',
|
||||
] as const;
|
||||
|
||||
export type ParseResult = ReturnType<typeof parse>;
|
||||
|
||||
export function parse(code: string, file_path: string) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { DATABASE_URL } from '$env/static/private';
|
||||
import { createClient } from '@libsql/client';
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import * as schema from './schema';
|
||||
|
||||
if (!DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
if (!process.env.DATABASE_TOKEN) throw new Error('DATABASE_TOKEN is not set');
|
||||
|
||||
const client = createClient({ url: DATABASE_URL });
|
||||
const client = createClient({
|
||||
url: process.env.DATABASE_URL,
|
||||
authToken: process.env.DATABASE_TOKEN,
|
||||
});
|
||||
|
||||
export const db = drizzle(client, { schema, logger: true });
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import devtoolsJson from 'vite-plugin-devtools-json';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit(), devtoolsJson()],
|
||||
test: {
|
||||
expect: { requireAssertions: true },
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
export default defineConfig(({ mode }) => {
|
||||
config({ path: ['.env', `.env.${mode}`] });
|
||||
|
||||
return {
|
||||
plugins: [sveltekit(), devtoolsJson()],
|
||||
server:
|
||||
mode === 'tunnel'
|
||||
? {
|
||||
allowedHosts: true,
|
||||
}
|
||||
: undefined,
|
||||
test: {
|
||||
expect: { requireAssertions: true },
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user