fix: record local alias for dynamic import() destructuring renames#1970
Conversation
const { a: b } = await import(...) recorded the original exported name
(a) instead of the local binding actually used at call sites (b), so
calls like b(...) never resolved to the imported symbol. The
pair_pattern branch of extractDynamicImportNames (TS/WASM) and
extract_dynamic_import_names (Rust) preferred tree-sitter's `key` field
over `value` - the same class of bug fixed for static
`import { X as Y }` specifiers in #1730.
Both engines now record the local alias in `names`, with the
local -> original mapping in `Import.renamedImports` so call-edge
resolution can still find the target module's real export. This reuses
the renamedImports mechanism #1730/#1823 already threaded through
resolution, so no resolver changes were needed - only extraction.
The Rust fix also corrects the same key-vs-value bug in CJS require()
destructuring (which shares collect_object_pattern_names), bringing it
in line with WASM's already-correct extractCjsRequireBinding.
Impact: 3 functions changed, 10 affected
Greptile SummaryThis PR fixes dynamic import destructuring aliases in both JavaScript extractors. The main changes are:
Confidence Score: 4/5The alias flow works for identifier keys, but quoted or computed destructuring keys can still miss dynamic import edges.
src/extractors/javascript.ts; crates/codegraph-core/src/extractors/javascript.rs Important Files Changed
Reviews (1): Last reviewed commit: "fix: record local alias for dynamic impo..." | Re-trigger Greptile |
| ) { | ||
| localNode = value; | ||
| } else if (value?.type === 'assignment_pattern') { | ||
| // { imported: local = defaultValue } — the local binding is the |
There was a problem hiding this comment.
When a dynamic import uses a valid quoted or computed destructuring key like const { 'foo-bar': local } = await import('./mod.js'), this stores the raw key text as imported. The resolver then looks for an export named 'foo-bar' or ['foo-bar'] instead of foo-bar, so calls through local() stay unresolved and the real export can still be marked dead.
| match (local_node, key) { | ||
| (Some(local_node), Some(key)) => { | ||
| let local_text = node_text(&local_node, source).to_string(); | ||
| let key_text = node_text(&key, source).to_string(); |
There was a problem hiding this comment.
When the native extractor sees a dynamic import destructure such as const { 'foo-bar': local } = await import('./mod.js'), node_text(&key, source) includes the quotes or computed-key syntax. renamed_imports.imported then no longer matches the target export name, so the alias call edge is not created and the exported symbol can be reported as unused.
Codegraph Impact Analysis3 functions changed → 10 callers affected across 1 files
|
Summary
const { a: b } = await import(...)recorded the original exported name (a) instead of the local binding actually used at call sites (b) — so a subsequent call tob(...)never resolved to the imported symbol. Thepair_patternbranch ofextractDynamicImportNames(TS/WASM) andextract_dynamic_import_names(Rust) preferred tree-sitter'skeyfield (the name exported by the target module) overvalue(the local binding) — the same class of bug fixed for staticimport { X as Y }specifiers in codegraph exports does not credit consumers that import via a rename (import { X as Y }) #1730.names, with the local → original mapping recorded inImport.renamedImports— reusing the exact mechanism codegraph exports does not credit consumers that import via a rename (import { X as Y }) #1730/Barrel re-export with rename (export { X as Y } from ...) is not tracked, breaking barrel resolution #1823 already threaded through call resolution, barrel tracing, and the incremental path. No resolver changes were needed, only extraction, since that infrastructure already exists and is generic across static/dynamic imports.{ a: b = defaultValue }) and falls back to the previous key-based behavior for nested patterns ({ foo: { nested } }), which have no single local binding to extract.collect_object_pattern_nameshelper, CJSrequire()destructuring renames (const { a: b } = require('./mod')) are now also correct on the native engine, bringing it in line with WASM's already-correctextractCjsRequireBinding.Test plan
npx vitest run tests/parsers/javascript.test.ts— 208/208 pass, including 5 new tests for the rename/default/parens+as-cast casesnpx vitest run tests/integration/issue-1824-dynamic-import-destructure-rename.test.ts— new integration test, WASM + native variants, 6/6 passdist/from the pre-fix source, confirmed 2 failures; restored the fix and rebuilt, confirmed all pass)cargo test --lib— 529 passed, including 3 existing Rust unit tests updated (they previously asserted the buggy behavior as expected output, per the "never document bugs as expected behavior" rule)npm test— full suite green (3756 passed, 30 skipped, 2 todo)npm run lint— cleancodegraph diff-impact --staged -T— confirmed blast radius is scoped toextractDynamicImportNames/collectDynamicImport/handleDynamicImportCalland their callers onlyFiled #1969 (WASM dynamic-import destructuring misses shorthand-default and rest-element bindings that native already handles) — a distinct, pre-existing parity gap discovered while reading the Rust/WASM object-pattern loops side by side, out of scope for this fix.
Stacked on #1823 (base branch
fix/issue-1823-barrel-re-export-with-rename-export-x-as-y) — only the extractor/types/test diff is this issue's change.Fixes #1824