Files
codegraph/__tests__/react-heuristic-same-file.test.ts
Colby MchenryandClaude Opus 5.5 f976a050a0 fix(react): hook and context heuristics pick only the file's own in JS/TS (#2240)
A JS/TS module reaches another file's hook or context only through an import
(already the import resolver's), so resolveHook/resolveContext pick from the
reference's own file alone there; an optional call `f?.(…)` is a bare call.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 03:31:22 +00:00

66 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* In a JS/TS module another file's binding is reached only through an
* import — the import resolver's to follow — so the React resolver's hook
* and context heuristics pick from the reference's own file alone. trpc's
* WebSocket adapter's `createContext?.(…)` (an option) went to an example
* app's `createContext`, and bulletproof-react's per-app `useUser()` to
* another app's hook.
*/
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-react-heur-'));
const files: Record<string, string> = {
'package.json': JSON.stringify({ name: 'trpc', private: true, dependencies: { react: '^18.0.0' } }),
'examples/next-app/src/server/context.ts': `export async function createContext() {
return {};
}
`,
'apps/nextjs-app/src/lib/auth.tsx': `export function useUser() {
return null;
}
`,
'packages/server/src/adapters/ws.ts': `export function getHandler(opts: any) {
const { createContext } = opts;
return createContext?.({});
}
`,
'apps/react-vite/src/lib/auth.tsx': `const { useUser } = configureAuth({});
export function AuthLoader() {
const user = useUser();
return user;
}
`,
};
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 targetFilesFrom = (file: string) => {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => cg.getNode(e.target)!.filePath);
};
describe('React name heuristics in JS/TS', () => {
it('never reach another file’s hook or context by name', () => {
expect(targetFilesFrom('packages/server/src/adapters/ws.ts')).not.toContain('examples/next-app/src/server/context.ts');
expect(targetFilesFrom('apps/react-vite/src/lib/auth.tsx')).not.toContain('apps/nextjs-app/src/lib/auth.tsx');
});
});