Compare commits

..

1 Commits

7 changed files with 59 additions and 98 deletions

View File

@@ -1,4 +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
GITHUB_WEBHOOK_SECRET=some_secret
VOYAGE_API_KEY=your_actual_api_key_here

View File

@@ -65,7 +65,6 @@
"@sveltejs/mcp-schema": "workspace:^",
"@sveltejs/mcp-server": "workspace:^",
"@tmcp/transport-http": "^0.6.3",
"tmcp": "^1.14.0",
"valibot": "^1.1.0"
"tmcp": "^1.14.0"
}
}

View File

@@ -1,32 +0,0 @@
import * as v from 'valibot';
// not the full schema but it contains the information we need
export const github_webhook_schema = v.object({
action: v.union([v.literal('closed')]),
pull_request: v.object({
patch_url: v.string(),
merged: v.boolean(),
user: v.object({
login: v.string(),
}),
}),
});
export const github_content_schema = v.object({
name: v.string(),
path: v.string(),
sha: v.string(),
size: v.number(),
url: v.string(),
html_url: v.string(),
git_url: v.string(),
download_url: v.nullable(v.string()),
type: v.literal('file'),
content: v.string(),
encoding: v.literal('base64'),
_links: v.object({
self: v.string(),
git: v.string(),
html: v.string(),
}),
});

View File

@@ -1,54 +0,0 @@
import { github_content_schema, github_webhook_schema } from '$lib/schemas/index.js';
import * as v from 'valibot';
export async function POST({ request, fetch }) {
const body = await request.json();
// TODO add secret validation
const validated_pull_request = v.safeParse(github_webhook_schema, body);
if (!validated_pull_request.success) {
return new Response('Invalid payload', { status: 400 });
}
const { pull_request } = validated_pull_request.output;
if (!pull_request.merged) {
return new Response(null, { status: 204 });
}
const patch = await fetch(pull_request.patch_url);
if (!patch.ok) {
return new Response('Failed to fetch patch', { status: 500 });
}
const patch_text = await patch.text();
const files = [
...patch_text.matchAll(
/^diff --git\sa\/(?<file>.+?)\sb\/\1\n(?:(?<action>deleted|new)\sfile mode)?/gm,
),
].map((res) => ({ file: res.groups!.file, action: res.groups?.action ?? 'modified' }));
for (const file of files) {
if (file.action === 'deleted') {
// delete path from db
continue;
}
const new_file_content = await fetch(
`https://api.github.com/repos/sveltejs/svelte.dev/contents/${file.file}`,
);
if (!new_file_content.ok) {
// push file in queue and try again later?
continue;
}
const new_file_json = await new_file_content.json();
const validated_content = v.safeParse(github_content_schema, new_file_json);
if (!validated_content.success) {
// push file in queue and try again later?
continue;
}
const content = Buffer.from(validated_content.output.content, 'base64').toString('utf-8');
// save content and distilled content in the db
console.log({ content, file: file.file });
}
return new Response(null, { status: 204 });
}

View File

@@ -117,6 +117,36 @@ describe('add_autofixers_issues', () => {
);
});
});
it('should add a suggestion when calling a function inside an effect', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data();
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are calling the function \`fetch_data\` inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
const content = run_autofixers_on_code(`
<script>
import { fetch_data } from './data.js';
$effect(() => {
fetch_data.fetch();
});
</script>`);
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
expect(content.suggestions).toContain(
`You are calling a function inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
});
with_possible_inits('($init)', ({ init }) => {

View File

@@ -1,4 +1,10 @@
import type { AssignmentExpression, Identifier, Node, UpdateExpression } from 'estree';
import type {
AssignmentExpression,
CallExpression,
Identifier,
Node,
UpdateExpression,
} from 'estree';
import type { Autofixer, AutofixerState } from './index.js';
import { left_most_id } from '../ast/utils.js';
import type { AST } from 'svelte-eslint-parser';
@@ -27,9 +33,9 @@ function run_if_in_effect(
}
}
function visitor(
function assign_or_update_visitor(
node: UpdateExpression | AssignmentExpression,
{ state, path }: Context<Node | AST.SvelteNode, AutofixerState>,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
function check_if_stateful_id(id: Identifier) {
@@ -58,9 +64,25 @@ function visitor(
}
}
});
next();
}
function call_expression_visitor(
node: CallExpression,
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
) {
run_if_in_effect(path, state, () => {
const function_name =
node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function';
state.output.suggestions.push(
`You are calling ${function_name} inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
);
});
next();
}
export const assign_in_effect: Autofixer = {
UpdateExpression: visitor,
AssignmentExpression: visitor,
UpdateExpression: assign_or_update_visitor,
AssignmentExpression: assign_or_update_visitor,
CallExpression: call_expression_visitor,
};

3
pnpm-lock.yaml generated
View File

@@ -62,9 +62,6 @@ importers:
tmcp:
specifier: ^1.14.0
version: 1.14.0(typescript@5.9.2)
valibot:
specifier: ^1.1.0
version: 1.1.0(typescript@5.9.2)
devDependencies:
'@eslint/compat':
specifier: ^1.3.2