diff --git a/.claude/skills/codegraph-build-validation/SKILL.md b/.claude/skills/codegraph-build-validation/SKILL.md
index 57a659830d..189e99b2f9 100644
--- a/.claude/skills/codegraph-build-validation/SKILL.md
+++ b/.claude/skills/codegraph-build-validation/SKILL.md
@@ -97,6 +97,12 @@ Behavior that differs by platform (path resolution, drive letters, `SENSITIVE_PA
The development host and default test target are Ubuntu under WSL. Run CodeGraph build and test commands through `fnm exec --using codegraph` so they use the supported Node 24 runtime, and have a Rust toolchain on PATH for the kernel. Platform-sensitive changes (file watching, sockets or named pipes, paths and symlinks, process lifecycle, and inotify limits) still need validation on every affected operating system.
+The **Highlight process lifecycle** pull-request job runs
+`node scripts/check-highlight-lifecycle.cjs` on Linux, Windows and macOS. It
+transpiles the production process adapter and uses a deliberately stuck helper
+to check deadline handling, queued requests, reaping, restart and shutdown. It
+does not load native grammars; native parse correctness uses the engine tests.
+
### Linux and containers
Run Linux validation directly in WSL. Use Docker only when a clean container or PID-1 behavior is part of the test; Docker is optional isolation, not the default Linux route.
diff --git a/.github/workflows/highlight-lifecycle.yml b/.github/workflows/highlight-lifecycle.yml
new file mode 100644
index 0000000000..5e391977d5
--- /dev/null
+++ b/.github/workflows/highlight-lifecycle.yml
@@ -0,0 +1,28 @@
+name: Highlight process lifecycle
+on:
+ pull_request:
+ branches: [fork/consolidated]
+ paths:
+ - 'src/ui-server/highlight/**'
+ - 'scripts/check-highlight-lifecycle.cjs'
+ - '.github/workflows/highlight-lifecycle.yml'
+permissions:
+ contents: read
+jobs:
+ lifecycle:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v7.0.1
+ with:
+ persist-credentials: false
+ - uses: actions/setup-node@v7.0.0
+ with:
+ node-version: 24
+ cache: npm
+ - run: npm ci --ignore-scripts
+ - run: node scripts/check-highlight-lifecycle.cjs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b32a366807..34bcaa9698 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -301,6 +301,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Lua built-in calls avoid unrelated table methods, while locally bound aliases of project or host table members follow their declared owner.
- PHP class names follow namespaces and imports, Objective-C conformance reaches the protocol, and C calls avoid C++ methods. Dart unnamed extensions stay local and extension methods require receiver evidence; R calls skip non-function data bindings.
- Delegation through another object avoids false self-calls, and production references avoid unrelated test declarations or unproven test doubles. Calls also avoid mangled names in minified JavaScript.
+- CommonJS requires and module-path imports link files to the exact module they name, including directory entries and configured aliases. Quoted examples and locally shadowed `require` calls do not add dependencies.
+- Vue, Svelte and Astro components own their instance script execution and members while preserving nested function scopes and module scripts. Nuxt and Astro pages call their own component.
+- Rails resource action filters recognize symbol and word arrays, strings, and hash-rocket options in both flat and scoped routes.
- Express inline function-expression handlers, including wrapped handlers and trailing commas, link routes to their body calls. The last argument selects the handler; functions in parameter defaults do not replace its body. Calls retain their lexical scope, and external `require` bindings remain external when framework references are resolved.
- A barrel's `export * as core from './core'` now exports only `core`, not everything in `./core`. In zod, `z.string()` and `z.number()` had been landing on same-named regex constants deep in its core module instead of the schema functions. Calls through `import * as ns` also find members a barrel forwards by name (`export { safeParse } from '../core'`). They follow a namespace path like `z.coerce.number()` too.
- JavaScript calls through CommonJS `require` bindings now reach the right function. `var express = require('../')` follows `module.exports` through an `index.js` to the function it exports, and every binding in a `var a = require('x'), b = require('y')` list is read. `require('./utils').setCharset` means that module's `setCharset` rather than its first export, and a call to a package from outside the project (`supertest`, `node:assert`) is no longer an edge to the local variable. Re-index affected JavaScript projects after upgrading.
diff --git a/README.md b/README.md
index d6ae643cdb..9637eed8dd 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.
## About this fork
-This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains all of upstream `main` (last merged: [`81e08b7`](https://github.com/colbymchenry/codegraph/commit/81e08b76), after v1.6.1, 2026-10-01) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.
+This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains all of upstream `main` (last merged: [`e12f5bbb`](https://github.com/colbymchenry/codegraph/commit/e12f5bbb925b91e0fd1f641c67db34065cc6b7f4), after v1.6.1, 2026-10-01) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.
The fork publishes no releases. The install scripts, npm package, badges and `codegraph upgrade` further down this page install **upstream's** releases. To run the fork, build it from source (below).
@@ -102,6 +102,8 @@ Compared with upstream `main` at `290e03f`. Each item was checked against upstre
The other languages are the same in both, listed under [Supported Languages](#supported-languages).
+Vue, Svelte and Astro files have one file node containing their component. The component contains top-level script members, while nested functions and methods keep their own parents. Vue `
+`,
+ 'lib/orders.ts': 'export function readOrders() { return 1; }',
+ 'pages/fetch.vue': 'fetch page
',
+ 'pages/admin.vue': `admin
+`,
+ 'pages/orders/index.vue': `orders
+`,
+ 'components/index.vue': `not a page
+`,
+ },
+ astro: {
+ 'package.json': JSON.stringify({ name: 'site', private: true, dependencies: { astro: '^4.0.0' } }),
+ 'src/pages/index.astro': `---
+import { readTitle } from '../lib/title';
+const title = readTitle();
+---
+
{title}
+`,
+ 'src/lib/title.ts': 'export function readTitle() { return \"Home\"; }',
+ 'src/pages/fetch.astro': 'fetch page
',
+ 'src/pages/about.astro': `about
+`,
+ 'src/pages/blog/index.astro': `blog
+`,
+ 'src/pages/api/hello.ts': `export const GET = async () => new Response('hi');
+export async function POST() { return new Response('ok'); }
+`,
+ },
+};
+
+const graphs: Record = {};
+
+beforeAll(async () => {
+ for (const [name, files] of Object.entries(projects)) {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `cg-page-${name}-`));
+ 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);
+ }
+ graphs[name] = { root, cg: await CodeGraph.init(root, { index: true }) };
+ }
+});
+
+afterAll(() => {
+ for (const { root, cg } of Object.values(graphs)) {
+ cg.close();
+ fs.rmSync(root, { recursive: true, force: true });
+ }
+});
+
+/** route name → the files of what it calls or references. */
+function served(cg: CodeGraph): Record {
+ const out: Record = {};
+ for (const route of cg.getNodesByKind('route')) {
+ out[route.name] = cg.getOutgoingEdgesFrom([route.id], ['calls', 'references'])
+ .map((e) => cg.getNode(e.target)!)
+ .map((n) => `${n.kind} ${n.filePath}`);
+ }
+ return out;
+}
+
+describe('a Nuxt page', () => {
+ it('is served by its own file’s component', () => {
+ const map = served(graphs.nuxt!.cg);
+ expect(map['/']).toEqual(['component pages/index.vue']);
+ expect(map['/fetch']).toEqual(['component pages/fetch.vue']);
+ expect(map['/admin']).toEqual(['component pages/admin.vue']);
+ expect(map['/orders']).toEqual(['component pages/orders/index.vue']);
+ });
+
+ it('is listed with the component that serves it', () => {
+ const { entries } = buildRoutes(graphs.nuxt!.cg, new URLSearchParams());
+ expect(entries.map((e) => `${e.url} ${e.handler} ${e.file}`).sort()).toEqual([
+ '/ index pages/index.vue',
+ '/admin admin pages/admin.vue',
+ '/fetch fetch pages/fetch.vue',
+ '/orders index pages/orders/index.vue',
+ ]);
+ });
+});
+
+describe('an Astro page', () => {
+ it('is served by its own file’s component, and an endpoint by the verbs it exports', () => {
+ const cg = graphs.astro!.cg;
+ const map = served(cg);
+ expect(map['/']).toEqual(['component src/pages/index.astro']);
+ expect(map['/fetch']).toEqual(['component src/pages/fetch.astro']);
+ expect(map['/about']).toEqual(['component src/pages/about.astro']);
+ expect(map['/blog']).toEqual(['component src/pages/blog/index.astro']);
+ for (const verb of ['GET', 'POST']) {
+ expect(map[`${verb} /api/hello`]).toEqual(['function src/pages/api/hello.ts']);
+ const route = cg.getNodesByKind('route').find((node) => node.name === `${verb} /api/hello`)!;
+ const targets = cg.getOutgoingEdgesFrom([route.id], ['calls', 'references']).map((edge) => cg.getNode(edge.target)!.name);
+ expect(targets).toContain(verb);
+ }
+ });
+});
+
+
+it('connects each page through its own component to an imported helper', () => {
+ for (const [project, page, helper] of [
+ ['nuxt', 'pages/index.vue', 'lib/orders.ts'],
+ ['astro', 'src/pages/index.astro', 'src/lib/title.ts'],
+ ]) {
+ const cg = graphs[project!]!.cg;
+ const route = cg.getNodesByKind('route').find((node) => node.name === '/')!;
+ const component = cg.getOutgoingEdgesFrom([route.id], ['calls']).map((edge) => cg.getNode(edge.target)!).find((node) => node.kind === 'component')!;
+ expect(component.filePath).toBe(page);
+ const owned = [component.id, ...cg.getOutgoingEdgesFrom([component.id], ['contains']).map((edge) => edge.target)];
+ const targets = cg.getOutgoingEdgesFrom(owned, ['calls']).map((edge) => cg.getNode(edge.target)!);
+ expect(targets.some((node) => node.filePath === helper && node.kind === 'function')).toBe(true);
+ }
+});
diff --git a/__tests__/fixtures/golden/sfc-mix.dump b/__tests__/fixtures/golden/sfc-mix.dump
index 3ee48d717c..7429d6f925 100644
--- a/__tests__/fixtures/golden/sfc-mix.dump
+++ b/__tests__/fixtures/golden/sfc-mix.dump
@@ -10,10 +10,10 @@
{"id":"constant:731fa565cbbeb3c83ef6f360af0deffb","kind":"constant","name":"panelKind","qualified_name":"panelKind","file_path":"src/lib/Panel.svelte","language":"svelte","start_line":2,"end_line":2,"start_column":15,"end_column":33,"docstring":null,"signature":"= \"info\"","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"constant:a603db0f72cd274c10bfb9baaf23a035","kind":"constant","name":"heading","qualified_name":"heading","file_path":"src/pages/index.astro","language":"astro","start_line":6,"end_line":6,"start_column":6,"end_column":41,"docstring":null,"signature":"= formatCount(items.length)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"file:Components/GreetingService.cs","kind":"file","name":"GreetingService.cs","qualified_name":"Components/GreetingService.cs","file_path":"Components/GreetingService.cs","language":"csharp","start_line":1,"end_line":6,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/lib/Counter.svelte","kind":"file","name":"Counter.svelte","qualified_name":"src/lib/Counter.svelte","file_path":"src/lib/Counter.svelte","language":"svelte","start_line":1,"end_line":9,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/lib/Panel.svelte","kind":"file","name":"Panel.svelte","qualified_name":"src/lib/Panel.svelte","file_path":"src/lib/Panel.svelte","language":"svelte","start_line":4,"end_line":8,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/lib/Counter.svelte","kind":"file","name":"Counter.svelte","qualified_name":"src/lib/Counter.svelte","file_path":"src/lib/Counter.svelte","language":"svelte","start_line":1,"end_line":16,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/lib/Panel.svelte","kind":"file","name":"Panel.svelte","qualified_name":"src/lib/Panel.svelte","file_path":"src/lib/Panel.svelte","language":"svelte","start_line":1,"end_line":11,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"file:src/lib/store.ts","kind":"file","name":"store.ts","qualified_name":"src/lib/store.ts","file_path":"src/lib/store.ts","language":"typescript","start_line":1,"end_line":5,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/pages/index.astro","kind":"file","name":"index.astro","qualified_name":"src/pages/index.astro","file_path":"src/pages/index.astro","language":"astro","start_line":13,"end_line":16,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/pages/index.astro","kind":"file","name":"index.astro","qualified_name":"src/pages/index.astro","file_path":"src/pages/index.astro","language":"astro","start_line":1,"end_line":19,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:0026a1cae8e888523240c893a10aa70d","kind":"function","name":"increment","qualified_name":"increment","file_path":"src/lib/store.ts","language":"typescript","start_line":3,"end_line":3,"start_column":7,"end_column":65,"docstring":null,"signature":"(): void","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:9777ee45a9be2e3fad362e78e1d7b6f9","kind":"function","name":"shout","qualified_name":"shout","file_path":"src/pages/index.astro","language":"astro","start_line":5,"end_line":5,"start_column":0,"end_column":61,"docstring":null,"signature":"(s: string): string","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:d2e12acb9fe4991a6ddced760347b343","kind":"function","name":"onReady","qualified_name":"onReady","file_path":"src/pages/index.astro","language":"astro","start_line":14,"end_line":14,"start_column":6,"end_column":56,"docstring":null,"signature":"(): void","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
@@ -30,20 +30,19 @@
{"id":"variable:5158b0a1f92af32a12f69947ecb9bc16","kind":"variable","name":"doubled","qualified_name":"doubled","file_path":"src/lib/Counter.svelte","language":"svelte","start_line":4,"end_line":4,"start_column":6,"end_column":17,"docstring":null,"signature":"= 0","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:91a83e89785d63330141724a9e77ecaf","kind":"variable","name":"title","qualified_name":"title","file_path":"src/lib/Panel.svelte","language":"svelte","start_line":6,"end_line":6,"start_column":13,"end_column":18,"docstring":null,"signature":null,"visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:b010b506bf4b046bcce67431112fcc62","kind":"variable","name":"label","qualified_name":"label","file_path":"src/lib/Counter.svelte","language":"svelte","start_line":3,"end_line":3,"start_column":13,"end_column":38,"docstring":null,"signature":"= \"Counter\"","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-== edges (60)
+== edges (46)
{"source":"class:e0436e1c421fad6fb415ebf1250fc957","target":"method:0fb9589fa48847b470e36febaa20fd6a","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:3c652b3a8468352ce722877b478d750c","target":"class:9250e5f7a14d90af56522055875d3efc","kind":"instantiates","metadata":"{\"confidence\":0.5,\"resolvedBy\":\"exact-match\",\"refName\":\"GreetRequest\"}","line":13,"col":18,"provenance":null}
{"source":"component:3c652b3a8468352ce722877b478d750c","target":"class:e0436e1c421fad6fb415ebf1250fc957","kind":"references","metadata":"{\"confidence\":0.5,\"resolvedBy\":\"exact-match\",\"refName\":\"GreetingService\"}","line":2,"col":0,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"component:f618e74c9653d19f14a4517ffbcc5a50","kind":"references","metadata":"{\"framework\":\"astro\",\"confidence\":0.8,\"resolvedBy\":\"framework\",\"refName\":\"Panel\"}","line":12,"col":5,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"constant:065e374572e8fdbbdc1b3f23a2d4fae7","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"constant:a603db0f72cd274c10bfb9baaf23a035","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"file:src/pages/index.astro","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"function:9777ee45a9be2e3fad362e78e1d7b6f9","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"exact-match\",\"refName\":\"shout\"}","line":11,"col":30,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"function:9777ee45a9be2e3fad362e78e1d7b6f9","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"function:d2e12acb9fe4991a6ddced760347b343","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatCount\"}","line":6,"col":16,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"import:5768886a079628a326b519ee5dd8ec52","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:9ab44aceec99c2241ddd988791f90540","target":"import:7e12aa398bd31cc8ad3c376496090ad5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:c072dde69c5406b8fc182d1339f31b54","target":"file:src/lib/Counter.svelte","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:c072dde69c5406b8fc182d1339f31b54","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatCount\"}","line":11,"col":40,"provenance":null}
{"source":"component:c072dde69c5406b8fc182d1339f31b54","target":"function:ed48ef0a7c92c2fe0641a87932a6e507","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:c072dde69c5406b8fc182d1339f31b54","target":"import:bd7c59a8ed1bfac8345e8ab60e865a83","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -51,46 +50,33 @@
{"source":"component:c072dde69c5406b8fc182d1339f31b54","target":"variable:b010b506bf4b046bcce67431112fcc62","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"component:c072dde69c5406b8fc182d1339f31b54","kind":"references","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"Counter\"}","line":10,"col":31,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"constant:731fa565cbbeb3c83ef6f360af0deffb","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"file:src/lib/Panel.svelte","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"function:d4e27dd1f8cc937cd2912ff3cfb54de3","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"exact-match\",\"refName\":\"describe\"}","line":10,"col":13,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"function:d4e27dd1f8cc937cd2912ff3cfb54de3","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"import:b6babedd191b7c5407cfcb5599bf90ef","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:f618e74c9653d19f14a4517ffbcc5a50","target":"variable:91a83e89785d63330141724a9e77ecaf","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"constant:a603db0f72cd274c10bfb9baaf23a035","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatCount\"}","line":6,"col":16,"provenance":null}
{"source":"file:Components/GreetingService.cs","target":"class:9250e5f7a14d90af56522055875d3efc","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:Components/GreetingService.cs","target":"class:e0436e1c421fad6fb415ebf1250fc957","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/lib/Counter.svelte","target":"component:c072dde69c5406b8fc182d1339f31b54","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/Counter.svelte","target":"constant:3fe6a7023d4d5ff3237fe178e2f0759f","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"count\"}","line":2,"col":11,"provenance":null}
{"source":"file:src/lib/Counter.svelte","target":"file:src/lib/store.ts","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"./store\"}","line":2,"col":2,"provenance":null}
{"source":"file:src/lib/Counter.svelte","target":"function:0026a1cae8e888523240c893a10aa70d","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"increment\"}","line":2,"col":18,"provenance":null}
{"source":"file:src/lib/Counter.svelte","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatCount\"}","line":2,"col":29,"provenance":null}
-{"source":"file:src/lib/Counter.svelte","target":"function:ed48ef0a7c92c2fe0641a87932a6e507","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/lib/Counter.svelte","target":"import:bd7c59a8ed1bfac8345e8ab60e865a83","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/lib/Counter.svelte","target":"variable:5158b0a1f92af32a12f69947ecb9bc16","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/lib/Counter.svelte","target":"variable:b010b506bf4b046bcce67431112fcc62","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/Panel.svelte","target":"component:c072dde69c5406b8fc182d1339f31b54","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"Counter\"}","line":5,"col":9,"provenance":null}
-{"source":"file:src/lib/Panel.svelte","target":"constant:731fa565cbbeb3c83ef6f360af0deffb","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/lib/Panel.svelte","target":"component:f618e74c9653d19f14a4517ffbcc5a50","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/Panel.svelte","target":"file:src/lib/Counter.svelte","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"./Counter.svelte\"}","line":5,"col":2,"provenance":null}
-{"source":"file:src/lib/Panel.svelte","target":"function:d4e27dd1f8cc937cd2912ff3cfb54de3","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/lib/Panel.svelte","target":"import:b6babedd191b7c5407cfcb5599bf90ef","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/lib/Panel.svelte","target":"variable:91a83e89785d63330141724a9e77ecaf","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/store.ts","target":"constant:3fe6a7023d4d5ff3237fe178e2f0759f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/store.ts","target":"function:0026a1cae8e888523240c893a10aa70d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/store.ts","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/lib/store.ts","target":"import:953305409c84a199d8d2b49218329392","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/pages/index.astro","target":"component:9ab44aceec99c2241ddd988791f90540","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/pages/index.astro","target":"component:f618e74c9653d19f14a4517ffbcc5a50","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"Panel\"}","line":2,"col":7,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"constant:065e374572e8fdbbdc1b3f23a2d4fae7","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"constant:a603db0f72cd274c10bfb9baaf23a035","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/pages/index.astro","target":"file:src/lib/Panel.svelte","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"../lib/Panel.svelte\"}","line":2,"col":0,"provenance":null}
{"source":"file:src/pages/index.astro","target":"file:src/lib/store.ts","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"../lib/store\"}","line":3,"col":0,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"function:9777ee45a9be2e3fad362e78e1d7b6f9","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"function:d2e12acb9fe4991a6ddced760347b343","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/pages/index.astro","target":"function:d2e12acb9fe4991a6ddced760347b343","kind":"references","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"function-ref\",\"refName\":\"onReady\",\"refKind\":\"function_ref\",\"fnRef\":true}","line":15,"col":52,"provenance":null}
{"source":"file:src/pages/index.astro","target":"function:e532a19cd6548d38325d9c3c5fab5190","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatCount\"}","line":3,"col":9,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"import:5768886a079628a326b519ee5dd8ec52","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/pages/index.astro","target":"import:7e12aa398bd31cc8ad3c376496090ad5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"function:ed48ef0a7c92c2fe0641a87932a6e507","target":"function:0026a1cae8e888523240c893a10aa70d","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"increment\"}","line":7,"col":4,"provenance":null}
{"source":"method:0fb9589fa48847b470e36febaa20fd6a","target":"class:9250e5f7a14d90af56522055875d3efc","kind":"references","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"exact-match\",\"refName\":\"GreetRequest\"}","line":4,"col":24,"provenance":null}
-{"source":"route:src/pages/index.astro:/:1","target":"component:9ab44aceec99c2241ddd988791f90540","kind":"references","metadata":"{\"framework\":\"astro\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"astro-page-component\"}","line":1,"col":0,"provenance":null}
+{"source":"route:src/pages/index.astro:/:1","target":"component:9ab44aceec99c2241ddd988791f90540","kind":"calls","metadata":"{\"framework\":\"astro\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"page-component:index\"}","line":1,"col":0,"provenance":null}
== refs (9)
{"from_node_id":"component:3c652b3a8468352ce722877b478d750c","reference_name":"Greeter.Greet","reference_kind":"calls","line":14,"col":18,"candidates":null,"file_path":"Components/Greeter.razor","language":"razor","status":"failed","name_tail":"Greet"}
{"from_node_id":"component:9ab44aceec99c2241ddd988791f90540","reference_name":"items.map","reference_kind":"calls","line":11,"col":8,"candidates":null,"file_path":"src/pages/index.astro","language":"astro","status":"failed","name_tail":"map","failure_reason":"unknown-receiver"}
@@ -129,6 +115,6 @@
{"path":"Components/Greeter.razor","language":"razor","node_count":1}
{"path":"Components/GreetingService.cs","language":"csharp","node_count":4}
{"path":"src/lib/Counter.svelte","language":"svelte","node_count":6}
-{"path":"src/lib/Panel.svelte","language":"svelte","node_count":7}
+{"path":"src/lib/Panel.svelte","language":"svelte","node_count":6}
{"path":"src/lib/store.ts","language":"typescript","node_count":5}
-{"path":"src/pages/index.astro","language":"astro","node_count":10}
+{"path":"src/pages/index.astro","language":"astro","node_count":9}
diff --git a/__tests__/fixtures/golden/tail-langs.dump b/__tests__/fixtures/golden/tail-langs.dump
index 2e2f02b3a3..2bff48eab6 100644
--- a/__tests__/fixtures/golden/tail-langs.dump
+++ b/__tests__/fixtures/golden/tail-langs.dump
@@ -132,7 +132,7 @@
{"id":"variable:d66985bd46f73c589bf626a476ca4dd9","kind":"variable","name":"name","qualified_name":"name","file_path":"nix/module.nix","language":"nix","start_line":6,"end_line":6,"start_column":4,"end_column":61,"docstring":null,"signature":"= mkOption { type = types.str; default = \"world\"; }","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:ec44d88622b9664503f7698cd63e9df9","kind":"variable","name":"options.services.greeter","qualified_name":"options.services.greeter","file_path":"nix/module.nix","language":"nix","start_line":4,"end_line":7,"start_column":2,"end_column":4,"docstring":null,"signature":"= {\n enable = mkEnableOption \"greeter\";\n name = mkOption { type = types.str; default = \"world\"; ...","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:fc46c3957e062c09f1d7dfd0c4e39e68","kind":"variable","name":"WS-COUNT","qualified_name":"WS-COUNT","file_path":"cobol/GREETWS.cpy","language":"cobol","start_line":2,"end_line":2,"start_column":7,"end_column":50,"docstring":null,"signature":"01 WS-COUNT PIC 9(4) VALUE ZERO","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-== edges (161)
+== edges (162)
{"source":"class:1354e5dd108f836e76d61a4ecedb6262","target":"class:ef405d3eb35a6ee244944eec0c93b6d4","kind":"extends","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"exact-match\",\"refName\":\"Ownable\"}","line":5,"col":0,"provenance":null}
{"source":"class:1354e5dd108f836e76d61a4ecedb6262","target":"field:084206ff4dcb8c3b848851b6f90bb652","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"class:1354e5dd108f836e76d61a4ecedb6262","target":"field:d5355ecdd0413fd3b56cdb0e7f75e0d5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -173,6 +173,7 @@
{"source":"class:ef405d3eb35a6ee244944eec0c93b6d4","target":"method:8774622dcc04ad898541b769e736065f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"class:ef405d3eb35a6ee244944eec0c93b6d4","target":"method:bce8c6cce85fec8bd2df9223eaf629de","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:arkts/Greeter.ets","target":"class:3e6591165ac27455fccf18fc888a0823","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:arkts/Greeter.ets","target":"file:arkts/util.ets","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"./util\"}","line":1,"col":0,"provenance":null}
{"source":"file:arkts/Greeter.ets","target":"function:5616f3b8fc746c8c0c09a11ece0dbf24","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"formatName\"}","line":1,"col":9,"provenance":null}
{"source":"file:arkts/Greeter.ets","target":"import:00cbe392ac9a605f9cae41475a23f438","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:arkts/Greeter.ets","target":"struct:00b863e6b6e7bd6569a36e94f72d3aaa","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -294,11 +295,10 @@
{"source":"struct:00b863e6b6e7bd6569a36e94f72d3aaa","target":"property:81982deaee358e155a33af725d85bd97","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"struct:8050a2eb89168a19b5ac70b577047b96","target":"field:e2c04abad409b6092124d38ce58616bb","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"variable:62c285bb0e5c30272251db2b4966175f","target":"class:8da3dc4378a8ad4becad38a01a3d4d63","kind":"references","metadata":"{\"framework\":\"terraform\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"aws_instance.web\"}","line":14,"col":26,"provenance":null}
-== refs (55)
+== refs (54)
{"from_node_id":"class:85e303095ea33303cdff1b72c25fb6db","reference_name":"IGreeter","reference_kind":"implements","line":1,"col":43,"candidates":null,"file_path":"cfml/Greeter.cfc","language":"cfml","status":"failed","name_tail":"IGreeter"}
{"from_node_id":"class:8da3dc4378a8ad4becad38a01a3d4d63","reference_name":"module.network:output.subnet_id","reference_kind":"references","line":10,"col":18,"candidates":null,"file_path":"terraform/main.tf","language":"terraform","status":"failed","name_tail":"subnet_id"}
{"from_node_id":"class:aa6851d523fe7c352fff4cc32fdcb79f","reference_name":"NSObject","reference_kind":"extends","line":5,"col":21,"candidates":null,"file_path":"objc/Greeter.h","language":"objc","status":"failed","name_tail":"NSObject"}
-{"from_node_id":"file:arkts/Greeter.ets","reference_name":"./util","reference_kind":"imports","line":1,"col":0,"candidates":null,"file_path":"arkts/Greeter.ets","language":"arkts","status":"failed","name_tail":"/util"}
{"from_node_id":"file:nix/default.nix","reference_name":"builtins.concatStringsSep","reference_kind":"calls","line":14,"col":25,"candidates":null,"file_path":"nix/default.nix","language":"nix","status":"failed","name_tail":"concatStringsSep"}
{"from_node_id":"file:nix/default.nix","reference_name":"map","reference_kind":"calls","line":7,"col":14,"candidates":null,"file_path":"nix/default.nix","language":"nix","status":"failed","name_tail":"map"}
{"from_node_id":"file:nix/default.nix","reference_name":"map","reference_kind":"calls","line":9,"col":9,"candidates":null,"file_path":"nix/default.nix","language":"nix","status":"failed","name_tail":"map"}
diff --git a/__tests__/fixtures/golden/torture-multilang.dump b/__tests__/fixtures/golden/torture-multilang.dump
index c3b10a2193..7cdaa71673 100644
--- a/__tests__/fixtures/golden/torture-multilang.dump
+++ b/__tests__/fixtures/golden/torture-multilang.dump
@@ -1486,7 +1486,7 @@
{"id":"variable:fb450996c12f335b36201ead67a34f64","kind":"variable","name":"INIT_MAP","qualified_name":"INIT_MAP","file_path":"torture.py","language":"python","start_line":54,"end_line":54,"start_column":0,"end_column":26,"docstring":null,"signature":"= {\"a\": helper()}","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:feb330d554c5d0caaa8978323a006e52","kind":"variable","name":"MD_BARE","qualified_name":"MD_BARE","file_path":"torture.rb","language":"ruby","start_line":238,"end_line":238,"start_column":0,"end_column":21,"docstring":null,"signature":"= 'README.md'","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"variable:fedacda34e94b85718ac655a74368326","kind":"variable","name":"okG","qualified_name":"okG","file_path":"torture.lua","language":"lua","start_line":12,"end_line":12,"start_column":6,"end_column":9,"docstring":null,"signature":"= pcall(require, \"app.guarded\")","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-== edges (1919)
+== edges (1915)
{"source":"class:0083dc18baa2b629e8572db20ba2b464","target":"class:25244cbdbe3e9bc720df9fc4d54fac0a","kind":"extends","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"qualified-name\",\"refName\":\"app::Config\"}","line":98,"col":35,"provenance":null}
{"source":"class:0083dc18baa2b629e8572db20ba2b464","target":"method:0795ac46f5bef3826c20039a05014ebc","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"class:0083dc18baa2b629e8572db20ba2b464","target":"method:35a476809e2727e80d7f2c8c404180b2","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -2799,13 +2799,9 @@
{"source":"file:torture.tsx","target":"function:f00a265ed388e20f65b3357ab065773e","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:torture.tsx","target":"function:f11355267c15b025e65a025974c2a4d1","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:torture.tsx","target":"import:29c3cc32eeee07c3c2b96bf6f1b275d2","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:torture.tsx","target":"import:29c3cc32eeee07c3c2b96bf6f1b275d2","kind":"imports","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"qualified-name\",\"refName\":\"./side-effect\"}","line":7,"col":0,"provenance":null}
{"source":"file:torture.tsx","target":"import:4935bc32d5f33e8f95c3cb009bf0935d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:torture.tsx","target":"import:4935bc32d5f33e8f95c3cb009bf0935d","kind":"imports","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"qualified-name\",\"refName\":\"./default-module\"}","line":6,"col":0,"provenance":null}
{"source":"file:torture.tsx","target":"import:7bff5cb286a60587ad04b8eed5de0e67","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:torture.tsx","target":"import:7bff5cb286a60587ad04b8eed5de0e67","kind":"imports","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"qualified-name\",\"refName\":\"./namespace-module\"}","line":5,"col":0,"provenance":null}
{"source":"file:torture.tsx","target":"import:92cf06e18d12ccca19cc4fcd24ce1065","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:torture.tsx","target":"import:92cf06e18d12ccca19cc4fcd24ce1065","kind":"imports","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"qualified-name\",\"refName\":\"./helpers\"}","line":8,"col":0,"provenance":null}
{"source":"file:torture.tsx","target":"import:d3f11476b3c70d68f9f9724f3e6ab699","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:torture.tsx","target":"interface:a05baa699fc9324830859524f09604c9","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:torture.tsx","target":"type_alias:600473b4a3158649443b2f214febf906","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -3406,7 +3402,7 @@
{"source":"variable:36e608fb368763b7f8a74a69f477cec7","target":"function:692077921d04b315880bc35a0bc872a9","kind":"references","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"function-ref\",\"refName\":\"view\",\"refKind\":\"function_ref\",\"fnRef\":true}","line":49,"col":24,"provenance":null}
{"source":"variable:6b7262d2883839c6f601126f4dd59e9b","target":"function:d8f9c30c4ee99a9302a150e8eed67acf","kind":"references","metadata":"{\"confidence\":0.95,\"resolvedBy\":\"function-ref\",\"refName\":\"TargetCb\",\"refKind\":\"function_ref\",\"fnRef\":true}","line":14,"col":9,"provenance":null}
{"source":"variable:788d75a828c79f9847e0be0e2b73a196","target":"variable:8fef25ea8abe486ee94190ed47c3644b","kind":"references","metadata":"{\"valueRef\":true}","line":null,"col":null,"provenance":null}
-== refs (1295)
+== refs (1299)
{"from_node_id":"class:01a3945fa12793f1af9ac531aedcc8cd","reference_name":"Mix2","reference_kind":"implements","line":34,"col":37,"candidates":null,"file_path":"TortureMini.dart","language":"dart","status":"failed","name_tail":"Mix2"}
{"from_node_id":"class:01a3945fa12793f1af9ac531aedcc8cd","reference_name":"Paint","reference_kind":"implements","line":34,"col":59,"candidates":null,"file_path":"TortureMini.dart","language":"dart","status":"failed","name_tail":"Paint"}
{"from_node_id":"class:066eb79e83cf3105cebd14593747068f","reference_name":"BaseService","reference_kind":"extends","line":13,"col":14,"candidates":null,"file_path":"torture.py","language":"python","status":"failed","name_tail":"BaseService"}
@@ -3747,7 +3743,11 @@
{"from_node_id":"file:torture.scala","reference_name":"xs.drop","reference_kind":"calls","line":117,"col":54,"candidates":null,"file_path":"torture.scala","language":"scala","status":"failed","name_tail":"drop"}
{"from_node_id":"file:torture.swift","reference_name":"Foundation","reference_kind":"imports","line":1,"col":0,"candidates":null,"file_path":"torture.swift","language":"swift","status":"failed","name_tail":"Foundation"}
{"from_node_id":"file:torture.swift","reference_name":"TortureKit","reference_kind":"imports","line":213,"col":0,"candidates":null,"file_path":"torture.swift","language":"swift","status":"failed","name_tail":"TortureKit"}
+{"from_node_id":"file:torture.tsx","reference_name":"./default-module","reference_kind":"imports","line":6,"col":0,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"/default-module"}
{"from_node_id":"file:torture.tsx","reference_name":"./dynamic-module","reference_kind":"imports","line":211,"col":0,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"/dynamic-module"}
+{"from_node_id":"file:torture.tsx","reference_name":"./helpers","reference_kind":"imports","line":8,"col":0,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"/helpers"}
+{"from_node_id":"file:torture.tsx","reference_name":"./namespace-module","reference_kind":"imports","line":5,"col":0,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"/namespace-module"}
+{"from_node_id":"file:torture.tsx","reference_name":"./side-effect","reference_kind":"imports","line":7,"col":0,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"/side-effect"}
{"from_node_id":"file:torture.tsx","reference_name":"CONFIG_TABLE","reference_kind":"imports","line":8,"col":19,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"CONFIG_TABLE"}
{"from_node_id":"file:torture.tsx","reference_name":"DefaultThing","reference_kind":"function_ref","line":201,"col":31,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"DefaultThing"}
{"from_node_id":"file:torture.tsx","reference_name":"DefaultThing","reference_kind":"imports","line":6,"col":7,"candidates":null,"file_path":"torture.tsx","language":"tsx","status":"failed","name_tail":"DefaultThing"}
diff --git a/__tests__/fixtures/golden/vue-sfc.dump b/__tests__/fixtures/golden/vue-sfc.dump
index 40a73a9d51..d359ad5660 100644
--- a/__tests__/fixtures/golden/vue-sfc.dump
+++ b/__tests__/fixtures/golden/vue-sfc.dump
@@ -14,13 +14,13 @@
{"id":"constant:cc53d30032c6a64950637c56908b7d0b","kind":"constant","name":"router","qualified_name":"router","file_path":"src/router/index.ts","language":"typescript","start_line":2,"end_line":9,"start_column":6,"end_column":2,"docstring":null,"signature":"= createRouter({\n history: createWebHistory(),\n routes: [\n { name: \"home\", path: \"/\", component: ...","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"constant:d9be2d3d04398687cd5215bbe3161ba5","kind":"constant","name":"title","qualified_name":"title","file_path":"src/views/Home.vue","language":"vue","start_line":13,"end_line":13,"start_column":6,"end_column":87,"docstring":null,"signature":"= computed(() => (session.loggedIn ? `Hi ${session.username}` : \"Welcome\"))","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"constant:ff0949df989967149797da1892030a1d","kind":"constant","name":"name","qualified_name":"name","file_path":"src/views/Login.vue","language":"vue","start_line":8,"end_line":8,"start_column":6,"end_column":20,"docstring":null,"signature":"= ref(\"\")","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/components/PlayerTable.vue","kind":"file","name":"PlayerTable.vue","qualified_name":"src/components/PlayerTable.vue","file_path":"src/components/PlayerTable.vue","language":"vue","start_line":4,"end_line":9,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/components/TheHeader.vue","kind":"file","name":"TheHeader.vue","qualified_name":"src/components/TheHeader.vue","file_path":"src/components/TheHeader.vue","language":"vue","start_line":4,"end_line":7,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/components/PlayerTable.vue","kind":"file","name":"PlayerTable.vue","qualified_name":"src/components/PlayerTable.vue","file_path":"src/components/PlayerTable.vue","language":"vue","start_line":1,"end_line":14,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/components/TheHeader.vue","kind":"file","name":"TheHeader.vue","qualified_name":"src/components/TheHeader.vue","file_path":"src/components/TheHeader.vue","language":"vue","start_line":1,"end_line":8,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"file:src/router/index.ts","kind":"file","name":"index.ts","qualified_name":"src/router/index.ts","file_path":"src/router/index.ts","language":"typescript","start_line":1,"end_line":11,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"file:src/stores/session.ts","kind":"file","name":"session.ts","qualified_name":"src/stores/session.ts","file_path":"src/stores/session.ts","language":"typescript","start_line":1,"end_line":10,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/views/Home.vue","kind":"file","name":"Home.vue","qualified_name":"src/views/Home.vue","file_path":"src/views/Home.vue","language":"vue","start_line":4,"end_line":17,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/views/Login.vue","kind":"file","name":"Login.vue","qualified_name":"src/views/Login.vue","file_path":"src/views/Login.vue","language":"vue","start_line":4,"end_line":15,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-{"id":"file:src/views/Profile.vue","kind":"file","name":"Profile.vue","qualified_name":"src/views/Profile.vue","file_path":"src/views/Profile.vue","language":"vue","start_line":4,"end_line":12,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/views/Home.vue","kind":"file","name":"Home.vue","qualified_name":"src/views/Home.vue","file_path":"src/views/Home.vue","language":"vue","start_line":1,"end_line":21,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/views/Login.vue","kind":"file","name":"Login.vue","qualified_name":"src/views/Login.vue","file_path":"src/views/Login.vue","language":"vue","start_line":1,"end_line":16,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
+{"id":"file:src/views/Profile.vue","kind":"file","name":"Profile.vue","qualified_name":"src/views/Profile.vue","file_path":"src/views/Profile.vue","language":"vue","start_line":1,"end_line":13,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:085111786b044269e1e486b05662945a","kind":"function","name":"logout","qualified_name":"logout","file_path":"src/stores/session.ts","language":"typescript","start_line":7,"end_line":7,"start_column":4,"end_column":53,"docstring":null,"signature":"()","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:6be3944381225a6a828e91729caf5788","kind":"function","name":"login","qualified_name":"login","file_path":"src/stores/session.ts","language":"typescript","start_line":6,"end_line":6,"start_column":4,"end_column":92,"docstring":null,"signature":"(username: string, token: string)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"function:8d98565edda11804927409e8e272013e","kind":"function","name":"submit","qualified_name":"submit","file_path":"src/views/Login.vue","language":"vue","start_line":11,"end_line":14,"start_column":0,"end_column":1,"docstring":null,"signature":"()","visibility":null,"is_exported":0,"is_async":1,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
@@ -41,16 +41,17 @@
{"id":"route:src/router/index.ts:5:/:vue","kind":"route","name":"/","qualified_name":"src/router/index.ts::route:/","file_path":"src/router/index.ts","language":"typescript","start_line":5,"end_line":5,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"route:src/router/index.ts:6:/login:vue","kind":"route","name":"/login","qualified_name":"src/router/index.ts::route:/login","file_path":"src/router/index.ts","language":"typescript","start_line":6,"end_line":6,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
{"id":"route:src/router/index.ts:7:/profile/:username:vue","kind":"route","name":"/profile/:username","qualified_name":"src/router/index.ts::route:/profile/:username","file_path":"src/router/index.ts","language":"typescript","start_line":7,"end_line":7,"start_column":0,"end_column":0,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null}
-== edges (82)
+== edges (63)
+{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"component:4adc8e1e4bb22917b580e3d56a1ea521","kind":"calls","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":10,"col":15,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"component:a2b972002bba2cab19e7c5b8376f5608","kind":"calls","metadata":"{\"synthesizedBy\":\"jsx-render\",\"via\":\"TheHeader\"}","line":1,"col":null,"provenance":"heuristic"}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"component:a2b972002bba2cab19e7c5b8376f5608","kind":"references","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"TheHeader\"}","line":2,"col":21,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"component:d243da7dd7c0285f73fbc46926de4830","kind":"calls","metadata":"{\"synthesizedBy\":\"jsx-render\",\"via\":\"PlayerTable\"}","line":1,"col":null,"provenance":"heuristic"}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"component:d243da7dd7c0285f73fbc46926de4830","kind":"references","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"PlayerTable\"}","line":2,"col":60,"provenance":null}
+{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":11,"col":16,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"constant:5d677039776558e912ac55676ec98100","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"constant:751e16382875beaa12f2dc749e1d9872","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"constant:c14c87c0b0c5b511e512bce492e77e4d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"constant:d9be2d3d04398687cd5215bbe3161ba5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"file:src/views/Home.vue","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"function:acb5cc12f5a5aac4fe728346a0195105","kind":"calls","metadata":"{\"synthesizedBy\":\"vue-handler\",\"event\":\"go\"}","line":1,"col":null,"provenance":"heuristic"}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"function:acb5cc12f5a5aac4fe728346a0195105","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"import:027a889525f1e6e315129a7ad281251d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -58,32 +59,26 @@
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"import:b13abb7ec1b5f328bf02153acc3271a5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"import:e479f74976640946cd942a1c120728fd","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:4adc8e1e4bb22917b580e3d56a1ea521","target":"import:e725b097897b9381f422d2cc6a84390d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:51ec8fb32fb2e5eb0334651e5437dbb3","target":"file:src/views/Profile.vue","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:51ec8fb32fb2e5eb0334651e5437dbb3","target":"method:714c8d0d55d742be8fb59cfdaeecefc2","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:a2b972002bba2cab19e7c5b8376f5608","target":"constant:a9f2e86d1d4a623601d625c820f6d4f8","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:a2b972002bba2cab19e7c5b8376f5608","target":"file:src/components/TheHeader.vue","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:a2b972002bba2cab19e7c5b8376f5608","target":"route:src/router/index.ts:6:/login:vue","kind":"navigates","metadata":"{\"synthesizedBy\":\"vue-router-link\",\"href\":\"/login\",\"navMethod\":\"link\",\"registeredAt\":\"src/components/TheHeader.vue:2\"}","line":2,"col":null,"provenance":"heuristic"}
+{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"component:bb25dfa092c88625d329317b0410e2cf","kind":"calls","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":9,"col":15,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"constant:17aec3feee6b90e31758cb1b41bc1451","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":10,"col":16,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"constant:cb450172e958b905f9dac8f66786c11a","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"constant:ff0949df989967149797da1892030a1d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"file:src/views/Login.vue","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"function:8d98565edda11804927409e8e272013e","kind":"calls","metadata":"{\"synthesizedBy\":\"vue-handler\",\"event\":\"submit\"}","line":1,"col":null,"provenance":"heuristic"}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"function:8d98565edda11804927409e8e272013e","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"import:18d11ae5a76f290915b58b51dd533f3f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"import:cb1810af8536de8faf29efc9265c1557","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"import:f38ceaeccfd0e13483a209bbc3005ae3","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:bb25dfa092c88625d329317b0410e2cf","target":"route:src/router/index.ts:5:/:vue","kind":"navigates","metadata":"{\"synthesizedBy\":\"vue-router-link\",\"href\":\"home\",\"navMethod\":\"link\",\"by\":\"name\",\"registeredAt\":\"src/views/Login.vue:2\"}","line":2,"col":null,"provenance":"heuristic"}
-{"source":"component:d243da7dd7c0285f73fbc46926de4830","target":"file:src/components/PlayerTable.vue","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"component:d243da7dd7c0285f73fbc46926de4830","target":"function:cbf5b3b6d9cf6f3f971b79716cb24ec1","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"constant:17aec3feee6b90e31758cb1b41bc1451","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":10,"col":16,"provenance":null}
-{"source":"constant:751e16382875beaa12f2dc749e1d9872","target":"constant:751e16382875beaa12f2dc749e1d9872","kind":"calls","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":10,"col":15,"provenance":null}
-{"source":"constant:c14c87c0b0c5b511e512bce492e77e4d","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"calls","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":11,"col":16,"provenance":null}
-{"source":"constant:cb450172e958b905f9dac8f66786c11a","target":"constant:cb450172e958b905f9dac8f66786c11a","kind":"calls","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":9,"col":15,"provenance":null}
{"source":"constant:cc53d30032c6a64950637c56908b7d0b","target":"component:4adc8e1e4bb22917b580e3d56a1ea521","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/views/Home.vue\"}","line":5,"col":48,"provenance":null}
{"source":"constant:cc53d30032c6a64950637c56908b7d0b","target":"component:51ec8fb32fb2e5eb0334651e5437dbb3","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/views/Profile.vue\"}","line":7,"col":68,"provenance":null}
{"source":"constant:cc53d30032c6a64950637c56908b7d0b","target":"component:bb25dfa092c88625d329317b0410e2cf","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/views/Login.vue\"}","line":6,"col":54,"provenance":null}
-{"source":"file:src/components/PlayerTable.vue","target":"function:cbf5b3b6d9cf6f3f971b79716cb24ec1","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/components/TheHeader.vue","target":"constant:a9f2e86d1d4a623601d625c820f6d4f8","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/components/PlayerTable.vue","target":"component:d243da7dd7c0285f73fbc46926de4830","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/components/TheHeader.vue","target":"component:a2b972002bba2cab19e7c5b8376f5608","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/router/index.ts","target":"constant:cc53d30032c6a64950637c56908b7d0b","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/router/index.ts","target":"import:5d64257bf8c0a7bc1b0e85cb44911c7f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/stores/session.ts","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
@@ -91,33 +86,19 @@
{"source":"file:src/stores/session.ts","target":"function:6be3944381225a6a828e91729caf5788","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/stores/session.ts","target":"function:98084f78440e902f0fb6526f37df2e46","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/stores/session.ts","target":"import:4c3d03fe5b468700553c025bde17c5da","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/views/Home.vue","target":"component:4adc8e1e4bb22917b580e3d56a1ea521","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/views/Home.vue","target":"component:a2b972002bba2cab19e7c5b8376f5608","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"TheHeader\"}","line":7,"col":7,"provenance":null}
{"source":"file:src/views/Home.vue","target":"component:a2b972002bba2cab19e7c5b8376f5608","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/components/TheHeader.vue\"}","line":7,"col":0,"provenance":null}
{"source":"file:src/views/Home.vue","target":"component:d243da7dd7c0285f73fbc46926de4830","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"PlayerTable\"}","line":8,"col":7,"provenance":null}
{"source":"file:src/views/Home.vue","target":"component:d243da7dd7c0285f73fbc46926de4830","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/components/PlayerTable.vue\"}","line":8,"col":0,"provenance":null}
{"source":"file:src/views/Home.vue","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":9,"col":9,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"constant:5d677039776558e912ac55676ec98100","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"constant:751e16382875beaa12f2dc749e1d9872","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"constant:c14c87c0b0c5b511e512bce492e77e4d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"constant:d9be2d3d04398687cd5215bbe3161ba5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/views/Home.vue","target":"file:src/stores/session.ts","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/stores/session\"}","line":9,"col":0,"provenance":null}
{"source":"file:src/views/Home.vue","target":"file:src/views/Home.vue","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":6,"col":9,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"function:acb5cc12f5a5aac4fe728346a0195105","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"import:027a889525f1e6e315129a7ad281251d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"import:abb463c3ae2e046fb414b1cc9e6791d0","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"import:b13abb7ec1b5f328bf02153acc3271a5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"import:e479f74976640946cd942a1c120728fd","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Home.vue","target":"import:e725b097897b9381f422d2cc6a84390d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"constant:17aec3feee6b90e31758cb1b41bc1451","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/views/Login.vue","target":"component:bb25dfa092c88625d329317b0410e2cf","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/views/Login.vue","target":"constant:452cdd44fc910418f24644cca18f6ebf","kind":"imports","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"import\",\"refName\":\"useSessionStore\"}","line":7,"col":9,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"constant:cb450172e958b905f9dac8f66786c11a","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"constant:ff0949df989967149797da1892030a1d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"file:src/views/Login.vue","target":"file:src/stores/session.ts","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":0.9,\"resolvedBy\":\"framework\",\"refName\":\"@/stores/session\"}","line":7,"col":0,"provenance":null}
{"source":"file:src/views/Login.vue","target":"file:src/views/Login.vue","kind":"imports","metadata":"{\"framework\":\"vue\",\"confidence\":1,\"resolvedBy\":\"framework\",\"refName\":\"useRouter\"}","line":6,"col":9,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"function:8d98565edda11804927409e8e272013e","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"import:18d11ae5a76f290915b58b51dd533f3f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"import:cb1810af8536de8faf29efc9265c1557","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
-{"source":"file:src/views/Login.vue","target":"import:f38ceaeccfd0e13483a209bbc3005ae3","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
+{"source":"file:src/views/Profile.vue","target":"component:51ec8fb32fb2e5eb0334651e5437dbb3","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null}
{"source":"function:8d98565edda11804927409e8e272013e","target":"function:6be3944381225a6a828e91729caf5788","kind":"calls","metadata":"{\"synthesizedBy\":\"pinia-store\",\"via\":\"login\",\"registeredAt\":\"src/views/Login.vue:12\"}","line":12,"col":null,"provenance":"heuristic"}
{"source":"function:8d98565edda11804927409e8e272013e","target":"route:src/router/index.ts:5:/:vue","kind":"navigates","metadata":"{\"href\":\"home\",\"navMethod\":\"push\",\"by\":\"name\",\"framework\":\"vue-router\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"router.push\",\"refKind\":\"calls\"}","line":13,"col":8,"provenance":null}
{"source":"function:acb5cc12f5a5aac4fe728346a0195105","target":"route:src/router/index.ts:5:/:vue","kind":"navigates","metadata":"{\"href\":\"/\",\"navMethod\":\"push\",\"framework\":\"vue-router\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"router.push\",\"refKind\":\"calls\"}","line":15,"col":2,"provenance":null}
@@ -125,16 +106,16 @@
{"source":"route:src/router/index.ts:6:/login:vue","target":"component:bb25dfa092c88625d329317b0410e2cf","kind":"calls","metadata":"{\"framework\":\"vue-router\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"import:@/views/Login.vue#Login\"}","line":6,"col":0,"provenance":null}
{"source":"route:src/router/index.ts:7:/profile/:username:vue","target":"component:51ec8fb32fb2e5eb0334651e5437dbb3","kind":"calls","metadata":"{\"framework\":\"vue-router\",\"confidence\":0.95,\"resolvedBy\":\"framework\",\"refName\":\"import:@/views/Profile.vue#Profile\"}","line":7,"col":0,"provenance":null}
== refs (24)
+{"from_node_id":"component:4adc8e1e4bb22917b580e3d56a1ea521","reference_name":"computed","reference_kind":"calls","line":13,"col":14,"candidates":null,"file_path":"src/views/Home.vue","language":"vue","status":"failed","name_tail":"computed"}
+{"from_node_id":"component:4adc8e1e4bb22917b580e3d56a1ea521","reference_name":"ref","reference_kind":"calls","line":12,"col":13,"candidates":null,"file_path":"src/views/Home.vue","language":"vue","status":"failed","name_tail":"ref"}
{"from_node_id":"component:a2b972002bba2cab19e7c5b8376f5608","reference_name":"RouterLink","reference_kind":"references","line":2,"col":77,"candidates":null,"file_path":"src/components/TheHeader.vue","language":"vue","status":"failed","name_tail":"RouterLink"}
+{"from_node_id":"component:a2b972002bba2cab19e7c5b8376f5608","reference_name":"defineEmits","reference_kind":"calls","line":6,"col":13,"candidates":null,"file_path":"src/components/TheHeader.vue","language":"vue","status":"failed","name_tail":"defineEmits"}
+{"from_node_id":"component:a2b972002bba2cab19e7c5b8376f5608","reference_name":"defineProps","reference_kind":"calls","line":5,"col":0,"candidates":null,"file_path":"src/components/TheHeader.vue","language":"vue","status":"failed","name_tail":"defineProps"}
{"from_node_id":"component:bb25dfa092c88625d329317b0410e2cf","reference_name":"RouterLink","reference_kind":"references","line":2,"col":58,"candidates":null,"file_path":"src/views/Login.vue","language":"vue","status":"failed","name_tail":"RouterLink"}
-{"from_node_id":"constant:5d677039776558e912ac55676ec98100","reference_name":"ref","reference_kind":"calls","line":12,"col":13,"candidates":null,"file_path":"src/views/Home.vue","language":"vue","status":"failed","name_tail":"ref"}
-{"from_node_id":"constant:a9f2e86d1d4a623601d625c820f6d4f8","reference_name":"defineEmits","reference_kind":"calls","line":6,"col":13,"candidates":null,"file_path":"src/components/TheHeader.vue","language":"vue","status":"failed","name_tail":"defineEmits"}
+{"from_node_id":"component:bb25dfa092c88625d329317b0410e2cf","reference_name":"ref","reference_kind":"calls","line":8,"col":13,"candidates":null,"file_path":"src/views/Login.vue","language":"vue","status":"failed","name_tail":"ref"}
+{"from_node_id":"component:d243da7dd7c0285f73fbc46926de4830","reference_name":"defineProps","reference_kind":"calls","line":5,"col":0,"candidates":null,"file_path":"src/components/PlayerTable.vue","language":"vue","status":"failed","name_tail":"defineProps"}
{"from_node_id":"constant:cc53d30032c6a64950637c56908b7d0b","reference_name":"createRouter","reference_kind":"calls","line":2,"col":15,"candidates":null,"file_path":"src/router/index.ts","language":"typescript","status":"failed","name_tail":"createRouter"}
{"from_node_id":"constant:cc53d30032c6a64950637c56908b7d0b","reference_name":"createWebHistory","reference_kind":"calls","line":3,"col":11,"candidates":null,"file_path":"src/router/index.ts","language":"typescript","status":"failed","name_tail":"createWebHistory"}
-{"from_node_id":"constant:d9be2d3d04398687cd5215bbe3161ba5","reference_name":"computed","reference_kind":"calls","line":13,"col":14,"candidates":null,"file_path":"src/views/Home.vue","language":"vue","status":"failed","name_tail":"computed"}
-{"from_node_id":"constant:ff0949df989967149797da1892030a1d","reference_name":"ref","reference_kind":"calls","line":8,"col":13,"candidates":null,"file_path":"src/views/Login.vue","language":"vue","status":"failed","name_tail":"ref"}
-{"from_node_id":"file:src/components/PlayerTable.vue","reference_name":"defineProps","reference_kind":"calls","line":5,"col":0,"candidates":null,"file_path":"src/components/PlayerTable.vue","language":"vue","status":"failed","name_tail":"defineProps"}
-{"from_node_id":"file:src/components/TheHeader.vue","reference_name":"defineProps","reference_kind":"calls","line":5,"col":0,"candidates":null,"file_path":"src/components/TheHeader.vue","language":"vue","status":"failed","name_tail":"defineProps"}
{"from_node_id":"file:src/router/index.ts","reference_name":"createRouter","reference_kind":"imports","line":1,"col":9,"candidates":null,"file_path":"src/router/index.ts","language":"typescript","status":"failed","name_tail":"createRouter"}
{"from_node_id":"file:src/router/index.ts","reference_name":"createWebHistory","reference_kind":"imports","line":1,"col":23,"candidates":null,"file_path":"src/router/index.ts","language":"typescript","status":"failed","name_tail":"createWebHistory"}
{"from_node_id":"file:src/router/index.ts","reference_name":"vue-router","reference_kind":"imports","line":1,"col":0,"candidates":null,"file_path":"src/router/index.ts","language":"typescript","status":"failed","name_tail":"vue-router"}
diff --git a/__tests__/flow-directed-exact-names.test.ts b/__tests__/flow-directed-exact-names.test.ts
new file mode 100644
index 0000000000..6636c837fa
--- /dev/null
+++ b/__tests__/flow-directed-exact-names.test.ts
@@ -0,0 +1,66 @@
+/**
+ * A directed Flow question ("how does X reach Y") names its two ends exactly,
+ * as the viewer's links write them — a symbol's own name. Names the free-text
+ * token filter drops were lost: an Objective-C selector (`initWithFrame:`), a
+ * Ruby predicate (`valid?`), a two-letter method (`ok`). The other end was
+ * then reported as naming nothing, though it was in the index.
+ */
+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';
+import { resolveNamedSymbolFlow } from '../src/graph/named-symbol-flow';
+
+let root = '';
+let cg: CodeGraph;
+
+beforeAll(async () => {
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-flow-exact-'));
+ const files: Record = {
+ 'src/check.ts': `export function ok(): number {
+ return go();
+}
+
+export function go(): number {
+ return 1;
+}
+`,
+ 'app/models/order.rb': `class Order
+ def save!
+ raise ArgumentError unless valid?
+ end
+
+ def valid?
+ true
+ end
+end
+`,
+ };
+ 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 directed = (from: string, to: string) =>
+ resolveNamedSymbolFlow(cg, `${from} ${to}`, { mode: 'directed', from, to });
+
+describe('a directed flow question', () => {
+ it('reaches a two-letter name', () => {
+ const flow = directed('ok', 'go');
+ expect(flow.chains[0]?.steps.map((s) => s.node.name)).toEqual(['ok', 'go']);
+ });
+
+ it('reaches a name with punctuation', () => {
+ const flow = directed('save!', 'valid?');
+ expect(flow.tokens).toEqual(['save!', 'valid?']);
+ expect(flow.chains[0]?.steps.map((s) => s.node.name)).toEqual(['save!', 'valid?']);
+ });
+});
diff --git a/__tests__/rails-only-except-forms.test.ts b/__tests__/rails-only-except-forms.test.ts
new file mode 100644
index 0000000000..c72e582e3c
--- /dev/null
+++ b/__tests__/rails-only-except-forms.test.ts
@@ -0,0 +1,57 @@
+/**
+ * A `resources` line's `only:` / `except:` is read however Rails lets it be
+ * written. maybe's `resources :family_exports, only: %i[new create index]` was
+ * not read, so it drew all seven RESTful routes — four of them to actions the
+ * controller does not have, which the viewer listed as routes bound to nothing.
+ */
+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-rails-only-'));
+ const files: Record = {
+ 'Gemfile': 'source "https://rubygems.org"\ngem "rails", "~> 7.1"\n',
+ 'config/routes.rb': `Rails.application.routes.draw do
+ resources :family_exports, only: %i[new create index] do
+ member do
+ get :download
+ end
+ end
+ resources :tags, except: %w(destroy edit update)
+ resources :imports, :only => [:index, :show]
+ resource :session, only: "show"
+end
+`,
+ };
+ 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 routes = (controller: string) =>
+ cg.getNodesByKind('route').map((r) => r.qualifiedName).filter((q) => q.includes(`${controller}#`)).map((q) => q.slice(q.lastIndexOf('#') + 1)).sort();
+
+describe('Rails resources only: / except:', () => {
+ it('reads %i[] and %w() lists', () => {
+ expect(routes('family_exports')).toEqual(['create', 'download', 'index', 'new']);
+ expect(routes('tags')).toEqual(['create', 'index', 'new', 'show']);
+ });
+
+ it('reads the hash-rocket and string forms', () => {
+ expect(routes('imports')).toEqual(['index', 'show']);
+ expect(routes('sessions')).toEqual(['show']);
+ });
+});
diff --git a/__tests__/request-parse-budget.test.ts b/__tests__/request-parse-budget.test.ts
new file mode 100644
index 0000000000..5c275e26d8
--- /dev/null
+++ b/__tests__/request-parse-budget.test.ts
@@ -0,0 +1,11 @@
+import { it, expect } from 'vitest';
+import { parseWithinBudget } from '../src/extraction/parse-budget';
+
+it('cancels request parsing and reuses the parser for a different document', () => {
+ expect(parseWithinBudget('const oldValue = 1;\n'.repeat(20_000), 'typescript', 1)).toBeNull();
+ const tree = parseWithinBudget('const freshValue = 2;', 'typescript');
+ expect(tree).not.toBeNull();
+ expect(tree!.rootNode.text).toBe('const freshValue = 2;');
+ expect(tree!.rootNode.descendantsOfType('identifier').map((node) => node.text)).toContain('freshValue');
+ tree!.delete();
+});
diff --git a/__tests__/sfc-component-owns-script.test.ts b/__tests__/sfc-component-owns-script.test.ts
new file mode 100644
index 0000000000..a7a2d89672
--- /dev/null
+++ b/__tests__/sfc-component-owns-script.test.ts
@@ -0,0 +1,103 @@
+/**
+ * A Vue / Svelte / Astro file is one file node holding the component it is,
+ * and the component holds its script: each symbol has one parent, and a
+ * component never contains its own file. What a per-instance script does at
+ * its top level — `
+`);
+ const file = r.nodes.find((n) => n.kind === 'file')!;
+ const component = componentOf(r);
+ expect(file).toMatchObject({ id: 'file:pages/index.vue', startLine: 1, endLine: 9 });
+ const p = parents(r);
+ expect(p.get(component.id)).toEqual([file.id]);
+ expect(p.get(file.id)).toBeUndefined();
+ const load = r.nodes.find((n) => n.name === 'load')!;
+ expect(p.get(load.id)).toEqual([component.id]);
+ for (const [, list] of p) expect(list).toHaveLength(1);
+ });
+
+ it('gives a
+`);
+ expect(from(r, 'useFetch').filter((id) => id.startsWith('component:'))).toHaveLength(1);
+ expect(r.unresolvedReferences.filter((ref) => ref.referenceKind === 'imports').every((ref) => ref.fromNodeId === 'file:pages/index.vue')).toBe(true);
+ });
+
+ it('leaves a plain
+`);
+ expect(from(r, 'registerWidget')).toEqual(['file:src/Widget.vue']);
+ });
+
+ it('gives a Svelte instance script to the component, and a module script to the file', () => {
+ const r = extractFromSource('src/routes/+page.svelte', `
+
+
+items
+`);
+ const component = componentOf(r).id;
+ expect(from(r, 'onMount')).toEqual([component]);
+ expect(from(r, 'preload')).toEqual(['file:src/routes/+page.svelte']);
+ expect(from(r, 'register')).toEqual(['file:src/routes/+page.svelte']);
+ });
+
+ it('gives Astro frontmatter to the component, and makes a file node for a file without one', () => {
+ const r = extractFromSource('src/pages/blog.astro', `---
+const posts = await getCollection('blog');
+---
+{posts.map((p) => - {p.title}
)}
+
+`);
+ expect(from(r, 'getCollection')).toEqual([componentOf(r).id]);
+ expect(from(r, 'registerWidget')).toEqual(['file:src/pages/blog.astro']);
+ const bare = extractFromSource('src/pages/about.astro', `about
\n`);
+ expect(bare.nodes.map((n) => n.kind).sort()).toEqual(['component', 'file']);
+ });
+});
diff --git a/__tests__/ui-highlight-bounded.test.ts b/__tests__/ui-highlight-bounded.test.ts
new file mode 100644
index 0000000000..e0d0175a54
--- /dev/null
+++ b/__tests__/ui-highlight-bounded.test.ts
@@ -0,0 +1,24 @@
+import { describe, it, expect, afterAll } from 'vitest';
+import { highlightLines, clearHighlightCache } from '../src/ui-server/highlight';
+import { tokenizeBounded, stopHighlightWorker } from '../src/ui-server/highlight/bounded-tokenize';
+
+afterAll(() => stopHighlightWorker());
+
+describe('highlighting a slice', () => {
+ it('finishes a COBOL slice ending in the sequence area', async () => {
+ clearHighlightCache();
+ const started = Date.now();
+ const slice = await highlightLines([' PERFORM AssertOk', ' .'], { language: 'cobol' });
+ expect(slice.reason ?? '').not.toMatch(/took too long/);
+ expect(Date.now() - started).toBeLessThan(10_000);
+ const fine = await highlightLines(['export const x: number = 1;'], { language: 'typescript' });
+ expect(fine.engine).toBe('tree-sitter');
+ }, 20_000);
+
+ it('reaps a child past its deadline and classifies the next slice', async () => {
+ await stopHighlightWorker();
+ expect(await tokenizeBounded('export const x = 1;', 'typescript', 0)).toEqual({ timedOut: true });
+ const next = await tokenizeBounded('export const value: number = 1;', 'typescript');
+ expect('result' in next && next.result?.spans.length).toBeGreaterThan(0);
+ });
+});
diff --git a/__tests__/ui-highlight-orderings.test.ts b/__tests__/ui-highlight-orderings.test.ts
new file mode 100644
index 0000000000..997053c850
--- /dev/null
+++ b/__tests__/ui-highlight-orderings.test.ts
@@ -0,0 +1,109 @@
+import { it, expect, vi } from 'vitest';
+
+type Request = { id: number; text: string; language: string };
+type Child = { pid: number; killed: boolean; closed: boolean; sent: Request[]; finish(): void; emit(event: string, ...args: unknown[]): void };
+const state = vi.hoisted(() => ({ children: [] as Child[], nextPid: 1 }));
+vi.mock('fs', () => ({ existsSync: () => true }));
+vi.mock('child_process', async () => {
+ const { EventEmitter } = await import('events');
+ class Child extends EventEmitter {
+ pid = state.nextPid++;
+ killed = false;
+ closed = false;
+ exitCode: number | null = null;
+ signalCode: string | null = null;
+ channel = { unref() {} };
+ sent: Request[] = [];
+ unref() {}
+ ref() {}
+ send(message: Request, callback: (error: Error | null) => void) { this.sent.push(message); callback(null); }
+ kill() { this.killed = true; return true; }
+ finish() { if (this.closed) return; this.closed = true; this.exitCode = 0; this.emit('exit', 0); this.emit('close', 0); }
+ }
+ return { fork: () => { const child = new Child(); state.children.push(child); return child; } };
+});
+import { tokenizeBounded, stopHighlightWorker } from '../src/ui-server/highlight/bounded-tokenize';
+
+type Event = 'request' | 'reply' | 'timeout' | 'stop' | 'old-error' | 'old-close';
+const events: Event[] = ['request', 'reply', 'timeout', 'stop', 'old-error', 'old-close'];
+function sequences(prefix: Event[] = []): Event[][] {
+ if (prefix.length === 4) return [prefix];
+ return [prefix, ...events.flatMap((event) => sequences([...prefix, event]))];
+}
+
+it('isolates child generations and settles requests after every bounded event ordering', async () => {
+ vi.useFakeTimers();
+ const violations: string[] = [];
+ try {
+ for (const sequence of sequences()) {
+ state.children.length = 0;
+ const allowedKills = new Set();
+ const requests: Promise[] = [];
+ const stops: Promise[] = [];
+ let requestCount = 0;
+ const allowCurrent = () => { const child = state.children.at(-1); if (child && !child.closed) allowedKills.add(child.pid); };
+ for (const event of sequence) {
+ if (event === 'request') {
+ const label = `request-${++requestCount}`;
+ requests.push(tokenizeBounded(label, 'typescript', 10).then((result) => {
+ if ('result' in result && result.result && result.result.grammars[0] !== label) violations.push(`${sequence}: wrong reply for ${label}`);
+ }));
+ } else if (event === 'reply') {
+ const child = state.children.at(-1);
+ const message = child?.sent.at(-1);
+ if (message) child!.emit('message', { id: message.id, result: { spans: [], grammars: [message.text] } });
+ } else if (event === 'timeout') {
+ allowCurrent();
+ vi.advanceTimersByTime(10);
+ } else if (event === 'stop') {
+ allowCurrent();
+ stops.push(stopHighlightWorker());
+ } else {
+ const old = state.children.find((child) => child.killed && !child.closed);
+ if (event === 'old-error') old?.emit('error', new Error('old child'));
+ else old?.finish();
+ }
+ await Promise.resolve();
+ for (const child of state.children) if (child.killed && !allowedKills.has(child.pid)) violations.push(`${sequence}: stale event killed child ${child.pid}`);
+ }
+ allowCurrent();
+ stops.push(stopHighlightWorker());
+ for (const child of state.children) child.finish();
+ await Promise.all([...requests, ...stops]);
+ expect(vi.getTimerCount(), sequence.join(' → ')).toBe(0);
+ }
+ expect(violations).toEqual([]);
+ } finally {
+ for (const child of state.children) child.finish();
+ await stopHighlightWorker();
+ vi.useRealTimers();
+ }
+});
+
+
+it.each(['timeout', 'stop'] as const)('keeps a replacement child alive after %s then a stale error', async (event) => {
+ vi.useFakeTimers();
+ try {
+ const first = tokenizeBounded('first', 'typescript', 10);
+ const old = state.children.at(-1)!;
+ const stopping = event === 'stop' ? stopHighlightWorker() : Promise.resolve();
+ if (event === 'timeout') vi.advanceTimersByTime(10);
+ const second = tokenizeBounded('second', 'typescript', 10);
+ const replacement = state.children.at(-1)!;
+ old.emit('error', new Error('stale'));
+ expect(replacement.killed).toBe(false);
+ old.finish();
+ await Promise.all([first, stopping]);
+ const message = replacement.sent[0]!;
+ replacement.emit('message', { id: message.id, result: { spans: [], grammars: ['second'] } });
+ expect(await second).toEqual({ result: { spans: [], grammars: ['second'] } });
+ const finalStop = stopHighlightWorker();
+ replacement.finish();
+ await finalStop;
+ } finally {
+ const cleanup = stopHighlightWorker();
+ for (const child of state.children) child.finish();
+ await cleanup;
+ vi.useRealTimers();
+ }
+});
diff --git a/__tests__/ui-map-api.test.ts b/__tests__/ui-map-api.test.ts
index 75e083162c..fa73d07f4d 100644
--- a/__tests__/ui-map-api.test.ts
+++ b/__tests__/ui-map-api.test.ts
@@ -30,6 +30,7 @@ import {
pickDefaultDepth,
passThroughDirs,
pickDefaultRoot,
+ pickDefaultView,
resetMapCache,
} from '../src/ui-server/api/map';
@@ -641,3 +642,37 @@ describe('GET /api/map', () => {
expect(all.modules.map((m: any) => m.id)).not.toEqual(src.modules.map((m: any) => m.id));
});
});
+
+describe('the view the map opens on', () => {
+ it('counts files loose in the repository root as program', () => {
+ // git: hundreds of top-level `.c` files beside `builtin/`.
+ expect(
+ pickDefaultRoot([
+ { path: 'git.c', symbols: 60, test: false },
+ { path: 'refs.c', symbols: 50, test: false },
+ { path: 'builtin/add.c', symbols: 40, test: false },
+ { path: 'builtin/commit.c', symbols: 30, test: false },
+ ])
+ ).toBe('');
+ });
+
+ it('opens on the repository when the source folder is one flat folder', () => {
+ // Express: everything under `lib/` sits in `lib/` itself.
+ const files = [
+ ...Array.from({ length: 30 }, (_, i) => ({ path: `lib/f${i}.js`, symbols: 10, test: false })),
+ { path: 'index.js', symbols: 1, test: false },
+ { path: 'benchmarks/run.js', symbols: 3, test: false },
+ { path: 'test/app.js', symbols: 50, test: true },
+ ];
+ expect(pickDefaultView(files)).toEqual({ root: '', depth: 1 });
+ });
+
+ it('keeps a source folder that draws a picture of its own', () => {
+ const files = [
+ { path: 'src/a/x.ts', symbols: 40, test: false },
+ { path: 'src/b/y.ts', symbols: 40, test: false },
+ { path: 'scripts/z.ts', symbols: 2, test: false },
+ ];
+ expect(pickDefaultView(files).root).toBe('src');
+ });
+});
diff --git a/codegraph-kernel/grammars/PROVENANCE.md b/codegraph-kernel/grammars/PROVENANCE.md
index 8bbc3d862e..cd4686b159 100644
--- a/codegraph-kernel/grammars/PROVENANCE.md
+++ b/codegraph-kernel/grammars/PROVENANCE.md
@@ -19,3 +19,8 @@ Phase 4 notes record the kind-table drift.
| objc | npm tree-sitter-objc 2.1.0 (the revision tree-sitter-wasms 0.1.13 built; wasm sha 7c1b5bfdca7e64b6…) | package `src/` | ABI 14; replaces the crates.io 3.0.2 pin (35 kinds differed) |
| solidity | JoranHonig/tree-sitter-solidity | b239a95f (the revision tree-sitter-wasms 0.1.13 pinned) | ABI 14; replaces the crates.io 1.2.13 pin (39 kinds differed) |
| cfml, cfscript, cfquery, cfml-common | cfmleditor/tree-sitter-cfml | a224ff10 (2026-06-30, the last commit before the 2026-07-02 vendoring) | three grammars sharing `common/scanner.h` + `tag.h` (include paths rewritten to `../cfml-common/`); ABI 15 |
+
+The COBOL scanner also supports the `CGWIDE` sequence-area sentinel for
+preprocessed free-format source. Its sequence-area loop stops at EOF and
+CR/LF boundaries, so a short final line cannot spin at EOF or consume the
+next line. A boundary with no consumed sequence-area text returns no token.
diff --git a/codegraph-kernel/grammars/cobol/scanner.c b/codegraph-kernel/grammars/cobol/scanner.c
index 86e3d7e70c..4c4d74a676 100644
--- a/codegraph-kernel/grammars/cobol/scanner.c
+++ b/codegraph-kernel/grammars/cobol/scanner.c
@@ -129,8 +129,10 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
if(valid_symbols[LINE_PREFIX_COMMENT] && lexer->get_column(lexer) <= 5) {
// The sequence area is ignored content — but the free-format
// preprocessor plants the CGWIDE sentinel here on the first line.
+ const unsigned start_column = lexer->get_column(lexer);
int match = 0;
- while(lexer->get_column(lexer) <= 5) {
+ while(lexer->get_column(lexer) <= 5 && !lexer->eof(lexer) &&
+ lexer->lookahead != '\n' && lexer->lookahead != '\r') {
if(match >= 0 && match < 6 && lexer->lookahead == CG_WIDE_SENTINEL[match]) {
match++;
} else {
@@ -138,6 +140,9 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
}
lexer->advance(lexer, true);
}
+ if(lexer->get_column(lexer) == start_column) {
+ return false;
+ }
if(match == 6 && wide) {
*wide = 1;
}
diff --git a/codegraph-kernel/src/resolve/imports.rs b/codegraph-kernel/src/resolve/imports.rs
index 2e6621393a..a546125e85 100644
--- a/codegraph-kernel/src/resolve/imports.rs
+++ b/codegraph-kernel/src/resolve/imports.rs
@@ -643,35 +643,23 @@ impl KernelResolver {
if is_nix_path_import_ref(r) {
return self.resolve_import_path_to_file_node(r);
}
- // TS/JS path-shaped `imports` ref whose referenceName IS the module
- // specifier — the module ref of `import … from './x'` or a dynamic
- // `import('./x')` call site. Resolve the specifier (extension- and
- // alias-aware) straight to the file node like the c/cpp include arm:
- // name-match would bind the file's own `import` STATEMENT node —
- // `./cmd.config` literally matches that node's name — or a same-named
- // file elsewhere. Needs no binding rows, so it precedes the empty-
- // imports early return, mirroring the TS-side arm's position ahead of
- // the mappings lookup (import-resolver.ts resolveViaImport).
- if r.reference_kind == "imports"
- && is_esm_import_language(&r.language)
- && r.reference_name.contains('/')
- {
- if let Some(resolved) =
+ // Module-path imports resolve to the exact indexed file or nothing,
+ // including extensionless CommonJS paths and configured aliases.
+ if is_js_path_import_ref(r) {
+ let Some(resolved) =
self.resolve_import_path(&r.reference_name, &r.file_path, &r.language)?
- {
- if let Some(file_node) = self
- .nodes_in_file(&resolved)?
- .iter()
- .find(|n| n.kind == "file")
- .cloned()
- {
- return Ok(Some(KCand {
- node: file_node,
- confidence: 0.9,
- resolved_by: "import",
- }));
- }
+ else {
+ return Ok(None);
+ };
+ if resolved == r.file_path {
+ return Ok(None);
}
+ return Ok(self
+ .nodes_in_file(&resolved)?
+ .iter()
+ .find(|n| n.kind == "file")
+ .cloned()
+ .map(|node| KCand { node, confidence: 0.9, resolved_by: "import" }));
}
let imports = self.import_mappings(&r.file_path)?;
if imports.is_empty() && self.read_file(&r.file_path).is_none() {
diff --git a/codegraph-kernel/src/resolve/pipeline.rs b/codegraph-kernel/src/resolve/pipeline.rs
index 329ac58449..875605ae3f 100644
--- a/codegraph-kernel/src/resolve/pipeline.rs
+++ b/codegraph-kernel/src/resolve/pipeline.rs
@@ -178,6 +178,7 @@ impl KernelResolver {
&r.reference_name
};
let pre_pass = probe!(r, "pre-pass", is_nix_path_import_ref(r)
+ || is_js_path_import_ref(r)
|| self.has_any_possible_match_in(existence, &r.language)
|| self.matches_any_import(r)?
|| self.framework_claims(&r.reference_name));
diff --git a/codegraph-kernel/src/resolve/tables.rs b/codegraph-kernel/src/resolve/tables.rs
index d46919181f..114cb6be82 100644
--- a/codegraph-kernel/src/resolve/tables.rs
+++ b/codegraph-kernel/src/resolve/tables.rs
@@ -364,7 +364,7 @@ pub(super) fn framework_claims_reference(framework: &str, name: &str) -> bool {
|| angular_lazy_re().is_match(name)
}
"astro" => {
- name == "astro-page-component" || name.starts_with("astro-href:") || name == "Astro.redirect"
+ name.starts_with("page-component:") || name.starts_with("astro-href:") || name == "Astro.redirect"
}
"cics" => name.starts_with("cics-transid:"),
"django" => name == "_iterable_class" || name.ends_with(".urls"),
@@ -394,7 +394,7 @@ pub(super) fn framework_claims_reference(framework: &str, name: &str) -> bool {
"vue-router" => {
vue_nav_re().is_match(name) || name.starts_with("import:") || name.starts_with("layout:")
}
- "vue" => name.starts_with("nuxt-page:"),
+ "vue" => name.starts_with("page-component:"),
"aspnet" | "express" | "expo-modules" | "expo-modules-js" | "fabric-view" | "fastapi" | "flask"
| "go" | "goframe" | "http-routing" | "nestjs" | "nuxt" | "react-router-files" | "redwood"
| "rust" | "svelte"
@@ -505,12 +505,27 @@ pub(super) fn is_nix_path_import_ref(r: &ResolveRefIn) -> bool {
&& !r.reference_name.chars().any(|c| c.is_whitespace() || "{}()[];\"'<>$".contains(c))
}
+/// A JS-family module path names a file, never an imported symbol. Bare
+/// packages keep their existing import handling; slash paths and `#`
+/// specifiers retain the external-package and configured-alias checks.
+pub(super) fn is_js_path_import_ref(r: &ResolveRefIn) -> bool {
+ let name = r.reference_name.as_str();
+ r.reference_kind == "imports"
+ && is_esm_import_language(&r.language)
+ && (name.starts_with("./")
+ || name.starts_with("../")
+ || name == "."
+ || name == ".."
+ || name.starts_with('#')
+ || name.contains('/'))
+}
+
/// resolveOneInner's import-only refs: a PHP include path, a COBOL copybook,
-/// a Nix path import or any Terraform ref resolves through the import arm
+/// a Nix or JS module path import, or any Terraform ref, resolves through the import arm
/// (and frameworks) only — a name match would bind an unrelated same-named
/// file or cross a module boundary Terraform forbids.
pub(super) fn is_import_only_ref(r: &ResolveRefIn) -> bool {
- is_php_include_path_ref(r) || is_cobol_copybook_ref(r) || is_nix_path_import_ref(r) || r.language == "terraform"
+ is_php_include_path_ref(r) || is_cobol_copybook_ref(r) || is_nix_path_import_ref(r) || is_js_path_import_ref(r) || r.language == "terraform"
}
/// isPhpIncludePathRef (import-resolver.ts): a PHP `imports` ref shaped like
diff --git a/codegraph-kernel/src/tree.rs b/codegraph-kernel/src/tree.rs
index 3825a3c60b..6ef348724c 100644
--- a/codegraph-kernel/src/tree.rs
+++ b/codegraph-kernel/src/tree.rs
@@ -59,7 +59,8 @@
use napi::bindgen_prelude::*;
use napi_derive::napi;
-use tree_sitter::{Node, Parser};
+use std::time::{Duration, Instant};
+use tree_sitter::{Node, ParseOptions, Parser};
use crate::langs::grammar_for;
@@ -115,6 +116,10 @@ struct Row {
/// Parse with this thread's parser for `language` (created on first use).
pub(crate) fn parse_with_cached_parser(content: &str, language: &str) -> Result {
+ parse_with_cached_parser_budget(content, language, None)
+}
+
+fn parse_with_cached_parser_budget(content: &str, language: &str, budget_ms: Option) -> Result {
// One parser per language per thread: read-time callers parse many small
// files in a row, and Parser::new + set_language per call was measurable
// against a sub-millisecond parse.
@@ -132,14 +137,41 @@ pub(crate) fn parse_with_cached_parser(content: &str, language: &str) -> Result<
v.insert(parser)
}
};
- parser
- .parse(content, None)
- .ok_or_else(|| Error::from_reason("parser returned null tree".to_string()))
+ let tree = if let Some(ms) = budget_ms {
+ if ms == 0 {
+ return Err(Error::from_reason("parse budget exceeded"));
+ }
+ let started = Instant::now();
+ let budget = Duration::from_millis(u64::from(ms));
+ let mut cancelled = false;
+ let mut progress = |_: &tree_sitter::ParseState| {
+ cancelled = started.elapsed() >= budget;
+ cancelled
+ };
+ let bytes = content.as_bytes();
+ let tree = parser.parse_with_options(
+ &mut |offset, _| bytes.get(offset..).unwrap_or_default(),
+ None,
+ Some(ParseOptions::new().progress_callback(&mut progress)),
+ );
+ if tree.is_none() {
+ // Cancellation leaves resumable state in the cached parser.
+ // The next request must start from its own source instead.
+ parser.reset();
+ if cancelled {
+ return Err(Error::from_reason("parse budget exceeded"));
+ }
+ }
+ tree
+ } else {
+ parser.parse(content, None)
+ };
+ tree.ok_or_else(|| Error::from_reason("parser returned null tree".to_string()))
})
}
-fn parse_tree_inner(content: &str, language: &str) -> Result {
- let tree = parse_with_cached_parser(content, language)?;
+fn parse_tree_inner(content: &str, language: &str, budget_ms: Option) -> Result {
+ let tree = parse_with_cached_parser_budget(content, language, budget_ms)?;
// ASCII: byte offsets ARE UTF-16 offsets and tree-sitter's byte columns
// are code-unit columns. Only a non-ASCII file pays for the prefix table.
@@ -330,7 +362,9 @@ pub fn tree_names(language: String) -> Option {
/// Parse `content` with the native grammar for `language` and return the
/// whole tree. The walk is a cursor loop, not recursion, so deep nesting
/// needs no stack guard; a missing grammar surfaces as a napi error.
+/// An optional request budget cancels parsing at progress callbacks. It does
+/// not interrupt external scanners or bound tree serialization.
#[napi]
-pub fn parse_tree(content: String, language: String) -> Result {
- parse_tree_inner(&content, &language)
+pub fn parse_tree(content: String, language: String, budget_ms: Option) -> Result {
+ parse_tree_inner(&content, &language, budget_ms)
}
diff --git a/docs/design/framework-coverage.md b/docs/design/framework-coverage.md
index 045b430771..31ce8e0189 100644
--- a/docs/design/framework-coverage.md
+++ b/docs/design/framework-coverage.md
@@ -132,13 +132,15 @@ direct calls from inline handlers. They do not add Screens navigation.
`http-routing.test.ts` covers all nine through full indexing and imported-handler
resolution, as well as false-positive controls, prefixes and same-file mounts.
-Nuxt file routing in `frameworks/vue.ts` now includes root index pages, `index`
-folders anywhere in the path, a `calls` link from each page route to its own
-file's component (a `nuxt-page:` reference, since every `index.vue` shares a
-name), Nuxt 4 route groups, server method suffixes, `server/routes/` and server catch-all
-segments. `nuxt-routes.test.ts` checks extraction and imported-handler resolution;
-the existing Next Pages/App Router and Vue navigation tests remain controls.
-This update does not re-verify the older coverage rows above.
+Nuxt file routing in `frameworks/vue.ts` includes root index pages, `index`
+folders anywhere in the path, Nuxt 4 route groups, server method suffixes,
+`server/routes/` and server catch-all segments. Each page route calls the component
+extracted from the same file. `page-component.ts` resolves that exact file's
+component, including scriptless pages, without choosing a same-named component
+elsewhere. Astro pages use the same call contract. `nuxt-routes.test.ts` checks
+extraction and imported-handler resolution; the existing Next Pages/App Router
+and Vue navigation tests remain controls. The older coverage rows above retain
+their recorded measurements.
Untouched official source checks (routes and indexing, no application execution):
@@ -412,3 +414,26 @@ Interface dispatch synthesis links supported base methods to concrete overrides.
### Express inline handler bodies
Express uses the last registration argument as the handler, excluding a trailing comma. Inline arrows and function expressions contribute their body calls, including handlers inside wrapper calls and chained `router.route(path)` registrations. An earlier inline middleware does not replace a named final handler. Regression coverage is in `__tests__/express-inline-function-handler.test.ts`.
+
+
+### Single-file component ownership
+
+`extraction/sfc-script.ts` folds Vue, Svelte and Astro script results into one
+whole-file node containing the component. Top-level script members have the
+component as their only parent; nested symbols retain their script parent. Vue
+`