fix: credit import-type usages as exports consumers#1814
Conversation
codegraph exports <file> --json (and the --unused dead-export filter it
feeds) reported zero consumers for interfaces/types that are demonstrably
imported and used elsewhere via `import type { X }`, even though
`codegraph deps <file>` correctly showed the importing file in
`importedBy`. Example: ChaContext in src/domain/graph/builder/cha.ts is
imported (type-only) by build-edges.ts and native-orchestrator.ts, but
`codegraph exports` showed consumerCount: 0.
Root cause: exportsFileImpl's per-symbol consumers query
(domain/analysis/exports.ts) only looked at kind = 'calls' edges. The
builder already emits a symbol-level `imports-type` edge for `import
type { X }` statements (source = importing file node, target = the
specific imported symbol -- see emitTypeOnlySymbolEdges in
build-edges.ts/incremental.ts), which `codegraph deps` reads from, but
the exports consumer query never looked at this edge kind. Role
classification (features/structure.ts) already includes 'imports-type'
in its fan-in formula, so `codegraph roles --role dead` was unaffected --
this was purely a gap in exports's independent consumer list.
Fix: widen the consumers query to `kind IN ('calls', 'imports-type')`,
matching the edge-kind set already used everywhere else in the codebase
for cross-file usage credit (structure.ts, graph-enrichment.ts,
boundaries.ts, dependencies.ts). No native Rust changes needed --
domain/analysis/exports.ts is pure query-layer code that reads the
already-built edges table and has no engine-specific mirror.
Deliberately does NOT add `extends`/`implements`: investigation found
those edges are resolved by symbol name only, with no file/import
scoping (buildClassHierarchyEdges in both build-edges.ts/incremental.ts
and the native emit_hierarchy_edges), so they link same-named
declarations across unrelated files (verified: this repo's own graph has
false `implements`/`extends` edges between unrelated fixture classes
across languages and a `Repository` interface in src/types.ts). Filed
as #1812. Also filed #1813 for a related but distinct gap: `import {
type X }` inline per-specifier modifiers aren't tracked as type-only in
either engine's extractor, so such X still gets no credit even after
this fix.
Added tests/integration/exports.test.ts coverage: an interface consumed
only via a symbol-level `imports-type` edge gets consumerCount >= 1 and
is excluded from --unused, while a genuinely unreferenced interface
still shows 0 consumers.
Verified against this repo's own graph: `codegraph exports
src/domain/graph/builder/cha.ts -T --json` now credits ChaContext with
2 consumers (build-edges.ts, native-orchestrator.ts). Full test suite
(201 files, 3359 tests) and lint pass clean.
docs check acknowledged: internal bug fix to exports consumer
computation, no new feature/language/CLI surface/architecture change --
README.md, CLAUDE.md, and ROADMAP.md are unaffected.
Fixes #1724
Impact: 1 functions changed, 3 affected
Greptile SummaryThis PR fixes a false-negative in
Confidence Score: 5/5Safe to merge — a minimal, well-contained query predicate extension backed by targeted regression tests and aligned with the edge-kind set already in use throughout the codebase. The change is a single SQL predicate line in one function. The fix is consistent with how imports-type edges are consumed everywhere else. The exclusion of extends/implements is well-reasoned and documented. The new test suite is isolated, covers the primary scenario, the --unused boundary case, and the true-negative case. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["exportsFileImpl()"] --> B["consumersStmt.all(symbolId)"]
B --> C{{"edges WHERE target_id = symbol\nAND kind IN ('calls', 'imports-type')"}}
C -->|"calls edge"| D["Source: caller function node\nname = function name\nline = call-site line"]
C -->|"imports-type edge (NEW)"| E["Source: importing file node\nname = filename\nline = 0"]
D --> F["consumers[] entry"]
E --> F
F --> G{noTests filter?}
G -->|yes| H["Filter out test-file consumers"]
G -->|no| I["Keep all consumers"]
H --> J["consumerCount = consumers.length"]
I --> J
J -->|"count > 0"| K["Export credited as used"]
J -->|"count = 0"| L["Export flagged as dead / unused"]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["exportsFileImpl()"] --> B["consumersStmt.all(symbolId)"]
B --> C{{"edges WHERE target_id = symbol\nAND kind IN ('calls', 'imports-type')"}}
C -->|"calls edge"| D["Source: caller function node\nname = function name\nline = call-site line"]
C -->|"imports-type edge (NEW)"| E["Source: importing file node\nname = filename\nline = 0"]
D --> F["consumers[] entry"]
E --> F
F --> G{noTests filter?}
G -->|yes| H["Filter out test-file consumers"]
G -->|no| I["Keep all consumers"]
H --> J["consumerCount = consumers.length"]
I --> J
J -->|"count > 0"| K["Export credited as used"]
J -->|"count = 0"| L["Export flagged as dead / unused"]
Reviews (3): Last reviewed commit: "fix: assert file-level name/line for imp..." | Re-trigger Greptile |
Codegraph Impact Analysis1 functions changed → 3 callers affected across 3 files
|
041b150
into
fix/issue-1723-roles-dead-params-interfaces
Summary
domain/analysis/exports.ts's consumer query only matchedkind = 'calls'edges, so interfaces/types consumed only viaimport type { X }+ type-annotation usage showedconsumerCount: 0even thoughcodegraph depscorrectly tracked the file-level import.imports-typeedges (emitTypeOnlySymbolEdges) — widened the consumer query tokind IN ('calls', 'imports-type'), matching the edge-kind set already used elsewhere (structure.ts, graph-enrichment.ts, boundaries.ts).Closes #1724
Test plan
npm test— full suite green (3359 passed, 0 failed)npm run lint— cleancodegraph exports src/domain/graph/builder/cha.ts -T --jsonnow showsChaContextwithconsumerCount: 2(was 0)consumerCount: 0Note: deliberately did NOT extend this to
extends/implementsedges — investigation found those resolve by symbol name with no file scoping, producing real false positives (e.g. unrelated same-namedRepository/UserRepositoryfixtures across languages). Filed #1812 for that, and #1813 for a separate gap (import { type X }inline modifier not tracked as type-only in either engine's extractor).Stacked on #1811 (base branch
fix/issue-1723-roles-dead-params-interfaces) — only the exports.ts/test diff is this issue's change.