feat(transformer/typescript): remove type only imports/exports correctly (#2055)

This commit is contained in:
Dunqing 2024-01-17 15:58:04 +08:00 committed by GitHub
parent 8d5f5b8a49
commit 95d741abd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 54 deletions

View file

@ -1,5 +1,6 @@
use oxc_allocator::{Box, Vec}; use oxc_allocator::{Box, Vec};
use oxc_ast::{ast::*, AstBuilder}; use oxc_ast::{ast::*, AstBuilder};
use oxc_semantic::SymbolFlags;
use oxc_span::{Atom, SPAN}; use oxc_span::{Atom, SPAN};
use oxc_syntax::{ use oxc_syntax::{
operator::{AssignmentOperator, BinaryOperator, LogicalOperator}, operator::{AssignmentOperator, BinaryOperator, LogicalOperator},
@ -20,8 +21,6 @@ pub struct TypeScript<'a> {
ast: Rc<AstBuilder<'a>>, ast: Rc<AstBuilder<'a>>,
ctx: TransformerCtx<'a>, ctx: TransformerCtx<'a>,
verbatim_module_syntax: bool, verbatim_module_syntax: bool,
/// type imports names
import_type_name_set: FxHashSet<Atom>,
export_name_set: FxHashSet<Atom>, export_name_set: FxHashSet<Atom>,
} }
@ -31,13 +30,7 @@ impl<'a> TypeScript<'a> {
ctx: TransformerCtx<'a>, ctx: TransformerCtx<'a>,
verbatim_module_syntax: bool, verbatim_module_syntax: bool,
) -> Self { ) -> Self {
Self { Self { ast, ctx, verbatim_module_syntax, export_name_set: FxHashSet::default() }
ast,
ctx,
verbatim_module_syntax,
import_type_name_set: FxHashSet::default(),
export_name_set: FxHashSet::default(),
}
} }
pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) { pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) {
@ -109,24 +102,84 @@ impl<'a> TypeScript<'a> {
/// * Adds `export {}` if all import / export statements are removed, this is used to tell /// * Adds `export {}` if all import / export statements are removed, this is used to tell
/// downstream tools that this file is in ESM. /// downstream tools that this file is in ESM.
pub fn transform_program(&mut self, program: &mut Program<'a>) { pub fn transform_program(&mut self, program: &mut Program<'a>) {
let mut needs_explicit_esm = false; let mut export_type_names = FxHashSet::default();
let mut export_names = FxHashSet::default();
for stmt in program.body.iter_mut() { // Collect export names
program.body.iter().for_each(|stmt| {
if let Statement::ModuleDeclaration(module_decl) = stmt { if let Statement::ModuleDeclaration(module_decl) = stmt {
needs_explicit_esm = true; match &**module_decl {
ModuleDeclaration::ExportNamedDeclaration(decl) => {
decl.specifiers.iter().for_each(|specifier| {
let name = specifier.exported.name();
if self.is_import_binding_only(name) {
let is_value =
decl.export_kind.is_value() && specifier.export_kind.is_value();
if is_value {
export_names.insert(name.clone());
} else {
export_type_names.insert(name.clone());
}
}
});
}
ModuleDeclaration::ExportDefaultDeclaration(decl) => {
let name = decl.exported.name();
if self.is_import_binding_only(name) {
export_names.insert(decl.exported.name().clone());
}
}
ModuleDeclaration::ExportAllDeclaration(decl) => {
if let Some(exported) = &decl.exported {
let name = exported.name();
if self.is_import_binding_only(name) {
let is_value =
decl.export_kind.is_value() && decl.export_kind.is_value();
if is_value {
export_names.insert(name.clone());
} else {
export_type_names.insert(name.clone());
}
}
}
}
_ => {}
}
}
});
let mut import_type_names = FxHashSet::default();
let mut delete_indexes = vec![];
let mut import_len = 0;
for (index, stmt) in program.body.iter_mut().enumerate() {
if let Statement::ModuleDeclaration(module_decl) = stmt {
import_len += 1;
match &mut **module_decl { match &mut **module_decl {
ModuleDeclaration::ExportNamedDeclaration(decl) => { ModuleDeclaration::ExportNamedDeclaration(decl) => {
decl.specifiers.retain(|specifier| { decl.specifiers.retain(|specifier| {
!(specifier.export_kind.is_type() !(specifier.export_kind.is_type()
|| self.import_type_name_set.contains(specifier.exported.name())) || import_type_names.contains(specifier.exported.name()))
}); });
if decl.export_kind.is_type()
|| self.verbatim_module_syntax
|| (decl.declaration.is_none() && decl.specifiers.is_empty())
{
delete_indexes.push(index);
} }
ModuleDeclaration::ImportDeclaration(decl) if decl.import_kind.is_value() => { }
ModuleDeclaration::ImportDeclaration(decl) => {
let is_type = decl.import_kind.is_type();
if let Some(specifiers) = &mut decl.specifiers { if let Some(specifiers) = &mut decl.specifiers {
specifiers.retain(|specifier| match specifier { specifiers.retain(|specifier| match specifier {
ImportDeclarationSpecifier::ImportSpecifier(s) => { ImportDeclarationSpecifier::ImportSpecifier(s) => {
if s.import_kind.is_type() { if is_type || s.import_kind.is_type() {
self.import_type_name_set.insert(s.local.name.clone()); import_type_names.insert(s.local.name.clone());
return false;
}
if export_type_names.contains(&s.local.name) {
return false; return false;
} }
@ -135,62 +188,55 @@ impl<'a> TypeScript<'a> {
} }
self.has_value_references(&s.local.name) self.has_value_references(&s.local.name)
|| export_names.contains(&s.local.name)
} }
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) ImportDeclarationSpecifier::ImportDefaultSpecifier(s)
if !self.verbatim_module_syntax => if !self.verbatim_module_syntax =>
{ {
if is_type {
import_type_names.insert(s.local.name.clone());
}
self.has_value_references(&s.local.name) self.has_value_references(&s.local.name)
|| export_names.contains(&s.local.name)
} }
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) ImportDeclarationSpecifier::ImportNamespaceSpecifier(s)
if !self.verbatim_module_syntax => if !self.verbatim_module_syntax =>
{ {
if is_type {
import_type_names.insert(s.local.name.clone());
}
self.has_value_references(&s.local.name) self.has_value_references(&s.local.name)
|| export_names.contains(&s.local.name)
} }
_ => true, _ => true,
}); });
} }
if decl.import_kind.is_type()
|| decl
.specifiers
.as_ref()
.is_some_and(|specifiers| specifiers.is_empty())
{
delete_indexes.push(index);
}
} }
_ => {} _ => {}
} }
} }
} }
program.body.retain(|stmt| match stmt { let delete_indexes_len = delete_indexes.len();
Statement::ModuleDeclaration(module_decl) => match &**module_decl {
ModuleDeclaration::ImportDeclaration(decl) => { // remove empty imports/exports
if decl.import_kind.is_type() { for index in delete_indexes.into_iter().rev() {
return false; program.body.remove(index);
} }
if self.verbatim_module_syntax { // explicit esm
return true; if import_len > 0 && import_len == delete_indexes_len {
}
!decl.specifiers.as_ref().is_some_and(|specifiers| specifiers.is_empty())
}
ModuleDeclaration::ExportNamedDeclaration(decl) => {
if decl.export_kind.is_type() {
return false;
}
if self.verbatim_module_syntax {
return true;
}
if decl.declaration.is_none() && decl.specifiers.is_empty() {
return false;
}
true
}
_ => true,
},
_ => true,
});
if needs_explicit_esm
&& !program.body.iter().any(|s| matches!(s, Statement::ModuleDeclaration(_)))
{
let empty_export = self.ast.export_named_declaration( let empty_export = self.ast.export_named_declaration(
SPAN, SPAN,
None, None,
@ -203,6 +249,23 @@ impl<'a> TypeScript<'a> {
} }
} }
/// ```ts
/// import foo from "foo"; // is import binding only
/// import bar from "bar"; // SymbolFlags::ImportBinding | SymbolFlags::BlockScopedVariable
/// let bar = "xx";
/// ```
fn is_import_binding_only(&self, name: &Atom) -> bool {
let root_scope_id = self.ctx.scopes().root_scope_id();
self.ctx.scopes().get_binding(root_scope_id, name).is_some_and(|symbol_id| {
let flag = self.ctx.symbols().get_flag(symbol_id);
flag.is_import_binding()
&& !flag.intersects(
SymbolFlags::FunctionScopedVariable | SymbolFlags::BlockScopedVariable,
)
})
}
fn has_value_references(&self, name: &Atom) -> bool { fn has_value_references(&self, name: &Atom) -> bool {
let root_scope_id = self.ctx.scopes().root_scope_id(); let root_scope_id = self.ctx.scopes().root_scope_id();

View file

@ -1,4 +1,4 @@
Passed: 306/1179 Passed: 308/1179
# All Passed: # All Passed:
* babel-plugin-transform-numeric-separator * babel-plugin-transform-numeric-separator
@ -832,7 +832,7 @@ Passed: 306/1179
* general/function-duplicate-name/input.js * general/function-duplicate-name/input.js
* general/object/input.js * general/object/input.js
# babel-plugin-transform-typescript (75/158) # babel-plugin-transform-typescript (77/158)
* class/abstract-class-decorated/input.ts * class/abstract-class-decorated/input.ts
* class/abstract-class-decorated-method/input.ts * class/abstract-class-decorated-method/input.ts
* class/abstract-class-decorated-parameter/input.ts * class/abstract-class-decorated-parameter/input.ts
@ -856,8 +856,6 @@ Passed: 306/1179
* exports/export-type-star-from/input.ts * exports/export-type-star-from/input.ts
* exports/export=/input.ts * exports/export=/input.ts
* exports/export=-to-cjs/input.ts * exports/export=-to-cjs/input.ts
* exports/imported-types/input.ts
* exports/imported-types-only-remove-type-imports/input.ts
* exports/issue-9916-3/input.ts * exports/issue-9916-3/input.ts
* function/overloads-exports/input.mjs * function/overloads-exports/input.mjs
* imports/elide-injected/input.ts * imports/elide-injected/input.ts