diff --git a/crates/oxc_semantic/src/reference.rs b/crates/oxc_semantic/src/reference.rs index 982337416f18e..4039312657c12 100644 --- a/crates/oxc_semantic/src/reference.rs +++ b/crates/oxc_semantic/src/reference.rs @@ -60,6 +60,10 @@ impl Reference { self.symbol_id = Some(symbol_id); } + pub fn flag_mut(&mut self) -> &mut ReferenceFlag { + &mut self.flag + } + /// Returns `true` if the identifier value was read. This is not mutually /// exclusive with [`#is_write`] pub fn is_read(&self) -> bool { diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index e8f64f8034bcc..bf26452f0aaff 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -145,6 +145,10 @@ impl SymbolTable { &self.references[reference_id] } + pub fn get_reference_mut(&mut self, reference_id: ReferenceId) -> &mut Reference { + &mut self.references[reference_id] + } + pub fn has_binding(&self, reference_id: ReferenceId) -> bool { self.references[reference_id].symbol_id().is_some() } diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index a6a181d59b8f3..268baf51bdcf0 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -4,7 +4,10 @@ use oxc_allocator::Allocator; use oxc_codegen::{Codegen, CodegenOptions}; use oxc_parser::Parser; use oxc_span::SourceType; -use oxc_transformer::{TransformOptions, Transformer}; +use oxc_transformer::{ + ArrowFunctionsOptions, ES2015Options, ReactOptions, TransformOptions, Transformer, + TypeScriptOptions, +}; // Instruction: // create a `test.js`, @@ -32,7 +35,17 @@ fn main() { println!("{source_text}\n"); let mut program = ret.program; - let transform_options = TransformOptions::default(); + let transform_options = TransformOptions { + typescript: TypeScriptOptions::default(), + es2015: ES2015Options { arrow_function: Some(ArrowFunctionsOptions::default()) }, + react: ReactOptions { + jsx_plugin: true, + jsx_self_plugin: true, + jsx_source_plugin: true, + ..Default::default() + }, + ..Default::default() + }; Transformer::new(&allocator, path, source_type, &source_text, &ret.trivias, transform_options) .build(&mut program) .unwrap(); diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 0f4cb6232444f..1d4ceb9344c20 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -102,9 +102,9 @@ impl<'a> Traverse<'a> for Transformer<'a> { self.x0_typescript.transform_program(program, ctx); } - fn exit_program(&mut self, program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) { + fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x1_react.transform_program_on_exit(program); - self.x0_typescript.transform_program_on_exit(program); + self.x0_typescript.transform_program_on_exit(program, ctx); } // ALPHASORT @@ -232,20 +232,12 @@ impl<'a> Traverse<'a> for Transformer<'a> { self.x0_typescript.transform_tagged_template_expression(expr); } - fn enter_identifier_reference( - &mut self, - ident: &mut IdentifierReference<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - self.x0_typescript.transform_identifier_reference(ident, ctx); - } - fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { self.x0_typescript.transform_statement(stmt, ctx); } - fn enter_declaration(&mut self, decl: &mut Declaration<'a>, _ctx: &mut TraverseCtx<'a>) { - self.x0_typescript.transform_declaration(decl); + fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) { + self.x0_typescript.transform_declaration(decl, ctx); self.x3_es2015.transform_declaration(decl); } diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index bab5f80986d77..9ab284e57ab61 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -9,10 +9,9 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; use oxc_span::{Atom, SPAN}; use oxc_syntax::operator::AssignmentOperator; +use oxc_traverse::TraverseCtx; use rustc_hash::FxHashSet; -use super::collector::TypeScriptReferenceCollector; - pub struct TypeScriptAnnotations<'a> { #[allow(dead_code)] options: Rc, @@ -25,6 +24,7 @@ pub struct TypeScriptAnnotations<'a> { has_jsx_fragment: bool, jsx_element_import_name: String, jsx_fragment_import_name: String, + type_identifier_names: FxHashSet>, } impl<'a> TypeScriptAnnotations<'a> { @@ -50,6 +50,7 @@ impl<'a> TypeScriptAnnotations<'a> { has_jsx_fragment: false, jsx_element_import_name, jsx_fragment_import_name, + type_identifier_names: FxHashSet::default(), } } @@ -82,19 +83,20 @@ impl<'a> TypeScriptAnnotations<'a> { // Remove type only imports/exports pub fn transform_program_on_exit( - &self, + &mut self, program: &mut Program<'a>, - references: &TypeScriptReferenceCollector, + ctx: &mut TraverseCtx<'a>, ) { - let mut type_names = FxHashSet::default(); let mut module_count = 0; let mut removed_count = 0; + // let mut type_identifier_names = self.type_identifier_names.clone(); + program.body.retain_mut(|stmt| { // fix namespace/export-type-only/input.ts // The namespace is type only. So if its name appear in the ExportNamedDeclaration, we should remove it. if let Statement::TSModuleDeclaration(decl) = stmt { - type_names.insert(decl.id.name().clone()); + self.type_identifier_names.insert(decl.id.name().clone()); return false; } @@ -106,7 +108,7 @@ impl<'a> TypeScriptAnnotations<'a> { ModuleDeclaration::ExportNamedDeclaration(decl) => { decl.specifiers.retain(|specifier| { !(specifier.export_kind.is_type() - || type_names.contains(specifier.exported.name())) + || self.type_identifier_names.contains(specifier.exported.name())) }); decl.export_kind.is_type() @@ -117,6 +119,9 @@ impl<'a> TypeScriptAnnotations<'a> { .is_some_and(Declaration::is_typescript_syntax)) && decl.specifiers.is_empty()) } + ModuleDeclaration::ExportAllDeclaration(decl) => { + return !decl.export_kind.is_type() + } ModuleDeclaration::ImportDeclaration(decl) => { let is_type = decl.import_kind.is_type(); @@ -127,7 +132,7 @@ impl<'a> TypeScriptAnnotations<'a> { specifiers.retain(|specifier| match specifier { ImportDeclarationSpecifier::ImportSpecifier(s) => { if is_type || s.import_kind.is_type() { - type_names.insert(s.local.name.clone()); + self.type_identifier_names.insert(s.local.name.clone()); return false; } @@ -135,32 +140,31 @@ impl<'a> TypeScriptAnnotations<'a> { return true; } - references.has_reference(&s.local.name) - || self.is_jsx_imports(&s.local.name) + self.has_value_reference(&s.local.name, ctx) } ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => { if is_type { - type_names.insert(s.local.name.clone()); + self.type_identifier_names.insert(s.local.name.clone()); return false; } if self.options.only_remove_type_imports { return true; } - references.has_reference(&s.local.name) - || self.is_jsx_imports(&s.local.name) + + self.has_value_reference(&s.local.name, ctx) } ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => { if is_type { - type_names.insert(s.local.name.clone()); + self.type_identifier_names.insert(s.local.name.clone()); + return false; } if self.options.only_remove_type_imports { return true; } - references.has_reference(&s.local.name) - || self.is_jsx_imports(&s.local.name) + self.has_value_reference(&s.local.name, ctx) } }); } @@ -428,4 +432,32 @@ impl<'a> TypeScriptAnnotations<'a> { pub fn transform_jsx_fragment(&mut self, _elem: &mut JSXFragment<'a>) { self.has_jsx_fragment = true; } + + pub fn transform_export_named_declaration(&mut self, decl: &mut ExportNamedDeclaration<'a>) { + let is_type = decl.export_kind.is_type(); + for specifier in &decl.specifiers { + if is_type || specifier.export_kind.is_type() { + self.type_identifier_names.insert(specifier.local.name().clone()); + } + } + } + + pub fn has_value_reference(&self, name: &Atom<'a>, ctx: &TraverseCtx<'a>) -> bool { + if let Some(symbol_id) = ctx.scopes().get_root_binding(name) { + if ctx.symbols().get_flag(symbol_id).is_export() + && !self.type_identifier_names.contains(name) + { + return true; + } + if ctx + .symbols() + .get_resolved_references(symbol_id) + .any(|reference| !reference.is_type()) + { + return true; + } + } + + self.is_jsx_imports(name) + } } diff --git a/crates/oxc_transformer/src/typescript/collector.rs b/crates/oxc_transformer/src/typescript/collector.rs deleted file mode 100644 index 923b15bb3dd08..0000000000000 --- a/crates/oxc_transformer/src/typescript/collector.rs +++ /dev/null @@ -1,36 +0,0 @@ -use oxc_ast::ast::{ExportNamedDeclaration, IdentifierReference}; -use oxc_span::Atom; -use rustc_hash::FxHashSet; - -/// Collects identifier references -/// Indicates whether the BindingIdentifier is referenced or used in the ExportNamedDeclaration -#[derive(Debug)] -pub struct TypeScriptReferenceCollector<'a> { - names: FxHashSet>, -} - -impl<'a> TypeScriptReferenceCollector<'a> { - pub fn new() -> Self { - Self { names: FxHashSet::default() } - } - - pub fn has_reference(&self, name: &Atom) -> bool { - self.names.contains(name) - } - - pub fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) { - self.names.insert(ident.name.clone()); - } - - pub fn visit_transform_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) { - if decl.export_kind.is_type() { - return; - } - - for specifier in &decl.specifiers { - if specifier.export_kind.is_value() { - self.names.insert(specifier.local.name().clone()); - } - } - } -} diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 4ac320018e4db..85554278e5847 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -1,5 +1,4 @@ mod annotations; -mod collector; mod diagnostics; mod r#enum; mod module; @@ -14,10 +13,7 @@ use oxc_traverse::TraverseCtx; use crate::context::Ctx; -use self::{ - annotations::TypeScriptAnnotations, collector::TypeScriptReferenceCollector, - r#enum::TypeScriptEnum, -}; +use self::{annotations::TypeScriptAnnotations, r#enum::TypeScriptEnum}; pub use self::options::TypeScriptOptions; @@ -49,7 +45,6 @@ pub struct TypeScript<'a> { annotations: TypeScriptAnnotations<'a>, r#enum: TypeScriptEnum<'a>, - reference_collector: TypeScriptReferenceCollector<'a>, } impl<'a> TypeScript<'a> { @@ -59,7 +54,6 @@ impl<'a> TypeScript<'a> { Self { annotations: TypeScriptAnnotations::new(&options, ctx), r#enum: TypeScriptEnum::new(ctx), - reference_collector: TypeScriptReferenceCollector::new(), options, ctx: Rc::clone(ctx), } @@ -79,8 +73,12 @@ impl<'a> TypeScript<'a> { } } - pub fn transform_program_on_exit(&self, program: &mut Program<'a>) { - self.annotations.transform_program_on_exit(program, &self.reference_collector); + pub fn transform_program_on_exit( + &mut self, + program: &mut Program<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.annotations.transform_program_on_exit(program, ctx); } pub fn transform_arrow_expression(&mut self, expr: &mut ArrowFunctionExpression<'a>) { @@ -104,7 +102,7 @@ impl<'a> TypeScript<'a> { } pub fn transform_export_named_declaration(&mut self, decl: &mut ExportNamedDeclaration<'a>) { - self.reference_collector.visit_transform_export_named_declaration(decl); + self.annotations.transform_export_named_declaration(decl); } pub fn transform_expression(&mut self, expr: &mut Expression<'a>) { @@ -196,22 +194,12 @@ impl<'a> TypeScript<'a> { self.annotations.transform_tagged_template_expression(expr); } - pub fn transform_identifier_reference( - &mut self, - ident: &mut IdentifierReference<'a>, - ctx: &TraverseCtx<'a>, - ) { - if !ctx.parent().is_ts_interface_heritage() && !ctx.parent().is_ts_type_reference() { - self.reference_collector.visit_identifier_reference(ident); - } - } - - pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) { + pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) { match decl { Declaration::TSImportEqualsDeclaration(ts_import_equals) if ts_import_equals.import_kind.is_value() => { - *decl = self.transform_ts_import_equals(ts_import_equals); + *decl = self.transform_ts_import_equals(ts_import_equals, ctx); } _ => {} } diff --git a/crates/oxc_transformer/src/typescript/module.rs b/crates/oxc_transformer/src/typescript/module.rs index 0f441cae4d113..7f277cee31f5c 100644 --- a/crates/oxc_transformer/src/typescript/module.rs +++ b/crates/oxc_transformer/src/typescript/module.rs @@ -1,21 +1,31 @@ use oxc_allocator::Box; use oxc_ast::ast::*; use oxc_span::SPAN; +use oxc_syntax::reference::ReferenceFlag; +use oxc_traverse::TraverseCtx; use super::TypeScript; impl<'a> TypeScript<'a> { - fn transform_ts_type_name(&self, type_name: &mut TSTypeName<'a>) -> Expression<'a> { + fn transform_ts_type_name( + &self, + type_name: &mut TSTypeName<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { match type_name { - TSTypeName::IdentifierReference(reference) => { - self.ctx.ast.identifier_reference_expression(IdentifierReference::new( - SPAN, - reference.name.clone(), - )) + TSTypeName::IdentifierReference(ident) => { + ident.reference_flag = ReferenceFlag::Read; + if let Some(reference_id) = ident.reference_id.get() { + let reference = ctx.symbols_mut().get_reference_mut(reference_id); + *reference.flag_mut() = ReferenceFlag::Read; + } else { + unreachable!() + } + self.ctx.ast.identifier_reference_expression(ctx.ast.copy(ident)) } TSTypeName::QualifiedName(qualified_name) => self.ctx.ast.static_member_expression( SPAN, - self.transform_ts_type_name(&mut qualified_name.left), + self.transform_ts_type_name(&mut qualified_name.left, ctx), qualified_name.right.clone(), false, ), @@ -33,6 +43,7 @@ impl<'a> TypeScript<'a> { pub fn transform_ts_import_equals( &self, decl: &mut Box<'a, TSImportEqualsDeclaration<'a>>, + ctx: &mut TraverseCtx<'a>, ) -> Declaration<'a> { let kind = VariableDeclarationKind::Var; let decls = { @@ -43,7 +54,7 @@ impl<'a> TypeScript<'a> { let init = match &mut decl.module_reference { type_name @ match_ts_type_name!(TSModuleReference) => { - self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut()) + self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut(), ctx) } TSModuleReference::ExternalModuleReference(reference) => { if self.ctx.source_type.is_module() { diff --git a/crates/oxc_traverse/src/lib.rs b/crates/oxc_traverse/src/lib.rs index 3194116b16894..8e02f8afc6233 100644 --- a/crates/oxc_traverse/src/lib.rs +++ b/crates/oxc_traverse/src/lib.rs @@ -60,6 +60,8 @@ //! scheme could very easily be derailed entirely by a single mistake, so in my opinion, it's unwise //! to edit by hand. +use std::path::PathBuf; + use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_semantic::SemanticBuilder; @@ -147,6 +149,7 @@ pub fn traverse_mut<'a, Tr: Traverse<'a>>( ) { let semantic = SemanticBuilder::new(source_text, source_type) .with_check_syntax_error(true) + .build_module_record(PathBuf::default(), program) .build(program) .semantic; let (symbols, scopes) = semantic.into_symbol_table_and_scope_tree(); diff --git a/tasks/coverage/transformer_typescript.snap b/tasks/coverage/transformer_typescript.snap index 33778c92c2da7..bfa4a8b9416b3 100644 --- a/tasks/coverage/transformer_typescript.snap +++ b/tasks/coverage/transformer_typescript.snap @@ -2,9 +2,7 @@ commit: 64d2eeea transformer_typescript Summary: AST Parsed : 5243/5243 (100.00%) -Positive Passed: 5238/5243 (99.90%) +Positive Passed: 5240/5243 (99.94%) Mismatch: "compiler/elidedEmbeddedStatementsReplacedWithSemicolon.ts" Mismatch: "compiler/jsxComplexSignatureHasApplicabilityError.tsx" -Mismatch: "compiler/jsxEmptyExpressionNotCountedAsChild.tsx" -Mismatch: "compiler/styledComponentsInstantiaionLimitNotReached.ts" Mismatch: "compiler/tsxReactPropsInferenceSucceedsOnIntersections.tsx"