diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 2b30c0d99..b9ed83046 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -44,6 +44,7 @@ pub use crate::{ es2020::NullishCoalescingOperatorOptions, options::{TransformOptions, TransformTarget}, react_jsx::{ReactJsxOptions, ReactJsxRuntime, ReactJsxRuntimeOption}, + typescript::TypescriptOptions, }; pub struct Transformer<'a> { @@ -90,7 +91,7 @@ impl<'a> Transformer<'a> { Self { ctx: ctx.clone(), // TODO: pass verbatim_module_syntax from user config - typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)), + typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false, &options)), regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options), // es2022 es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options), diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 29927aeb7..f572c5fbd 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -2,7 +2,7 @@ use oxc_syntax::assumptions::CompilerAssumptions; use crate::{ es2015::ArrowFunctionsOptions, es2020::NullishCoalescingOperatorOptions, - react_jsx::ReactJsxOptions, + react_jsx::ReactJsxOptions, typescript::TypescriptOptions, }; #[derive(Debug, Default, Clone)] @@ -12,6 +12,8 @@ pub struct TransformOptions { pub react_jsx: Option, + pub typescript: Option, + // es2022 pub class_static_block: bool, // es2021 diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 618a4a005..305c525c9 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -9,7 +9,10 @@ use oxc_syntax::{ use rustc_hash::FxHashSet; use std::{mem, rc::Rc}; -use crate::{context::TransformerCtx, utils::is_valid_identifier}; +mod options; + +pub use self::options::TypescriptOptions; +use crate::{context::TransformerCtx, utils::is_valid_identifier, TransformOptions}; /// Transform TypeScript /// @@ -22,6 +25,7 @@ pub struct TypeScript<'a> { ctx: TransformerCtx<'a>, verbatim_module_syntax: bool, export_name_set: FxHashSet, + options: TypescriptOptions, } impl<'a> TypeScript<'a> { @@ -29,8 +33,15 @@ impl<'a> TypeScript<'a> { ast: Rc>, ctx: TransformerCtx<'a>, verbatim_module_syntax: bool, + options: &TransformOptions, ) -> Self { - Self { ast, ctx, verbatim_module_syntax, export_name_set: FxHashSet::default() } + Self { + ast, + ctx, + verbatim_module_syntax, + export_name_set: FxHashSet::default(), + options: options.typescript.clone().unwrap_or_default(), + } } pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) { @@ -142,11 +153,11 @@ impl<'a> TypeScript<'a> { let mut import_type_names = FxHashSet::default(); let mut delete_indexes = vec![]; - let mut import_len = 0; + let mut module_declaration_len = 0; for (index, stmt) in program.body.iter_mut().enumerate() { if let Statement::ModuleDeclaration(module_decl) = stmt { - import_len += 1; + module_declaration_len += 1; match &mut **module_decl { ModuleDeclaration::ExportNamedDeclaration(decl) => { decl.specifiers.retain(|specifier| { @@ -173,6 +184,7 @@ impl<'a> TypeScript<'a> { } ModuleDeclaration::ImportDeclaration(decl) => { let is_type = decl.import_kind.is_type(); + let is_specifiers_empty = decl.specifiers.as_ref().is_some_and(|s| s.is_empty()); @@ -184,12 +196,14 @@ impl<'a> TypeScript<'a> { return false; } - if export_type_names.contains(&s.local.name) { - return false; + if self.verbatim_module_syntax + || self.options.only_remove_type_imports + { + return true; } - if self.verbatim_module_syntax { - return true; + if export_type_names.contains(&s.local.name) { + return false; } self.has_value_references(&s.local.name) @@ -200,6 +214,11 @@ impl<'a> TypeScript<'a> { { if is_type { import_type_names.insert(s.local.name.clone()); + return false; + } + + if self.options.only_remove_type_imports { + return true; } self.has_value_references(&s.local.name) @@ -212,15 +231,23 @@ impl<'a> TypeScript<'a> { import_type_names.insert(s.local.name.clone()); } + if self.options.only_remove_type_imports { + return true; + } + + if export_names.contains(&s.local.name) { + return false; + } + self.has_value_references(&s.local.name) - || export_names.contains(&s.local.name) } _ => true, }); } if decl.import_kind.is_type() - || (!is_specifiers_empty + || (!self.options.only_remove_type_imports + && !is_specifiers_empty && decl .specifiers .as_ref() @@ -242,7 +269,7 @@ impl<'a> TypeScript<'a> { } // explicit esm - if import_len > 0 && import_len == delete_indexes_len { + if module_declaration_len > 0 && module_declaration_len == delete_indexes_len { let empty_export = self.ast.export_named_declaration( SPAN, None, diff --git a/crates/oxc_transformer/src/typescript/options.rs b/crates/oxc_transformer/src/typescript/options.rs new file mode 100644 index 000000000..4d57ac0ee --- /dev/null +++ b/crates/oxc_transformer/src/typescript/options.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct TypescriptOptions { + /// When set to true, the transform will only remove [type-only](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-exports) imports (introduced in TypeScript 3.8). This should only be used if you are using TypeScript >= 3.8. + /// defaults to false + pub only_remove_type_imports: bool, +} diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 52e0f1bf9..1c2d37a5f 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 318/1179 +Passed: 322/1179 # All Passed: * babel-plugin-transform-numeric-separator @@ -832,7 +832,7 @@ Passed: 318/1179 * general/function-duplicate-name/input.js * general/object/input.js -# babel-plugin-transform-typescript (87/158) +# babel-plugin-transform-typescript (91/158) * class/abstract-class-decorated/input.ts * class/abstract-class-decorated-method/input.ts * class/abstract-class-decorated-parameter/input.ts @@ -858,14 +858,10 @@ Passed: 318/1179 * imports/elide-injected/input.ts * imports/enum-id/input.ts * imports/enum-value/input.ts -* imports/import-named-type/input.ts -* imports/import-named-type-default-and-named/input.ts * imports/import=-module/input.ts * imports/import=-module-to-cjs/input.ts -* imports/only-remove-type-imports/input.ts * imports/parameter-decorators/input.ts * imports/type-only-export-specifier-2/input.ts -* imports/type-only-import-specifier-4/input.ts * namespace/ambient-module-nested/input.ts * namespace/ambient-module-nested-exported/input.ts * namespace/canonical/input.ts diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index 16520f75a..691280918 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -12,7 +12,7 @@ use oxc_span::{SourceType, VALID_EXTENSIONS}; use oxc_tasks_common::{normalize_path, print_diff_in_terminal, BabelOptions}; use oxc_transformer::{ ArrowFunctionsOptions, NullishCoalescingOperatorOptions, ReactJsxOptions, TransformOptions, - TransformTarget, Transformer, + TransformTarget, Transformer, TypescriptOptions, }; use serde::de::DeserializeOwned; use serde_json::Value; @@ -92,6 +92,9 @@ pub trait TestCase { react_jsx: options .get_plugin("transform-react-jsx") .map(get_options::), + typescript: options + .get_plugin("transform-typescript") + .map(get_options::), assumptions: options.assumptions, class_static_block: options.get_plugin("transform-class-static-block").is_some(), instanceof: options.get_plugin("transform-instanceof").is_some(),