Summary
While fixing #1730 (renamed static import { X as Y } specifiers not resolving to the correct call edge), I found the same class of bug in the dynamic import extraction path: const { a: b } = await import('./mod') records the original name (a) instead of the local binding actually used at call sites (b), so a subsequent call to b(...) will not resolve against the exported symbol a in ./mod.
Root cause
extractDynamicImportNames in src/extractors/javascript.ts (pair_pattern branch, handling { a: b } = await import(...)) currently does:
} else if (child.type === 'pair_pattern') {
// { a: localName } → use localName (the alias) for the local binding,
// but use the key (original name) for import resolution
const key = child.childForFieldName('key');
if (key) names.push(key.text);
}
The comment describes wanting both pieces of information, but only key.text (a, the original exported name) is ever pushed into the flat names array — the local alias (b) is dropped entirely. Downstream, buildImportedNamesMap/buildImportedNamesForNative use imp.names entries as the local binding name to key the importedNames map against call-site text (this is exactly the invariant #1730 restored for static imports). Since names holds a instead of b here, a call site b(...) never matches this import's map entry.
I have not checked whether the Rust extractor's extract_dynamic_import_names (crates/codegraph-core/src/extractors/javascript.rs) has the identical issue — likely, given the mirrored-engine convention, but not yet confirmed.
Repro
async function run() {
const { realName: alias } = await import('./mod.js');
alias();
}
alias() should resolve to realName exported from ./mod.js, but does not.
Expected
extractDynamicImportNames needs the same local/original split #1730 introduced for static import_specifier handling (Import.renamedImports / RenamedImport), so names carries the local alias b and a rename pair records { local: 'b', imported: 'a' } for the resolver to consult.
Scope note
Deliberately not fixed as part of #1730 — different extraction function (dynamic import() destructuring, not static import specifiers), or a distinct enough sub-case to warrant its own fix + tests.
Summary
While fixing #1730 (renamed static
import { X as Y }specifiers not resolving to the correct call edge), I found the same class of bug in the dynamic import extraction path:const { a: b } = await import('./mod')records the original name (a) instead of the local binding actually used at call sites (b), so a subsequent call tob(...)will not resolve against the exported symbolain./mod.Root cause
extractDynamicImportNamesinsrc/extractors/javascript.ts(pair_patternbranch, handling{ a: b } = await import(...)) currently does:The comment describes wanting both pieces of information, but only
key.text(a, the original exported name) is ever pushed into the flatnamesarray — the local alias (b) is dropped entirely. Downstream,buildImportedNamesMap/buildImportedNamesForNativeuseimp.namesentries as the local binding name to key theimportedNamesmap against call-site text (this is exactly the invariant #1730 restored for static imports). Sincenamesholdsainstead ofbhere, a call siteb(...)never matches this import's map entry.I have not checked whether the Rust extractor's
extract_dynamic_import_names(crates/codegraph-core/src/extractors/javascript.rs) has the identical issue — likely, given the mirrored-engine convention, but not yet confirmed.Repro
alias()should resolve torealNameexported from./mod.js, but does not.Expected
extractDynamicImportNamesneeds the same local/original split #1730 introduced for staticimport_specifierhandling (Import.renamedImports/RenamedImport), sonamescarries the local aliasband a rename pair records{ local: 'b', imported: 'a' }for the resolver to consult.Scope note
Deliberately not fixed as part of #1730 — different extraction function (dynamic
import()destructuring, not staticimportspecifiers), or a distinct enough sub-case to warrant its own fix + tests.