mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-02 09:45:39 +08:00
The Express-style route extractor attributed an inline arrow handler's calls
to the route by bare name - `c.text('Hello')` became `text` - so each
matched whatever same-named symbol the project had: hono's 419 `c.text(...)`
routes called its client's `ClientResponse.text`, `c.html(...)` its `html`
helper, `c.req.parseBody()` a utility `parseBody`. A member call now keeps
its receiver (`c.text`, `userService.lookup`) and resolves like any member
call; a member of an expression (`a.b().c(`) names nothing to follow. Both
the plain and the `router.route(...).get(...)` forms share the scanner.
hono -538 +71 (`Context::text` / `Context::html` where the receiver
resolves; `c.req.*` 3-link chains now stay unresolved like any other
untyped chain); express, react-redux-realworld, trpc, kit, BookStack
unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
/**
|
|
* The calls an inline route handler makes keep their receivers. Read bare,
|
|
* hono's `app.get('/', (c) => c.text('Hello'))` called the one other `text`
|
|
* in the project — its client's `ClientResponse.text` — 419 times; kept as
|
|
* `c.text` it resolves like any other member call, and `userService.lookup()`
|
|
* reaches the service it names.
|
|
*/
|
|
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
let root = '';
|
|
let cg: CodeGraph;
|
|
|
|
beforeAll(async () => {
|
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-route-inline-'));
|
|
const files: Record<string, string> = {
|
|
'package.json': JSON.stringify({ name: 'api', dependencies: { hono: '^4' } }),
|
|
'src/client/types.ts': `export interface ClientResponse {
|
|
text(): Promise<string>;
|
|
}
|
|
`,
|
|
'src/services.ts': `export const userService = {
|
|
lookup(id: number) {
|
|
return { id };
|
|
},
|
|
};
|
|
`,
|
|
'src/routes/app.ts': `import { Hono } from 'hono';
|
|
import { userService } from '../services';
|
|
|
|
const app = new Hono();
|
|
|
|
app.get('/hello', (c) => c.text('Hello'));
|
|
|
|
app.get('/users/:id', async (c) => {
|
|
const user = await userService.lookup(1);
|
|
return c.json(user);
|
|
});
|
|
|
|
export default app;
|
|
`,
|
|
};
|
|
for (const [rel, content] of Object.entries(files)) {
|
|
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, rel), content);
|
|
}
|
|
cg = await CodeGraph.init(root, { index: true });
|
|
});
|
|
|
|
afterAll(() => {
|
|
cg?.close();
|
|
if (root) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
const routeCalls = (name: string) => {
|
|
const route = cg.getNodesByKind('route').find((r) => r.name.endsWith(name));
|
|
if (!route) return [];
|
|
return cg.getOutgoingEdges(route.id).filter((e) => e.kind === 'calls').map((e) => {
|
|
const t = cg.getNode(e.target)!;
|
|
return `${t.filePath}: ${t.qualifiedName}`;
|
|
}).sort();
|
|
};
|
|
|
|
describe('inline route handler calls', () => {
|
|
it('are not a same-named method of an unrelated type', () => {
|
|
expect(routeCalls('/hello')).not.toContain('src/client/types.ts: ClientResponse::text');
|
|
});
|
|
|
|
it('reach the service a member call names', () => {
|
|
expect(routeCalls('/users/:id')).toContain('src/services.ts: lookup');
|
|
});
|
|
});
|