fix(packages/codegen): print matching quoted import names as identifiers (#26404)

Oxc codegen npm package emits invalid JavaScript when a quoted imported name matches its local binding. The printer compares the decoded names and omits the alias, but leaves the imported name quoted. A quoted import name requires an explicit `as` binding.

For this valid input:

```js
import { "foo" as foo } from "m";
```

The JavaScript printer previously emitted:

```js
import { "foo" } from "m";
```

It now prints the matching local binding as identifier shorthand:

```js
import { foo } from "m";
```

`printImportDeclaration` checks for a string-literal imported name whose decoded value equals the local binding name before printing the imported name. In that case, it prints the local identifier directly, including its source-map mapping. The local binding already supplies a valid identifier, so this requires neither a separate identifier-validity check nor an AST mutation.

Comparing the decoded value also handles escaped and Unicode import names:

```js
// Input
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";

// Output
import { ab } from "m";
import { π } from "m";
```

Declaration-level and inline TypeScript type imports use the same shorthand:

```ts
// Input
import type { "Foo" as Foo } from "m";
import { type "Bar" as Bar } from "m";

// Output
import type { Foo } from "m";
import { type Bar } from "m";
```

Quoted names that differ from their local bindings retain their quotes and aliases, including `"foo-bar" as foo`, `"" as foo`, and `"default" as foo`. Ordinary identifier imports keep their existing shorthand behavior. The correction applies to both the ordinary and source-map-enabled builds and brings the JavaScript printer in line with the Rust codegen fix in #26386.
This commit is contained in:
camc314
2026-09-07 15:28:07 +00:00
parent bbbb4bc5c9
commit 6e15ad5cfb
3 changed files with 23 additions and 2 deletions
+7 -2
View File
@@ -114,8 +114,13 @@ export function printImportDeclaration(node: ESTree.ImportDeclaration, state: St
if (TS && specifier.importKind === "type") write(state, "type ", CAT_OTHER);
const importedName = moduleExportName(specifier.imported, state);
const { local } = specifier;
const { imported, local } = specifier;
if (imported.type === "Literal" && imported.value === local.name) {
printSpaceBeforeIdentifier(state);
writeWithMapNamed(state, local.name, local.start, local.end, local);
break;
}
const importedName = moduleExportName(imported, state);
if (importedName !== local.name) {
write(state, " as ", CAT_OTHER);
writeWithMapNamed(state, local.name, local.start, local.end, local);
+10
View File
@@ -0,0 +1,10 @@
import { "foo" as foo } from "m";
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";
import { "type" as type } from "m";
import { "foo-bar" as dashed } from "m";
import { "" as empty } from "m";
import { "default" as defaultImport } from "m";
import { "foo" as bar } from "m";
import { "one" as one, "two" as two, three } from "m";
import { four as four, five } from "m";
+6
View File
@@ -0,0 +1,6 @@
import type { "Foo" as Foo } from "m";
import { type "Bar" as Bar } from "m";
import type { "a\u0063" as ac } from "m";
import { type "a\u0064" as ad } from "m";
import type { Baz as Baz } from "m";
import { type Qux as Qux } from "m";