Skip to content

Dynamic import destructuring rename (const { a: b } = await import(...)) records the wrong binding name #1824

Description

@carlos-alm

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions