Files
codegraph/__tests__/framework-hint-resolver-only.test.ts
T
Eric MinishandClaude Opus 5.5 5526e33d1d fix(status): recommend a re-index when package.json alone adds or drops a framework
Since PR #2941, a sync records the frameworks that came or went since the
last full index, and `codegraph status` recommends a re-index. But a sync
detects frameworks again only when it extracts a file, so adding `express`
or `@sveltejs/kit` to package.json with no source file edited recorded
nothing: status said the index was up to date while an unchanged Express
app's routes, or the `goto('/login')` in an unchanged +layout.svelte,
stayed unlinked until a full index.

A full index now also stamps the resolver's framework set, and
getFrameworkChangesSinceIndex() adds the difference between that stamp and
the set the resolver detects now (it detects on every open) to what syncs
recorded. So a fresh `codegraph status` sees a manifest-only change whether
or not a sync ran. An index built before this has no stamp, so that half
starts at its next full index. A long-running server's resolver keeps its
old set until it extracts a file, as before.

Validation on main 61776927 (Windows 11):
- Red/green: 3 of the 4 new tests fail with main's src/index.ts; the 4th,
  an unchanged project staying silent, passes on both. All 4 pass with the
  fix, as do the 8 sync-framework-detection tests.
- The CLI before and after, on a SvelteKit fixture indexed without
  @sveltejs/kit, then given it in package.json, then synced: main's status
  says "Index is up to date", and --json has reindexRecommended false and
  frameworksChangedSinceIndex null. With the fix, status adds "Frameworks
  changed since the last full index (added sveltekit-router)", and --json
  has true and {added: [sveltekit-router]}. Dropping the dependency gives
  {removed: [sveltekit-router]}, adding express to an Express app gives
  {added: [express]}, an unchanged project gives null on both arms, and a
  full `codegraph index` clears the advice.
- No false advice: a fresh index and then `status --json` on 18 real repos
  reports no change on any. They include express, flask, gin, vuejs/core,
  excalidraw, vuejs/docs, starlight, codegraph's own source and vscode's
  base and editor; Express, Flask, Next.js, React, Vue, Vue Router, Astro,
  Svelte, Spring, Go and UIKit are detected among them.

Original PR #3005 by @inth3shadows.
Fixes #3007

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:36:12 +00:00

104 lines
4.0 KiB
TypeScript

/**
* `codegraph status` recommends a re-index when a framework arrives or leaves
* through `package.json` alone.
*
* #2516 records the frameworks a full index extracted with, and a sync that
* detects a different set says the files indexed before it lack the change. A
* sync detects again only when it extracts a file, though: add `@sveltejs/kit`
* (or `express`) to `package.json` with no source file edited and nothing is
* recorded, so status stayed silent while `goto('/login')` in an unchanged
* `+layout.svelte` (or an unchanged Express app's routes) stayed unlinked.
*/
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
const KIT = JSON.stringify({ name: 'app', devDependencies: { '@sveltejs/kit': '^2.0.0', svelte: '^4.0.0' } });
const NO_KIT = JSON.stringify({ name: 'app', devDependencies: { svelte: '^4.0.0' } });
describe('framework changes since the last full index, from package.json alone', () => {
let root: string;
let cg: CodeGraph | undefined;
const write = (rel: string, content: string) => {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
};
const navigates = (): number => {
const nodes = cg!.getNodesInFile('src/routes/+layout.svelte');
return cg!.getOutgoingEdgesFrom(nodes.map((n) => n.id)).filter((e) => e.kind === 'navigates').length;
};
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-fw-hint-'));
write('src/routes/+layout.svelte', '<script>\n import { goto } from "$app/navigation";\n function out() { goto("/login"); }\n</script>\n');
write('src/routes/login/+page.svelte', '<h1>login</h1>\n');
});
afterEach(() => {
cg?.destroy();
cg = undefined;
fs.rmSync(root, { recursive: true, force: true });
});
it('names the SvelteKit router when @sveltejs/kit is added after the index', async () => {
write('package.json', NO_KIT);
cg = CodeGraph.initSync(root);
await cg.indexAll();
expect(cg.getFrameworkChangesSinceIndex()).toBeNull();
write('package.json', KIT);
cg.close();
cg = CodeGraph.openSync(root);
await cg.sync();
// The unchanged layout's goto is still unlinked: only a re-index links it...
expect(navigates()).toBe(0);
// ...which is what status must now say.
expect(cg.getFrameworkChangesSinceIndex()?.added).toContain('sveltekit-router');
cg.close();
cg = await CodeGraph.recreate(root);
await cg.indexAll();
expect(navigates()).toBe(1);
expect(cg.getFrameworkChangesSinceIndex()).toBeNull();
});
it('names it as removed when the dependency goes', async () => {
write('package.json', KIT);
cg = CodeGraph.initSync(root);
await cg.indexAll();
expect(navigates()).toBe(1);
write('package.json', NO_KIT);
cg.close();
cg = CodeGraph.openSync(root);
expect(cg.getFrameworkChangesSinceIndex()?.removed).toContain('sveltekit-router');
});
it('names Express when it is added with no source file changed', async () => {
write('package.json', JSON.stringify({ name: 'app', dependencies: {} }));
write('src/server.js', "const express = require('express');\nconst app = express();\napp.get('/hello', (req, res) => res.send('hi'));\n");
cg = CodeGraph.initSync(root);
await cg.indexAll();
expect(cg.getFrameworkChangesSinceIndex()).toBeNull();
write('package.json', JSON.stringify({ name: 'app', dependencies: { express: '^4.0.0' } }));
cg.close();
cg = CodeGraph.openSync(root);
await cg.sync();
expect(cg.getFrameworkChangesSinceIndex()?.added).toContain('express');
});
it('says nothing when the project is as it was indexed', async () => {
write('package.json', KIT);
cg = CodeGraph.initSync(root);
await cg.indexAll();
cg.close();
cg = CodeGraph.openSync(root);
await cg.sync();
expect(cg.getFrameworkChangesSinceIndex()).toBeNull();
});
});