fix(ui): count module-level initializer calls as the file's top-level code

Walking a declaration's initializer attributes its calls to the declared
name, so `const service = new Service()` no longer leaves a calls edge on
the FILE node — and the viewer's entry points ranking and the file screen's
top-level count, which read only the file node's edges, went blank on
exactly the files that run the most. Count a module-level variable or
constant's calls with the file in both places.
This commit is contained in:
danusha2345
2026-09-02 00:41:16 +03:00
parent 246ae3b791
commit ee43faeb34
3 changed files with 40 additions and 8 deletions
+1 -1
View File
@@ -204,7 +204,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Python module-level assignments now contribute the calls their right-hand side makes — `app = FastAPI()`, `ENGINE = create_engine(url)`, `handler = lambda: run()`, a registry dict or list of handlers. Previously everything a module wires up at import time was missing from the graph, so the objects it builds looked unreferenced. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- Rust `const` and `static` initializers now contribute their calls: `static REGISTRY: Lazy<Cfg> = Lazy::new(|| build())`, `const LEN: usize = compute_len()`. Previously anything a lazily-built singleton or a computed constant called was missing from the graph entirely. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- Scala `val`/`var` definitions now contribute the calls their initializer makes — `val handler = () => process(msg)`, `val client = buildClient()`, `lazy val engine = start()`. Previously everything on the right-hand side was dropped, which on a val-heavy codebase (SpinalHDL hardware descriptions, Akka wiring) is most of the wiring: a 32-file SpinalHDL project gained 812 references it had been missing. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- TypeScript and JavaScript module-level declarations now name themselves as the caller of whatever their initializer runs. `const cfg = loadConfig()` recorded the *file* as loadConfig's caller, which is no use for callers or impact; it now records `cfg`. And an object literal that wasn't exported — `const handlers = { onSave: () => persist() }` — was skipped entirely, so nothing inside it reached the graph at all. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- TypeScript and JavaScript module-level declarations now name themselves as the caller of whatever their initializer runs. `const cfg = loadConfig()` recorded the *file* as loadConfig's caller, which is no use for callers or impact; it now records `cfg`, and the viewer's entry points and file screens still count that call as the file's top-level code. And an object literal that wasn't exported — `const handlers = { onSave: () => persist() }` — was skipped entirely, so nothing inside it reached the graph at all. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- `codegraph_explore` again lists a dynamic-dispatch link when the same two symbols are also joined by an ordinary call, instead of dropping it from the summary.
- Java fields initialized with a lambda or an anonymous class — `private final Runnable r = () -> doWork();`, `new LocationListener() { … }`, `Parcelable.Creator` — now contribute call edges, and the anonymous class and its overrides become real symbols instead of being invisible. Previously everything inside a field initializer was dropped, so a method reached only from one looked like it had no callers. Re-index with `codegraph index -f` after upgrading to pick up the new edges.
- Kotlin `init { }` blocks and destructuring declarations no longer swallow their code: `init { val cfg = load() }` and `val (a, b) = makePair()` contributed no call edge at all, and now attribute to the enclosing class or file.
+25 -6
View File
@@ -2184,6 +2184,12 @@ export class QueryBuilder {
* build script does its work on the way down the file. `instantiates` counts
* the same way — `new Server(...)` at module scope is the same act.
*
* A call made while initializing a module-level `variable` / `constant` —
* `const service = new Service()`, `app = FastAPI()` — is attributed to the
* declared name (#693), not to the file, so the file's own edges alone would
* miss most of what a real entry point runs. Those names are the file's
* top-level code too, so `tops` counts them alongside the file node.
*
* Ranking multiplies the two things an entry point does: it runs (calls), and
* it wires the project together (distinct other files its symbols reach). One
* alone is misleading — a registration table makes hundreds of module-level
@@ -2196,12 +2202,25 @@ export class QueryBuilder {
if (limit <= 0) return [];
return this.db
.prepare(
`WITH runs AS (
SELECT e.source AS id, COUNT(*) AS calls
FROM edges e
JOIN nodes n ON n.id = e.source
WHERE n.kind = 'file' AND e.kind IN ('calls', 'instantiates')
GROUP BY e.source
`WITH tops AS (
SELECT n.id AS file_id, n.id AS src
FROM nodes n
WHERE n.kind = 'file'
UNION ALL
SELECT c.source AS file_id, c.target AS src
FROM edges c
JOIN nodes f ON f.id = c.source
JOIN nodes v ON v.id = c.target
WHERE c.kind = 'contains'
AND f.kind = 'file'
AND v.kind IN ('variable', 'constant')
),
runs AS (
SELECT t.file_id AS id, COUNT(*) AS calls
FROM tops t
JOIN edges e ON e.source = t.src
WHERE e.kind IN ('calls', 'instantiates')
GROUP BY t.file_id
),
cand AS (
SELECT r.id AS id, n.file_path AS fp, r.calls AS calls
+14 -1
View File
@@ -66,6 +66,7 @@ export function buildFile(cg: CodeGraph, projectRoot: string, requested: string)
const nodes = cg.getNodesInFile(storedPath);
const nodeIds = nodes.map((n) => n.id);
const inThisFile = new Set(nodeIds);
const nodeKindById = new Map(nodes.map((n) => [n.id, n.kind]));
const fileNode = nodes.find((n) => n.kind === 'file') ?? null;
// ---------------------------------------------------------------------------
@@ -106,8 +107,20 @@ export function buildFile(cg: CodeGraph, projectRoot: string, requested: string)
// the same signal `/api/entrypoints` ranks on. It is worth a line on this
// screen because the outline cannot show it: top-level code belongs to no
// symbol, so the only way to read it is to open the file node itself.
// A call made while initializing a module-level variable or constant is
// attributed to that name (#693), so those names are top-level code too and
// are counted with the file — the same set `getTopCallingFiles` ranks on.
const moduleLevelValueIds = fileNode
? cg
.getOutgoingEdgesFrom([fileNode.id], ['contains'])
.map((e) => e.target)
.filter((id) => {
const kind = nodeKindById.get(id);
return kind === 'variable' || kind === 'constant';
})
: [];
const topLevelEdges = fileNode
? cg.getOutgoingEdgesFrom([fileNode.id], ['calls', 'instantiates'])
? cg.getOutgoingEdgesFrom([fileNode.id, ...moduleLevelValueIds], ['calls', 'instantiates'])
: [];
return {