Skip to content

fix: credit import-type usages as exports consumers#1814

Merged
carlos-alm merged 2 commits into
fix/issue-1723-roles-dead-params-interfacesfrom
fix/issue-1724-exports-type-only-consumers
Jul 6, 2026
Merged

fix: credit import-type usages as exports consumers#1814
carlos-alm merged 2 commits into
fix/issue-1723-roles-dead-params-interfacesfrom
fix/issue-1724-exports-type-only-consumers

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • domain/analysis/exports.ts's consumer query only matched kind = 'calls' edges, so interfaces/types consumed only via import type { X } + type-annotation usage showed consumerCount: 0 even though codegraph deps correctly tracked the file-level import.
  • The builder already emits symbol-level imports-type edges (emitTypeOnlySymbolEdges) — widened the consumer query to kind 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 — clean
  • Repro: codegraph exports src/domain/graph/builder/cha.ts -T --json now shows ChaContext with consumerCount: 2 (was 0)
  • Negative case: genuinely-unused exports still show consumerCount: 0

Note: deliberately did NOT extend this to extends/implements edges — investigation found those resolve by symbol name with no file scoping, producing real false positives (e.g. unrelated same-named Repository/UserRepository fixtures 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.

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-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a false-negative in exportsData where exported interfaces and types consumed only via import type { X } were incorrectly reported as having zero consumers. The fix widens the SQL consumer query from kind = 'calls' to kind IN ('calls', 'imports-type'), matching the edge-kind set already used consistently in structure.ts, graph-enrichment.ts, and boundaries.ts. Three focused integration tests cover the happy path, the --unused filter exclusion, and the negative case (genuinely unreferenced exports still report zero consumers).

  • Core change (exports.ts): One-line SQL predicate extension with thorough inline documentation explaining why extends/implements edges are deliberately excluded.
  • Test coverage (exports.test.ts): New describe block adds a self-contained fixture DB (own tmpDir2/dbPath2) so it doesn't affect existing tests; assertions include the intentional file-level name/line shape of imports-type consumers, addressing the previous review thread.

Confidence Score: 5/5

Safe 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

Filename Overview
src/domain/analysis/exports.ts Single SQL predicate widened from kind = 'calls' to kind IN ('calls', 'imports-type'); well-commented with deliberate exclusion of extends/implements edges documented inline.
tests/integration/exports.test.ts Adds a new isolated describe block with three tests covering the credited-consumer, --unused-exclusion, and genuinely-unused cases; assertions for consumers[0].name/line document the file-level shape of imports-type edges.

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"]
Loading
%%{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"]
Loading

Reviews (3): Last reviewed commit: "fix: assert file-level name/line for imp..." | Re-trigger Greptile

Comment thread tests/integration/exports.test.ts
Comment thread src/domain/analysis/exports.ts
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

1 functions changed3 callers affected across 3 files

  • exportsFileImpl in src/domain/analysis/exports.ts:134 (3 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 041b150 into fix/issue-1723-roles-dead-params-interfaces Jul 6, 2026
19 checks passed
@carlos-alm carlos-alm deleted the fix/issue-1724-exports-type-only-consumers branch July 6, 2026 01:00
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 6, 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.

1 participant