1use oxc_allocator::{TakeIn, Vec as ArenaVec};
2use oxc_ast::ast::*;
3use oxc_diagnostics::OxcDiagnostic;
4use oxc_semantic::{Reference, SymbolFlags};
5use oxc_span::{GetSpan, SPAN, Span, Str};
6use oxc_syntax::{
7 operator::AssignmentOperator,
8 reference::ReferenceFlags,
9 scope::{ScopeFlags, ScopeId},
10 symbol::SymbolId,
11};
12use oxc_traverse::Traverse;
13
14use crate::{TypeScriptOptions, context::TraverseCtx, state::TransformState};
15
16pub struct TypeScriptAnnotations<'a> {
17 only_remove_type_imports: bool,
19
20 assignments: Vec<Assignment<'a>>,
22 has_super_call: bool,
23
24 has_jsx_element: bool,
25 has_jsx_fragment: bool,
26 jsx_element_import_name: String,
27 jsx_fragment_import_name: String,
28}
29
30impl TypeScriptAnnotations<'_> {
31 pub fn new(options: &TypeScriptOptions) -> Self {
32 let jsx_element_import_name = if options.jsx_pragma.contains('.') {
33 options.jsx_pragma.split('.').next().map(String::from).unwrap()
34 } else {
35 options.jsx_pragma.to_string()
36 };
37
38 let jsx_fragment_import_name = if options.jsx_pragma_frag.contains('.') {
39 options.jsx_pragma_frag.split('.').next().map(String::from).unwrap()
40 } else {
41 options.jsx_pragma_frag.to_string()
42 };
43
44 Self {
45 only_remove_type_imports: options.only_remove_type_imports,
46 has_super_call: false,
47 assignments: vec![],
48 has_jsx_element: false,
49 has_jsx_fragment: false,
50 jsx_element_import_name,
51 jsx_fragment_import_name,
52 }
53 }
54}
55
56impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a> {
57 fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
58 let mut no_modules_remaining = true;
59 let mut some_modules_deleted = false;
60
61 program.body.retain_mut(|stmt| {
62 let need_retain = match stmt {
63 Statement::ExportNamedDeclaration(decl) if decl.declaration.is_some() => {
64 decl.declaration.as_ref().is_some_and(|decl| !decl.is_typescript_syntax())
65 }
66 Statement::ExportNamedDeclaration(decl) => {
67 if decl.export_kind.is_type() {
68 false
69 } else if decl.specifiers.is_empty() {
70 true
73 } else {
74 decl.specifiers
75 .retain(|specifier| Self::can_retain_export_specifier(specifier, ctx));
76 !decl.specifiers.is_empty()
78 }
79 }
80 Statement::ExportAllDeclaration(decl) => !decl.export_kind.is_type(),
81 Statement::ExportDefaultDeclaration(decl) => {
82 !decl.is_typescript_syntax()
83 && !matches!(
84 &decl.declaration,
85 ExportDefaultDeclarationKind::Identifier(ident) if Self::is_refers_to_type(ident, ctx)
86 )
87 }
88 Statement::ImportDeclaration(decl) => {
89 if decl.import_kind.is_type() {
90 false
91 } else if let Some(specifiers) = &mut decl.specifiers {
92 if specifiers.is_empty() {
93 decl.specifiers = None;
95 true
96 } else {
97 specifiers.retain(|specifier| {
98 let id = match specifier {
99 ImportDeclarationSpecifier::ImportSpecifier(s) => {
100 if s.import_kind.is_type() {
101 return false;
102 }
103 &s.local
104 }
105 ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
106 &s.local
107 }
108 ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
109 &s.local
110 }
111 };
112 if self.only_remove_type_imports {
115 true
116 } else {
117 self.has_value_reference(id, ctx)
118 }
119 });
120
121 if specifiers.is_empty() {
122 if self.only_remove_type_imports {
124 decl.specifiers = None;
126 true
127 } else {
128 false
130 }
131 } else {
132 true
133 }
134 }
135 } else {
136 true
137 }
138 }
139 Statement::TSTypeAliasDeclaration(_)
143 | Statement::TSExportAssignment(_)
144 | Statement::TSNamespaceExportDeclaration(_) => false,
145 _ => return true,
146 };
147
148 if need_retain {
149 no_modules_remaining = false;
150 } else {
151 some_modules_deleted = true;
152 }
153
154 need_retain
155 });
156
157 if no_modules_remaining && some_modules_deleted && ctx.state.module_imports.is_empty() {
161 let export_decl = Statement::ExportNamedDeclaration(
162 ctx.ast.plain_export_named_declaration(SPAN, ctx.ast.vec(), None),
163 );
164 program.body.push(export_decl);
165 }
166 }
167
168 fn enter_arrow_function_expression(
169 &mut self,
170 expr: &mut ArrowFunctionExpression<'a>,
171 _ctx: &mut TraverseCtx<'a>,
172 ) {
173 expr.type_parameters = None;
174 expr.return_type = None;
175 }
176
177 fn enter_variable_declarator(
178 &mut self,
179 decl: &mut VariableDeclarator<'a>,
180 _ctx: &mut TraverseCtx<'a>,
181 ) {
182 decl.definite = false;
183 decl.type_annotation = None;
184 }
185
186 fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, _ctx: &mut TraverseCtx<'a>) {
187 expr.type_arguments = None;
188 }
189
190 fn enter_chain_element(&mut self, element: &mut ChainElement<'a>, ctx: &mut TraverseCtx<'a>) {
191 if let ChainElement::TSNonNullExpression(e) = element {
192 *element = match e.expression.get_inner_expression_mut().take_in(ctx.ast) {
193 Expression::CallExpression(call_expr) => ChainElement::CallExpression(call_expr),
194 expr @ match_member_expression!(Expression) => {
195 ChainElement::from(expr.into_member_expression())
196 }
197 _ => {
198 return;
200 }
201 }
202 }
203 }
204
205 fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) {
206 func.type_parameters = None;
209 func.return_type = None;
210 func.this_param = None;
211 }
212
213 fn enter_class(&mut self, class: &mut Class<'a>, _ctx: &mut TraverseCtx<'a>) {
214 class.type_parameters = None;
217 class.super_type_arguments = None;
218 class.implements.clear();
219 class.r#abstract = false;
220
221 class.body.body.retain(|elem| match elem {
223 ClassElement::MethodDefinition(method) => {
224 matches!(method.r#type, MethodDefinitionType::MethodDefinition)
225 && !method.value.is_typescript_syntax()
226 }
227 ClassElement::PropertyDefinition(prop) => {
228 matches!(prop.r#type, PropertyDefinitionType::PropertyDefinition)
229 }
230 ClassElement::AccessorProperty(prop) => {
231 matches!(prop.r#type, AccessorPropertyType::AccessorProperty)
232 }
233 ClassElement::TSIndexSignature(_) => false,
234 ClassElement::StaticBlock(_) => true,
235 });
236 }
237
238 fn exit_class(&mut self, class: &mut Class<'a>, _: &mut TraverseCtx<'a>) {
239 class
243 .body
244 .body
245 .retain(|elem| !matches!(elem, ClassElement::PropertyDefinition(prop) if prop.declare));
246 }
247
248 fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
249 if expr.is_typescript_syntax() {
250 let inner_expr = expr.get_inner_expression_mut();
251 *expr = inner_expr.take_in(ctx.ast);
252 }
253 }
254
255 fn enter_simple_assignment_target(
256 &mut self,
257 target: &mut SimpleAssignmentTarget<'a>,
258 ctx: &mut TraverseCtx<'a>,
259 ) {
260 if let Some(expr) = target.get_expression_mut() {
261 match expr.get_inner_expression_mut() {
262 inner_expr @ Expression::Identifier(_) => {
264 let inner_expr = inner_expr.take_in(ctx.ast);
265 let Expression::Identifier(ident) = inner_expr else {
266 unreachable!();
267 };
268 *target = SimpleAssignmentTarget::AssignmentTargetIdentifier(ident);
269 }
270 inner_expr @ match_member_expression!(Expression) => {
272 let inner_expr = inner_expr.take_in(ctx.ast);
273 let member_expr = inner_expr.into_member_expression();
274 *target = SimpleAssignmentTarget::from(member_expr);
275 }
276 _ => {
277 ctx.state.error(OxcDiagnostic::error("Cannot strip out typescript syntax if SimpleAssignmentTarget is not an IdentifierReference or MemberExpression"));
279 }
280 }
281 }
282 }
283
284 fn enter_assignment_target(
285 &mut self,
286 target: &mut AssignmentTarget<'a>,
287 ctx: &mut TraverseCtx<'a>,
288 ) {
289 if let Some(expr) = target.get_expression_mut() {
290 let inner_expr = expr.get_inner_expression_mut();
291 if inner_expr.is_member_expression() {
292 let inner_expr = inner_expr.take_in(ctx.ast);
293 let member_expr = inner_expr.into_member_expression();
294 *target = AssignmentTarget::from(member_expr);
295 }
296 }
297 }
298
299 fn enter_formal_parameter(
300 &mut self,
301 param: &mut FormalParameter<'a>,
302 _ctx: &mut TraverseCtx<'a>,
303 ) {
304 param.accessibility = None;
305 param.readonly = false;
306 param.r#override = false;
307 param.optional = false;
308 param.type_annotation = None;
309 }
310
311 fn exit_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) {
312 func.this_param = None;
313 func.type_parameters = None;
314 func.return_type = None;
315 }
316
317 fn enter_jsx_opening_element(
318 &mut self,
319 elem: &mut JSXOpeningElement<'a>,
320 _ctx: &mut TraverseCtx<'a>,
321 ) {
322 elem.type_arguments = None;
323 }
324
325 fn enter_method_definition(
326 &mut self,
327 def: &mut MethodDefinition<'a>,
328 _ctx: &mut TraverseCtx<'a>,
329 ) {
330 def.accessibility = None;
331 def.optional = false;
332 def.r#override = false;
333 }
334
335 fn enter_new_expression(&mut self, expr: &mut NewExpression<'a>, _ctx: &mut TraverseCtx<'a>) {
336 expr.type_arguments = None;
337 }
338
339 fn enter_property_definition(
340 &mut self,
341 def: &mut PropertyDefinition<'a>,
342 _ctx: &mut TraverseCtx<'a>,
343 ) {
344 def.accessibility = None;
345 def.definite = false;
346 def.r#override = false;
347 def.optional = false;
348 def.readonly = false;
349 def.type_annotation = None;
350 }
351
352 fn enter_accessor_property(
353 &mut self,
354 def: &mut AccessorProperty<'a>,
355 _ctx: &mut TraverseCtx<'a>,
356 ) {
357 def.accessibility = None;
358 def.definite = false;
359 def.type_annotation = None;
360 }
361
362 fn enter_statements(
363 &mut self,
364 stmts: &mut ArenaVec<'a, Statement<'a>>,
365 ctx: &mut TraverseCtx<'a>,
366 ) {
367 stmts.retain(|stmt| match stmt {
369 match_declaration!(Statement) => {
370 self.should_keep_declaration(stmt.to_declaration(), ctx)
371 }
372 _ => true,
373 });
374 }
375
376 fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
377 if self.assignments.is_empty() {
379 return;
380 }
381
382 let has_super_call = matches!(stmt, Statement::ExpressionStatement(stmt) if stmt.expression.is_super_call_expression());
383 if !has_super_call {
384 return;
385 }
386
387 let assignments: Vec<_> = self
389 .assignments
390 .iter()
391 .map(|assignment| assignment.create_this_property_assignment(ctx))
392 .collect();
393 ctx.state.statement_injector.insert_many_after(stmt, assignments);
394 self.has_super_call = true;
395 }
396
397 fn enter_if_statement(&mut self, stmt: &mut IfStatement<'a>, ctx: &mut TraverseCtx<'a>) {
404 if !self.assignments.is_empty() {
405 let consequent_span = match &stmt.consequent {
406 Statement::ExpressionStatement(expr)
407 if expr.expression.is_super_call_expression() =>
408 {
409 Some(expr.span)
410 }
411 _ => None,
412 };
413 if let Some(span) = consequent_span {
414 let consequent = stmt.consequent.take_in(ctx.ast);
415 stmt.consequent = Self::create_block_with_statement(consequent, span, ctx);
416 }
417
418 let alternate_span = match &stmt.alternate {
419 Some(Statement::ExpressionStatement(expr))
420 if expr.expression.is_super_call_expression() =>
421 {
422 Some(expr.span)
423 }
424 _ => None,
425 };
426 if let Some(span) = alternate_span {
427 let alternate = stmt.alternate.take().unwrap();
428 stmt.alternate = Some(Self::create_block_with_statement(alternate, span, ctx));
429 }
430 }
431
432 Self::replace_with_empty_block_if_ts(&mut stmt.consequent, ctx.current_scope_id(), ctx);
433
434 if stmt.alternate.as_ref().is_some_and(Statement::is_typescript_syntax) {
435 stmt.alternate = None;
436 }
437 }
438
439 fn enter_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
440 let scope_id = stmt.scope_id();
441 Self::replace_for_statement_body_with_empty_block_if_ts(&mut stmt.body, scope_id, ctx);
442 }
443
444 fn enter_for_in_statement(&mut self, stmt: &mut ForInStatement<'a>, ctx: &mut TraverseCtx<'a>) {
445 let scope_id = stmt.scope_id();
446 Self::replace_for_statement_body_with_empty_block_if_ts(&mut stmt.body, scope_id, ctx);
447 }
448
449 fn enter_for_of_statement(&mut self, stmt: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) {
450 let scope_id = stmt.scope_id();
451 Self::replace_for_statement_body_with_empty_block_if_ts(&mut stmt.body, scope_id, ctx);
452 }
453
454 fn enter_while_statement(&mut self, stmt: &mut WhileStatement<'a>, ctx: &mut TraverseCtx<'a>) {
455 Self::replace_with_empty_block_if_ts(&mut stmt.body, ctx.current_scope_id(), ctx);
456 }
457
458 fn enter_do_while_statement(
459 &mut self,
460 stmt: &mut DoWhileStatement<'a>,
461 ctx: &mut TraverseCtx<'a>,
462 ) {
463 Self::replace_with_empty_block_if_ts(&mut stmt.body, ctx.current_scope_id(), ctx);
464 }
465
466 fn enter_tagged_template_expression(
467 &mut self,
468 expr: &mut TaggedTemplateExpression<'a>,
469 _ctx: &mut TraverseCtx<'a>,
470 ) {
471 expr.type_arguments = None;
472 }
473
474 fn enter_jsx_element(&mut self, _elem: &mut JSXElement<'a>, _ctx: &mut TraverseCtx<'a>) {
475 self.has_jsx_element = true;
476 }
477
478 fn enter_jsx_fragment(&mut self, _elem: &mut JSXFragment<'a>, _ctx: &mut TraverseCtx<'a>) {
479 self.has_jsx_fragment = true;
480 }
481
482 fn enter_formal_parameter_rest(
483 &mut self,
484 node: &mut FormalParameterRest<'a>,
485 _ctx: &mut oxc_traverse::TraverseCtx<'a, TransformState<'a>>,
486 ) {
487 node.type_annotation = None;
488 }
489
490 fn enter_catch_parameter(
491 &mut self,
492 node: &mut CatchParameter<'a>,
493 _ctx: &mut oxc_traverse::TraverseCtx<'a, TransformState<'a>>,
494 ) {
495 node.type_annotation = None;
496 }
497}
498
499impl<'a> TypeScriptAnnotations<'a> {
500 #[inline]
501 fn should_keep_declaration(&self, decl: &Declaration<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
502 match decl {
503 Declaration::TSTypeAliasDeclaration(_)
505 | Declaration::TSInterfaceDeclaration(_)
506 | Declaration::TSGlobalDeclaration(_) => false,
507 Declaration::VariableDeclaration(var_decl) => !var_decl.declare,
509 Declaration::FunctionDeclaration(func_decl) => {
511 !func_decl.declare && func_decl.body.is_some()
512 }
513 Declaration::ClassDeclaration(class_decl) => !class_decl.declare,
515 Declaration::TSModuleDeclaration(module_decl) => {
519 !module_decl.declare
520 && !matches!(
521 &module_decl.id,
522 TSModuleDeclarationName::Identifier(ident)
523 if ctx.scoping().symbol_flags(ident.symbol_id()).is_namespace_module()
524 )
525 }
526 Declaration::TSEnumDeclaration(enum_decl) => !enum_decl.declare,
528 Declaration::TSImportEqualsDeclaration(import_equals) => {
530 let keep = import_equals.import_kind.is_value()
531 && (self.only_remove_type_imports
532 || !ctx
533 .scoping()
534 .get_resolved_references(import_equals.id.symbol_id())
535 .all(Reference::is_type));
536 if !keep {
537 let scope_id = ctx.current_scope_id();
538 ctx.scoping_mut().remove_binding(scope_id, import_equals.id.name);
539 }
540 keep
541 }
542 }
543 }
544
545 fn is_jsx_imports(&self, name: &str) -> bool {
548 self.has_jsx_element && name == self.jsx_element_import_name
549 || self.has_jsx_fragment && name == self.jsx_fragment_import_name
550 }
551
552 fn create_block_with_statement(
553 stmt: Statement<'a>,
554 span: Span,
555 ctx: &mut TraverseCtx<'a>,
556 ) -> Statement<'a> {
557 let scope_id = ctx.insert_scope_below_statement(&stmt, ScopeFlags::empty());
558 ctx.ast.statement_block_with_scope_id(span, ctx.ast.vec1(stmt), scope_id)
559 }
560
561 fn replace_for_statement_body_with_empty_block_if_ts(
562 body: &mut Statement<'a>,
563 parent_scope_id: ScopeId,
564 ctx: &mut TraverseCtx<'a>,
565 ) {
566 Self::replace_with_empty_block_if_ts(body, parent_scope_id, ctx);
567 }
568
569 fn replace_with_empty_block_if_ts(
570 stmt: &mut Statement<'a>,
571 parent_scope_id: ScopeId,
572 ctx: &mut TraverseCtx<'a>,
573 ) {
574 if stmt.is_typescript_syntax() {
575 let scope_id = ctx.create_child_scope(parent_scope_id, ScopeFlags::empty());
576 *stmt = ctx.ast.statement_block_with_scope_id(stmt.span(), ctx.ast.vec(), scope_id);
577 }
578 }
579
580 fn has_value_reference(&self, id: &BindingIdentifier<'a>, ctx: &TraverseCtx<'a>) -> bool {
581 let symbol_id = id.symbol_id();
582
583 if (ctx.scoping().symbol_flags(symbol_id) - SymbolFlags::Import).is_value() {
588 return false;
589 }
590
591 if ctx.scoping().get_resolved_references(symbol_id).any(|reference| !reference.is_type()) {
592 return true;
593 }
594
595 self.is_jsx_imports(&id.name)
596 }
597
598 fn can_retain_export_specifier(specifier: &ExportSpecifier<'a>, ctx: &TraverseCtx<'a>) -> bool {
599 if specifier.export_kind.is_type() {
600 return false;
601 }
602 !matches!(&specifier.local, ModuleExportName::IdentifierReference(ident) if Self::is_refers_to_type(ident, ctx))
603 }
604
605 fn is_refers_to_type(ident: &IdentifierReference<'a>, ctx: &TraverseCtx<'a>) -> bool {
606 let scoping = ctx.scoping();
607 let reference = scoping.get_reference(ident.reference_id());
608
609 reference.symbol_id().is_some_and(|symbol_id| {
610 reference.is_type()
611 || scoping.symbol_flags(symbol_id).is_ambient()
612 && scoping.symbol_redeclarations(symbol_id).iter().all(|r| r.flags.is_ambient())
613 })
614 }
615}
616
617struct Assignment<'a> {
618 span: Span,
619 name: Str<'a>,
620 symbol_id: SymbolId,
621}
622
623impl<'a> Assignment<'a> {
624 fn create_this_property_assignment(&self, ctx: &mut TraverseCtx<'a>) -> Statement<'a> {
626 let reference_id = ctx.create_bound_reference(self.symbol_id, ReferenceFlags::Read);
627 let id = ctx.ast.identifier_reference_with_reference_id(self.span, self.name, reference_id);
628
629 ctx.ast.statement_expression(
630 SPAN,
631 ctx.ast.expression_assignment(
632 SPAN,
633 AssignmentOperator::Assign,
634 SimpleAssignmentTarget::from(ctx.ast.member_expression_static(
635 SPAN,
636 ctx.ast.expression_this(SPAN),
637 ctx.ast.identifier_name(self.span, self.name),
638 false,
639 ))
640 .into(),
641 Expression::Identifier(ctx.alloc(id)),
642 ),
643 )
644 }
645}