mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(codegen): print matching quoted import names as identifiers (#26386)
Codegen omitted the alias whenever an imported name matched its local binding, comparing the decoded names without accounting for whether the imported name was a string literal. That produces invalid syntax for quoted import names, which require an explicit `as` binding:
```js
// Input
import { "foo" as foo } from "m";
// Before
import { "foo" } from "m";
// After
import { foo } from "m";
// After, minified
import{foo}from"m";
```
When a string-literal import name matches the emitted local binding name, print the local binding directly and use identifier shorthand. The matching binding already supplies a valid identifier, so this does not need a separate identifier-validity check or an AST mutation. The conversion applies in both default and minified output.
The comparison uses the decoded string value, so escaped names and Unicode names can also use shorthand. Declaration-level and inline type imports follow the same rule:
```ts
// Input
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";
import type { "Foo" as Foo } from "types";
import { type "Bar" as Bar } from "types";
// After
import { ab } from "m";
import { π } from "m";
import type { Foo } from "types";
import { type Bar } from "types";
```
Quoted names that differ from their local binding retain their quotes and aliases, including `"foo-bar" as foo`, `"" as foo`, and `"default" as foo`. Ordinary identifier imports retain the existing shorthand behavior.
This commit is contained in:
1 parent
646c27cae3
commit
2da73b7824
3 files changed
+51
-3
No files matched your search
@@ -988,9 +988,15 @@ impl Gen for ImportDeclaration<'_> {
|
||||
p.print_str("type ");
|
||||
}
|
||||
|
||||
spec.imported.print(p, ctx);
|
||||
let local_name = p.get_binding_identifier_name(&spec.local);
|
||||
let imported_name = get_module_export_name(&spec.imported, p);
|
||||
if matches!(spec.imported, ModuleExportName::StringLiteral(_))
|
||||
&& imported_name == local_name
|
||||
{
|
||||
spec.local.print(p, ctx);
|
||||
continue;
|
||||
}
|
||||
spec.imported.print(p, ctx);
|
||||
if imported_name != local_name {
|
||||
p.print_soft_space();
|
||||
p.print_space_before_identifier();
|
||||
|
||||
@@ -63,6 +63,28 @@ fn module_decl() {
|
||||
test_minify("export { foo as bar }", "export{foo as bar};");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quoted_import_names() {
|
||||
test(r#"import { "foo" as foo } from "m";"#, "import { foo } from \"m\";\n");
|
||||
test(r#"import { "a\u0062" as ab } from "m";"#, "import { ab } from \"m\";\n");
|
||||
test(r#"import { "π" as π } from "m";"#, "import { π } from \"m\";\n");
|
||||
test(r#"import { "type" as type } from "m";"#, "import { type } from \"m\";\n");
|
||||
|
||||
test_same("import { \"foo-bar\" as foo } from \"m\";\n");
|
||||
test_same("import { \"\" as foo } from \"m\";\n");
|
||||
test_same("import { \"default\" as foo } from \"m\";\n");
|
||||
test_same("import { \"foo\" as bar } from \"m\";\n");
|
||||
test(
|
||||
r#"import { "foo" as foo, "bar" as bar, baz } from "m";"#,
|
||||
"import { foo, bar, baz } from \"m\";\n",
|
||||
);
|
||||
test(r#"import { foo as foo, bar } from "m";"#, "import { foo, bar } from \"m\";\n");
|
||||
|
||||
test_minify(r#"import { "foo" as foo } from "m";"#, r#"import{foo}from"m";"#);
|
||||
test_minify(r#"import { "a\u0062" as ab } from "m";"#, r#"import{ab}from"m";"#);
|
||||
test_minify(r#"import { foo as foo, bar } from "m";"#, r#"import{foo,bar}from"m";"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_type() {
|
||||
test_same("export type {} from \"mod\";\n");
|
||||
|
||||
@@ -5,8 +5,8 @@ use oxc_span::SourceType;
|
||||
use crate::{
|
||||
snapshot, snapshot_options,
|
||||
tester::{
|
||||
default_options, test_idempotency, test_options_with_source_type, test_same, test_tsx,
|
||||
test_with_parse_options,
|
||||
default_options, test, test_idempotency, test_options_with_source_type, test_same,
|
||||
test_tsx, test_with_parse_options,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -55,6 +55,26 @@ fn cases() {
|
||||
test_same("const Bar = class<T,> {};\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quoted_type_import_names() {
|
||||
test(r#"import type { "foo" as foo } from "m";"#, "import type { foo } from \"m\";\n");
|
||||
test(r#"import { type "foo" as foo } from "m";"#, "import { type foo } from \"m\";\n");
|
||||
test(r#"import type { "a\u0062" as ab } from "m";"#, "import type { ab } from \"m\";\n");
|
||||
test(r#"import { type "a\u0062" as ab } from "m";"#, "import { type ab } from \"m\";\n");
|
||||
test(r#"import type { foo as foo } from "m";"#, "import type { foo } from \"m\";\n");
|
||||
test(r#"import { type foo as foo } from "m";"#, "import { type foo } from \"m\";\n");
|
||||
|
||||
let min = |source: &str, expected: &str| {
|
||||
test_options_with_source_type(source, expected, SourceType::ts(), CodegenOptions::minify());
|
||||
};
|
||||
min(r#"import type { "foo" as foo } from "m";"#, r#"import type{foo}from"m";"#);
|
||||
min(r#"import { type "foo" as foo } from "m";"#, r#"import{type foo}from"m";"#);
|
||||
min(r#"import type { "a\u0062" as ab } from "m";"#, r#"import type{ab}from"m";"#);
|
||||
min(r#"import { type "a\u0062" as ab } from "m";"#, r#"import{type ab}from"m";"#);
|
||||
min(r#"import type { foo as foo } from "m";"#, r#"import type{foo}from"m";"#);
|
||||
min(r#"import { type foo as foo } from "m";"#, r#"import{type foo}from"m";"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_default_interface() {
|
||||
test_same("export default interface X {}\nconst y = 1;\n");
|
||||
|
||||
Reference in new issue
Block a user