-
Notifications
You must be signed in to change notification settings - Fork 15
fix: record local alias for dynamic import() destructuring renames #1970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: fix/issue-1823-barrel-re-export-with-rename-export-x-as-y
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -810,12 +810,14 @@ function collectDynamicImport(node: TreeSitterNode, imports: Import[]): boolean | |
| const strArg = findChild(args, 'string'); | ||
| if (strArg) { | ||
| const modPath = strArg.text.replace(/['"]/g, ''); | ||
| const names = extractDynamicImportNames(node); | ||
| const renamedImports: Array<{ local: string; imported: string }> = []; | ||
| const names = extractDynamicImportNames(node, renamedImports); | ||
| imports.push({ | ||
| source: modPath, | ||
| names, | ||
| line: nodeStartLine(node), | ||
| dynamicImport: true, | ||
| ...(renamedImports.length > 0 ? { renamedImports } : {}), | ||
| }); | ||
| } else { | ||
| debug( | ||
|
|
@@ -1555,8 +1557,15 @@ function handleDynamicImportCall(node: TreeSitterNode, imports: Import[]): void | |
| const strArg = findChild(args, 'string'); | ||
| if (strArg) { | ||
| const modPath = strArg.text.replace(/['"]/g, ''); | ||
| const names = extractDynamicImportNames(node); | ||
| imports.push({ source: modPath, names, line: nodeStartLine(node), dynamicImport: true }); | ||
| const renamedImports: Array<{ local: string; imported: string }> = []; | ||
| const names = extractDynamicImportNames(node, renamedImports); | ||
| imports.push({ | ||
| source: modPath, | ||
| names, | ||
| line: nodeStartLine(node), | ||
| dynamicImport: true, | ||
| ...(renamedImports.length > 0 ? { renamedImports } : {}), | ||
| }); | ||
| } else { | ||
| debug( | ||
| `Skipping non-static dynamic import() at line ${nodeStartLine(node)} (template literal or variable)`, | ||
|
|
@@ -4177,13 +4186,22 @@ const DYNAMIC_IMPORT_WRAPPER_TYPES = new Set([ | |
| * const { a, b } = await import('./foo.js') → ['a', 'b'] | ||
| * const mod = await import('./foo.js') → ['mod'] | ||
| * const { a } = (await import('./foo.js')) as { a: Fn } → ['a'] | ||
| * const { a: b } = await import('./foo.js') → ['b'] | ||
| * import('./foo.js') → [] (no names extractable) | ||
| * | ||
| * Walks up the AST from the call_expression — through any nesting of | ||
| * await/parenthesized/as-cast wrappers — to find the enclosing | ||
| * variable_declarator and reads the name/object_pattern. | ||
| * | ||
| * `renamedOut`, when supplied, is populated with `{ local, imported }` pairs | ||
| * for every `{ imported: local }` specifier — mirrors `extractImportNames`'s | ||
| * static-import convention (#1730) so call-edge resolution can recover the | ||
| * original exported name when a call site uses the local alias (#1824). | ||
| */ | ||
| function extractDynamicImportNames(callNode: TreeSitterNode): string[] { | ||
| function extractDynamicImportNames( | ||
| callNode: TreeSitterNode, | ||
| renamedOut?: Array<{ local: string; imported: string }>, | ||
| ): string[] { | ||
| // Walk up through await_expression / parenthesized_expression / as_expression | ||
| // wrappers, in any combination or order, to reach the variable_declarator. | ||
| let current = callNode.parent; | ||
|
|
@@ -4204,10 +4222,37 @@ function extractDynamicImportNames(callNode: TreeSitterNode): string[] { | |
| if (child.type === 'shorthand_property_identifier_pattern') { | ||
| names.push(child.text); | ||
| } 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 | ||
| // { imported: local } → the local binding (`value`) is what call | ||
| // sites actually reference; `key` is the name exported by the target | ||
| // module. Preferring `key` unconditionally (as this branch used to) | ||
| // silently dropped the local alias for every renamed destructure, | ||
| // the same class of bug fixed for static `import { X as Y }` | ||
| // specifiers in #1730 (#1824). | ||
| const key = child.childForFieldName('key'); | ||
| if (key) names.push(key.text); | ||
| const value = child.childForFieldName('value'); | ||
| let localNode: TreeSitterNode | undefined; | ||
| if ( | ||
| value?.type === 'identifier' || | ||
| value?.type === 'shorthand_property_identifier_pattern' | ||
| ) { | ||
| localNode = value; | ||
| } else if (value?.type === 'assignment_pattern') { | ||
| // { imported: local = defaultValue } — the local binding is the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When a dynamic import uses a valid quoted or computed destructuring key like |
||
| // assignment_pattern's left-hand identifier. | ||
| const left = value.childForFieldName('left'); | ||
| if (left?.type === 'identifier') localNode = left; | ||
| } | ||
| if (localNode && key) { | ||
| names.push(localNode.text); | ||
| if (localNode.text !== key.text) { | ||
| renamedOut?.push({ local: localNode.text, imported: key.text }); | ||
| } | ||
| } else if (key) { | ||
| // Nested pattern (`{ foo: { nested } }`) or other unsupported | ||
| // value shape — no single local binding to extract; fall back to | ||
| // the key so the specifier isn't dropped entirely. | ||
| names.push(key.text); | ||
| } | ||
| } | ||
| } | ||
| return names; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.importedthen no longer matches the target export name, so the alias call edge is not created and the exported symbol can be reported as unused.