Skip to content

fix(exports): top-level-call consumer entries no longer masquerade as a named caller - #2506

Merged
carlos-alm merged 2 commits into
mainfrom
fix/issue-2365-toplevel-call-caller-metadata
Aug 14, 2026
Merged

fix(exports): top-level-call consumer entries no longer masquerade as a named caller#2506
carlos-alm merged 2 commits into
mainfrom
fix/issue-2365-toplevel-call-caller-metadata

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Problem

findCaller (src/domain/graph/builder/call-resolver.ts) falls back to the FILE node as a calls edge's source when a call is a bare top-level statement with no enclosing function/binding. findExternalConsumers (edges.ts) and its mirror in exports.ts discriminate consumerKind purely off edge kind ('calls' vs 'imports-type'), so this genuinely-real calls edge gets consumerKind: 'symbol' — but its name/file/line are the file node's own values (the file's basename, line 0), not a real caller symbol/call-site. codegraph exports/codegraph check output could therefore show something like consumer.ts (consumer.ts:0), which reads as if consumer.ts were itself a calling function.

This was an inherited characteristic of the existing discriminator design (present since #1973, explicitly called out as out-of-scope when #2189 fixed a related misclassification bug), flagged by Greptile on PR #2364 as worth addressing on its own.

Fix

Chose the least invasive of the three options the issue raised: added a third consumerKind value, 'topLevelCall', discriminated by additionally selecting the caller node's own kind column (not just the edge kind) — 'calls' edge + file-kind source → 'topLevelCall'; 'calls' edge + real source → 'symbol' (unchanged); 'imports-type''file' (unchanged).

Did not pursue the other two options from the issue:

  • Synthesizing a real call-site line for the bare top-level call would need a schema change (the edges table has no per-edge line column today) or a new synthetic-node scheme — a materially larger change than what this metadata-quality gap warrants.
  • Documentation-only was the weakest option and doesn't actually fix the misleading CLI output.

Updated both renderers (presentation/check.ts, presentation/queries-cli/exports.ts) to present the new kind distinctly ("file.ts (top-level call)") rather than falling through to the generic name (file:line) format used for real named callers.

Closes #2365

Test plan

  • Updated two existing regression tests (from checkNoDeletedExportsInUse consumer refs also conflate file-level imports-type with symbol-level calls #1973/exports' consumer discriminator (#1830) misclassifies top-level calls sourced from the file node #2189) whose fixtures are exactly this file-node-fallback scenario — they previously asserted consumerKind: 'symbol' for it, which is now 'topLevelCall'
  • Added a new integration test asserting the new kind is distinct from a real named caller
  • Added a new presentation-layer test asserting the CLI renders "file (top-level call)", not the file's own name as if it were a caller
  • Verified consumerKind === 'symbol' assertions for genuinely real callers (main/testAdd calling add/multiply) are untouched
  • Full vitest run suite (5283 passed, 328 files) after rebuilding the native addon (stale in the fresh worktree, unrelated to this TS-only change)
  • tsc --noEmit, biome check clean
  • TS-only change — confirmed via diff that no Rust files changed; this is query/presentation-layer code that reads from the already-built SQLite DB, not part of the dual-engine graph-building pipeline, so no Rust mirroring is needed

… a named caller

findCaller falls back to the file node as a calls edge's source for a bare
top-level statement with no enclosing function/binding, so the resulting
consumer entry's name/file/line were the file node's own values rendered
under consumerKind: 'symbol', making a filename read as if it were a real
calling function. Adds a third consumerKind, 'topLevelCall', discriminated
by the caller node's own kind rather than just the edge kind, and updates
both renderers (check.ts, queries-cli/exports.ts) to present it distinctly
from a genuine named caller.

Closes #2365

docs check acknowledged

Impact: 12 functions changed, 19 affected
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR distinguishes bare top-level calls from named-symbol callers throughout export analysis and presentation.

  • Adds the topLevelCall consumer classification to TypeScript query, advisory-reading, type, and rendering paths.
  • Mirrors the classification in the native Rust deleted-export advisory writer.
  • Adds regression coverage for native persistence, export analysis, checks, and CLI rendering.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the native advisory writer now mirrors the TypeScript classification, and the persisted value is preserved through the advisory reader and check renderer.

Important Files Changed

Filename Overview
crates/codegraph-core/src/domain/graph/builder/stages/detect_changes.rs The native advisory writer now selects the caller node kind and correctly persists topLevelCall for file-sourced call edges, resolving the previous thread.
src/db/repository/deleted-export-advisories.ts The advisory reader preserves the newly persisted topLevelCall discriminator.
src/db/repository/edges.ts Live external-consumer queries distinguish type imports, named callers, and file-sourced top-level calls.
src/domain/analysis/exports.ts Export analysis applies the same three-way consumer classification used by the repository query.
src/presentation/check.ts Check output renders top-level calls without presenting the file node as a named caller or fabricated call site.
src/presentation/queries-cli/exports.ts Export CLI output renders the new consumer kind as a distinct top-level call.

Reviews (2): Last reviewed commit: "fix(native): deleted-export advisory wri..." | Re-trigger Greptile

Comment thread src/db/repository/edges.ts
@github-actions

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

7 functions changed19 callers affected across 8 files

  • getDeletedExportAdvisories in src/db/repository/deleted-export-advisories.ts:218 (3 transitive callers)
  • findExternalConsumers in src/db/repository/edges.ts:220 (7 transitive callers)
  • exportsFileImpl in src/domain/analysis/exports.ts:160 (3 transitive callers)
  • buildSymbolResult in src/domain/analysis/exports.ts:230 (4 transitive callers)
  • formatPredicateViolations in src/presentation/check.ts:64 (2 transitive callers)
  • formatViolation in src/presentation/check.ts:79 (3 transitive callers)
  • formatConsumer in src/presentation/queries-cli/exports.ts:21 (6 transitive callers)

… calls

The Rust-side record_deleted_export_advisories had its own independent
copy of the file/symbol discriminator and was never updated for the new
topLevelCall kind, so a native build's persisted advisory snapshot would
still classify a bare top-level call as a named caller after files with
such a consumer were purged, reintroducing the exact misleading check
output this change removes on the live-query path.

docs check acknowledged
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review — pushed the native-side advisory-writer fix you flagged.

@carlos-alm
carlos-alm merged commit 742baec into main Aug 14, 2026
18 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2365-toplevel-call-caller-metadata branch August 14, 2026 17:13
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

follow-up: symbol-level consumer entries for file-sourced top-level calls carry file metadata, not a real caller name/line

1 participant