← 返回首页
fix(exports): credit plain imports of TypeScript interfaces/type aliases by carlos-alm · Pull Request #1978 · optave/ops-codegraph-tool · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

fix(exports): credit plain imports of TypeScript interfaces/type aliases#1978

Open
carlos-alm wants to merge 1 commit into
fix/issue-1831-contributing-md-is-stale-references-removedfrom
fix/issue-1833-exports-dead-export-analysis-never-counts
Open

fix(exports): credit plain imports of TypeScript interfaces/type aliases#1978
carlos-alm wants to merge 1 commit into
fix/issue-1831-contributing-md-is-stale-references-removedfrom
fix/issue-1833-exports-dead-export-analysis-never-counts

Conversation

Copy link
Copy Markdown
Contributor

Summary

codegraph exports/dead-export analysis (the same query the pre-commit dead-export gate uses) only credited a symbol-level imports-type edge when the importing statement was syntactically marked type-only — a whole-statement import type { X } (#1724) or the inline import { type X } modifier (#1813).

TypeScript also allows importing an interface or type alias through a plain import { X } from 'y' with no type keyword at all — an extremely common pattern in codebases that don't enforce import/consistent-type-imports. Since interfaces and type aliases are erased before runtime, they can never receive a calls edge either, so a plain import was the only possible consumption signal, and it was being ignored — permanently misclassifying every such interface/type alias as a dead export, even when it's genuinely used everywhere.

Root cause & fix

imports-type crediting was gated on the importing statement's syntax rather than the resolved target's actual kind. The fix classifies crediting by whether the resolved target is a TypeScript interface/type-alias declaration (isTypeErasedImportTarget in shared/kinds.ts, mirrored as is_type_erased_import_target in Rust's shared/constants.rs) — regardless of whether the import statement used the type keyword.

  • A plain import of a value symbol (function, class, const) is unaffected — its consumption credit still requires a real calls edge, so genuinely-unused value imports still correctly show 0 consumers.
  • Scoped specifically to .ts/.tsx files, since other languages reuse the 'interface'/'type' node kinds for constructs that are runtime-observable (e.g. a Go type alias, a Java interface dispatched through at runtime) — crediting those on mere import would mask genuinely dead code instead of fixing a false positive.

Dual-engine parity

Mirrored in all four import-edge-building code paths per this repo's dual-engine architecture:

  • src/domain/graph/builder/stages/build-edges.ts (JS/WASM full build)
  • src/domain/graph/builder/incremental.ts (JS/WASM incremental / watch)
  • crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs (native FFI-hybrid path, used e.g. on --force full rebuilds)
  • crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs (native orchestrator's pure-Rust path — the primary path for --engine native)

Test plan

  • New fixture tests/fixtures/issue-1833-plain-type-import/ + tests/integration/issue-1833-plain-type-import.test.ts — a plain import { Config, Mode } from './types.js' (no type keyword) used only in type position; verifies both Config (interface) and Mode (type alias) get a symbol-level imports-type edge and are reported as consumed via exportsData/codegraph exports, on both wasm and native engines. Also verifies a genuinely-unimported export (helper) still correctly reports 0 consumers.
  • New Rust unit tests in both build_edges.rs and import_edges.rs covering: plain import of a TS interface (credited), plain import of a TS value symbol (not credited), and plain import of a non-TypeScript 'interface'-kind node e.g. Go (not credited — heuristic correctly scoped to TS).
  • Reproduced the original repro from exports/dead-export analysis never counts consumers of TypeScript interfaces/type aliases #1833 (codegraph exports src/types.ts -T on this repo's own LanguageRegistryEntry) — confirmed the manual fix path, then added a minimal synthetic reproduction (import { Foo, Bar } from './types' with no type keyword, used only as parameter/return types) that failed identically on both engines before the fix and passes after.
  • Full suite: npm test — 229 test files, 3773 passed (30 pre-existing skipped, 2 pre-existing todo).
  • cargo test --lib in crates/codegraph-core — 539 passed, including 18 tests in build_edges.rs's import_edge_tests module and 11 in import_edges.rs's tests module (new tests added for this fix).
  • npm run lint — clean (one intentional useImportType warning on the fixture itself, which deliberately uses the plain-import syntax under test; non-blocking).
  • npm run build and cargo check --lib / cargo clippy --lib — clean, no new warnings introduced.
  • codegraph diff-impact --staged -T reviewed — blast radius limited to the modified import-edge-building functions and their direct/transitive callers, as expected.

Fixes #1833

codegraph exports/dead-export analysis only credited a symbol-level imports-type edge when the importing statement was syntactically marked type-only (import type { X } or the inline import { type X } modifier). TypeScript also allows importing an interface or type alias through a plain import { X } from 'y' with no type keyword at all -- an extremely common pattern in codebases that don't enforce import/consistent-type-imports. Since interfaces and type aliases are erased before runtime, they can never receive a calls edge either, so a plain import was the only possible consumption signal and it was being ignored -- permanently misclassifying every such interface/type alias as a dead export. Fix the root cause: classify imports-type crediting by the resolved target's node kind (interface/type in a .ts/.tsx file), not by the importing statement's syntax. A plain import of a value symbol (function, class, const) is unaffected -- its consumption credit still requires a real calls edge. Scoped to TypeScript files specifically, since other languages reuse the 'interface'/'type' node kinds for constructs that are runtime-observable (e.g. a Go type alias, a Java interface dispatched through at runtime). Mirrored in both engines: the WASM/JS full-build and incremental pipelines (build-edges.ts, incremental.ts) and both native Rust import-edge paths (the FFI-hybrid build_edges.rs and the native orchestrator's import_edges.rs). Impact: 13 functions changed, 25 affected

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR credits plain imports of erased TypeScript exports in dead-export analysis. The main changes are:

  • Adds shared helpers for interface/type-alias import classification.
  • Updates JS full-build and incremental import-edge creation.
  • Updates both Rust import-edge builders for native parity.
  • Adds fixture and integration coverage for plain .ts type imports.

Confidence Score: 4/5

The changed import-credit flow needs an extension-parity fix before merging.

  • Plain imports from .mts and .cts type exports can still be reported as dead.
  • The issue affects both the JS helper and the native Rust helper.
  • The core .ts and .tsx paths appear consistent across the changed builders.

src/shared/kinds.ts and crates/codegraph-core/src/shared/constants.rs

Important Files Changed

Filename Overview
src/domain/graph/builder/stages/build-edges.ts Full-build JS import-edge emission now credits plain imports when the resolved target kind is erased.
src/domain/graph/builder/incremental.ts Incremental JS import-edge emission mirrors the full-build behavior using DB candidate kind data.
src/shared/kinds.ts Adds erased-target classification, but its extension gate misses TypeScript module files.
crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs Native FFI-hybrid import-edge building now carries symbol kind and credits erased TypeScript targets.
crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs Pure Rust import-edge building now loads symbol kind and widens lookup collection for possible erased targets.
crates/codegraph-core/src/shared/constants.rs Adds Rust-side erased-target classification, but its TypeScript extension list is incomplete.
tests/integration/issue-1833-plain-type-import.test.ts Adds regression coverage for plain imports of .ts interface and type-alias exports on both engines.

Reviews (1): Last reviewed commit: "fix(exports): credit plain imports of Ty..." | Re-trigger Greptile

Comment thread src/shared/kinds.ts
* instead of fixing a false positive.
*/
export function isTypeErasedImportTarget(kind: string, file: string): boolean {
return TYPE_ERASED_SYMBOL_KINDS.has(kind) && TYPESCRIPT_EXTENSIONS.has(path.extname(file));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

TypeScript Modules Stay Uncredited

When a plain import resolves to an interface or type alias in a .mts or .cts file, this check returns false because path.extname(file) is compared against a set containing only .ts and .tsx. Those TypeScript module files can still export erased declarations, so the new imports-type credit is skipped and codegraph exports can still report the used type export as dead.


/// TypeScript source extensions — type annotations (and TS's compile-time-only
/// 'interface'/'type' declarations) only exist for these.
pub const TYPESCRIPT_EXTENSIONS: [&str; 2] = [".ts", ".tsx"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

Native TypeScript Modules Stay Uncredited

The native builders use this list to decide whether a plain import can credit an erased TypeScript target, but it excludes .mts and .cts. A plain import of an interface or type from those module files therefore fails is_type_erased_import_target, so the native codegraph exports path can still mark the used export as dead.

Suggested change
pub const TYPESCRIPT_EXTENSIONS: [&str; 2] = [".ts", ".tsx"];
pub const TYPESCRIPT_EXTENSIONS: [&str; 4] = [".ts", ".tsx", ".mts", ".cts"];

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

14 functions changed25 callers affected across 8 files

  • emitNamedSymbolEdges in src/domain/graph/builder/incremental.ts:423 (4 transitive callers)
  • emitEdgesForImport in src/domain/graph/builder/incremental.ts:466 (5 transitive callers)
  • emitNamedSymbolEdges in src/domain/graph/builder/stages/build-edges.ts:230 (3 transitive callers)
  • emitEdgesForImport in src/domain/graph/builder/stages/build-edges.ts:267 (3 transitive callers)
  • collectSymbolNodes in src/domain/graph/builder/stages/build-edges.ts:490 (3 transitive callers)
  • parseFileAuto in src/domain/parser.ts:1111 (5 transitive callers)
  • backfillTypeMapBatch in src/domain/parser.ts:1141 (7 transitive callers)
  • ingestNativeResults in src/domain/parser.ts:1311 (7 transitive callers)
  • parseFileIncremental in src/domain/parser.ts:1427 (6 transitive callers)
  • isTypeErasedImportTarget in src/shared/kinds.ts:109 (6 transitive callers)
  • NativeAddon.buildImportEdges in src/types.ts:2342 (0 transitive callers)
  • useConfig in tests/fixtures/issue-1833-plain-type-import/consumer.ts:8 (0 transitive callers)
  • Config.name in tests/fixtures/issue-1833-plain-type-import/types.ts:2 (0 transitive callers)
  • helper in tests/fixtures/issue-1833-plain-type-import/types.ts:7 (0 transitive callers)

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Footer

© 2026 GitHub, Inc.