1use std::{cell::RefCell, fmt::Display, rc::Rc};
2
3use anyhow::Context;
4use serde::{Deserialize, Serialize};
5use swc_common::{
6 comments::SingleThreadedComments,
7 errors::{Handler, HANDLER},
8 source_map::DefaultSourceMapGenConfig,
9 sync::Lrc,
10 BytePos, FileName, Mark, SourceMap, Span, Spanned,
11};
12use swc_ecma_ast::{
13 ArrayPat, ArrowExpr, AutoAccessor, BindingIdent, Class, ClassDecl, ClassMethod, ClassProp,
14 Constructor, Decl, DefaultDecl, DoWhileStmt, EsVersion, ExportAll, ExportDecl,
15 ExportDefaultDecl, ExportSpecifier, FnDecl, ForInStmt, ForOfStmt, ForStmt, GetterProp, IfStmt,
16 ImportDecl, ImportSpecifier, ModuleDecl, ModuleItem, NamedExport, ObjectPat, Param, Pat,
17 PrivateMethod, PrivateProp, Program, ReturnStmt, SetterProp, Stmt, ThrowStmt, TsAsExpr,
18 TsConstAssertion, TsEnumDecl, TsExportAssignment, TsImportEqualsDecl, TsIndexSignature,
19 TsInstantiation, TsModuleDecl, TsModuleName, TsNamespaceBody, TsNonNullExpr, TsParamPropParam,
20 TsSatisfiesExpr, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion, TsTypeParamDecl,
21 TsTypeParamInstantiation, VarDeclarator, WhileStmt, YieldExpr,
22};
23use swc_ecma_parser::{
24 lexer::Lexer,
25 token::{BinOpToken, IdentLike, KnownIdent, Token, TokenAndSpan, Word},
26 Capturing, Parser, StringInput, Syntax, TsSyntax,
27};
28use swc_ecma_transforms_base::{
29 fixer::fixer,
30 helpers::{inject_helpers, Helpers, HELPERS},
31 hygiene::hygiene,
32 resolver,
33};
34use swc_ecma_transforms_typescript::typescript;
35use swc_ecma_visit::{Visit, VisitWith};
36#[cfg(feature = "wasm-bindgen")]
37use wasm_bindgen::prelude::*;
38
39#[derive(Default, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct Options {
42 #[serde(default)]
43 pub module: Option<bool>,
44 #[serde(default)]
45 pub filename: Option<String>,
46
47 #[serde(default = "default_ts_syntax")]
48 pub parser: TsSyntax,
49
50 #[serde(default)]
51 pub mode: Mode,
52
53 #[serde(default)]
54 pub transform: Option<typescript::Config>,
55
56 #[serde(default)]
57 pub deprecated_ts_module_as_error: Option<bool>,
58
59 #[serde(default)]
60 pub source_map: bool,
61}
62
63#[cfg(feature = "wasm-bindgen")]
64#[wasm_bindgen(typescript_custom_section)]
65const Type_Options: &'static str = r#"
66interface Options {
67 module?: boolean;
68 filename?: string;
69 mode?: Mode;
70 transform?: TransformConfig;
71 deprecatedTsModuleAsError?: boolean;
72 sourceMap?: boolean;
73}
74
75interface TransformConfig {
76 /**
77 * @see https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax
78 */
79 verbatimModuleSyntax?: boolean;
80 /**
81 * Native class properties support
82 */
83 nativeClassProperties?: boolean;
84 importNotUsedAsValues?: "remove" | "preserve";
85 /**
86 * Don't create `export {}`.
87 * By default, strip creates `export {}` for modules to preserve module
88 * context.
89 *
90 * @see https://github.com/swc-project/swc/issues/1698
91 */
92 noEmptyExport?: boolean;
93 importExportAssignConfig?: "Classic" | "Preserve" | "NodeNext" | "EsNext";
94 /**
95 * Disables an optimization that inlines TS enum member values
96 * within the same module that assumes the enum member values
97 * are never modified.
98 *
99 * Defaults to false.
100 */
101 tsEnumIsMutable?: boolean;
102}
103"#;
104
105#[derive(Debug, Default, Deserialize)]
106#[serde(rename_all = "kebab-case")]
107pub enum Mode {
108 #[default]
109 StripOnly,
110 Transform,
111}
112
113#[cfg(feature = "wasm-bindgen")]
114#[wasm_bindgen(typescript_custom_section)]
115const Type_Mode: &'static str = r#"
116type Mode = "strip-only" | "transform";
117"#;
118
119fn default_ts_syntax() -> TsSyntax {
120 TsSyntax {
121 decorators: true,
122 ..Default::default()
123 }
124}
125
126#[derive(Debug, Serialize)]
127pub struct TransformOutput {
128 pub code: String,
129 pub map: Option<String>,
130}
131
132#[cfg(feature = "wasm-bindgen")]
133#[wasm_bindgen(typescript_custom_section)]
134const Type_TransformOutput: &'static str = r#"
135interface TransformOutput {
136 code: string;
137 map?: string;
138}
139"#;
140
141#[derive(Debug, Serialize)]
142pub struct TsError {
143 pub message: String,
144 pub code: ErrorCode,
145}
146
147impl std::fmt::Display for TsError {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "[{}] {}", self.code, self.message)
150 }
151}
152
153impl std::error::Error for TsError {
154 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155 None
156 }
157}
158
159#[non_exhaustive]
160#[derive(Debug, Clone, Copy, Serialize)]
161pub enum ErrorCode {
162 InvalidSyntax,
163 UnsupportedSyntax,
164 Unknown,
165}
166
167impl Display for ErrorCode {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 write!(f, "{:?}", self)
170 }
171}
172
173impl From<anyhow::Error> for TsError {
174 fn from(err: anyhow::Error) -> Self {
175 TsError {
176 message: err.to_string(),
177 code: ErrorCode::Unknown,
178 }
179 }
180}
181
182pub fn operate(
183 cm: &Lrc<SourceMap>,
184 handler: &Handler,
185 input: String,
186 options: Options,
187) -> Result<TransformOutput, TsError> {
188 let filename = options
189 .filename
190 .map_or(FileName::Anon, |f| FileName::Real(f.into()));
191
192 let fm = cm.new_source_file(filename.into(), input);
193
194 let syntax = Syntax::Typescript(options.parser);
195 let target = EsVersion::latest();
196
197 let comments = SingleThreadedComments::default();
198
199 let lexer = Capturing::new(Lexer::new(
200 syntax,
201 target,
202 StringInput::from(&*fm),
203 Some(&comments),
204 ));
205 let tokens = lexer.tokens().clone();
206
207 let mut parser = Parser::new_from(lexer);
208
209 let program = match options.module {
210 Some(true) => parser.parse_module().map(Program::Module),
211 Some(false) => parser.parse_script().map(Program::Script),
212 None => parser.parse_program(),
213 };
214 let errors = parser.take_errors();
215
216 let mut program = match program {
217 Ok(program) => program,
218 Err(err) => {
219 err.into_diagnostic(handler).emit();
220
221 for e in errors {
222 e.into_diagnostic(handler).emit();
223 }
224
225 return Err(TsError {
226 message: "Syntax error".to_string(),
227 code: ErrorCode::InvalidSyntax,
228 });
229 }
230 };
231
232 if !errors.is_empty() {
233 for e in errors {
234 e.into_diagnostic(handler).emit();
235 }
236
237 return Err(TsError {
238 message: "Syntax error".to_string(),
239 code: ErrorCode::InvalidSyntax,
240 });
241 }
242
243 drop(parser);
244
245 let deprecated_ts_module_as_error = options.deprecated_ts_module_as_error.unwrap_or_default();
246
247 match options.mode {
248 Mode::StripOnly => {
249 let mut tokens = RefCell::into_inner(Rc::try_unwrap(tokens).unwrap());
250
251 tokens.sort_by_key(|t| t.span);
252
253 if deprecated_ts_module_as_error {
254 program.visit_with(&mut ErrorOnTsModule {
255 src: &fm.src,
256 tokens: &tokens,
257 });
258 }
259
260 let mut ts_strip = TsStrip::new(fm.src.clone(), tokens);
262
263 program.visit_with(&mut ts_strip);
264 if handler.has_errors() {
265 return Err(TsError {
266 message: "Unsupported syntax".to_string(),
267 code: ErrorCode::UnsupportedSyntax,
268 });
269 }
270
271 let replacements = ts_strip.replacements;
272 let overwrites = ts_strip.overwrites;
273
274 if replacements.is_empty() && overwrites.is_empty() {
275 return Ok(TransformOutput {
276 code: fm.src.to_string(),
277 map: Default::default(),
278 });
279 }
280
281 let source = fm.src.clone();
282 let mut code = fm.src.to_string().into_bytes();
283
284 for r in replacements {
285 let (start, end) = (r.0 .0 as usize - 1, r.1 .0 as usize - 1);
286
287 for (i, c) in source[start..end].char_indices() {
288 let i = start + i;
289 match c {
290 '\u{0009}' | '\u{0000B}' | '\u{000C}' | '\u{FEFF}' => continue,
292 '\u{0020}' | '\u{00A0}' | '\u{1680}' | '\u{2000}' | '\u{2001}'
294 | '\u{2002}' | '\u{2003}' | '\u{2004}' | '\u{2005}' | '\u{2006}'
295 | '\u{2007}' | '\u{2008}' | '\u{2009}' | '\u{200A}' | '\u{202F}'
296 | '\u{205F}' | '\u{3000}' => continue,
297 '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => continue,
299 _ => match c.len_utf8() {
300 1 => {
301 code[i] = 0x20;
303 }
304 2 => {
305 code[i] = 0xc2;
307 code[i + 1] = 0xa0;
308 }
309 3 => {
310 code[i] = 0xe2;
312 code[i + 1] = 0x80;
313 code[i + 2] = 0x82;
314 }
315 4 => {
316 code[i] = 0x20;
320 code[i + 1] = 0xef;
322 code[i + 2] = 0xbb;
323 code[i + 3] = 0xbf;
324 }
325 _ => unreachable!(),
326 },
327 }
328 }
329 }
330
331 for (i, v) in overwrites {
332 code[i.0 as usize - 1] = v;
333 }
334
335 let code = if cfg!(debug_assertions) {
336 String::from_utf8(code).map_err(|err| TsError {
337 message: format!("failed to convert to utf-8: {}", err),
338 code: ErrorCode::Unknown,
339 })?
340 } else {
341 unsafe { String::from_utf8_unchecked(code) }
344 };
345
346 Ok(TransformOutput {
347 code,
348 map: Default::default(),
349 })
350 }
351
352 Mode::Transform => {
353 let unresolved_mark = Mark::new();
354 let top_level_mark = Mark::new();
355
356 HELPERS.set(&Helpers::new(false), || {
357 program.mutate(&mut resolver(unresolved_mark, top_level_mark, true));
358
359 if deprecated_ts_module_as_error {
360 let mut tokens = RefCell::into_inner(Rc::try_unwrap(tokens).unwrap());
361
362 tokens.sort_by_key(|t| t.span);
363
364 program.visit_with(&mut ErrorOnTsModule {
365 src: &fm.src,
366 tokens: &tokens,
367 });
368 }
369
370 program.mutate(&mut typescript::typescript(
371 options.transform.unwrap_or_default(),
372 unresolved_mark,
373 top_level_mark,
374 ));
375
376 program.mutate(&mut inject_helpers(unresolved_mark));
377
378 program.mutate(&mut hygiene());
379
380 program.mutate(&mut fixer(Some(&comments)));
381 });
382
383 let mut src = std::vec::Vec::new();
384 let mut src_map_buf = if options.source_map {
385 Some(Vec::new())
386 } else {
387 None
388 };
389
390 {
391 let mut emitter = swc_ecma_codegen::Emitter {
392 cfg: swc_ecma_codegen::Config::default(),
393 comments: if options.source_map {
394 Some(&comments)
395 } else {
396 None
397 },
398 cm: cm.clone(),
399 wr: swc_ecma_codegen::text_writer::JsWriter::new(
400 cm.clone(),
401 "\n",
402 &mut src,
403 src_map_buf.as_mut(),
404 ),
405 };
406
407 emitter.emit_program(&program).unwrap();
408
409 let map = src_map_buf
410 .map(|map| {
411 let map =
412 cm.build_source_map_with_config(&map, None, DefaultSourceMapGenConfig);
413
414 let mut s = std::vec::Vec::new();
415 map.to_writer(&mut s)
416 .context("failed to write source map")?;
417
418 String::from_utf8(s).context("source map was not utf8")
419 })
420 .transpose()?;
421
422 Ok(TransformOutput {
423 code: String::from_utf8(src).context("generated code was not utf-8")?,
424 map,
425 })
426 }
427 }
428 }
429}
430
431struct ErrorOnTsModule<'a> {
432 src: &'a str,
433 tokens: &'a [TokenAndSpan],
434}
435
436impl Visit for ErrorOnTsModule<'_> {
439 fn visit_stmt(&mut self, n: &Stmt) {
440 if n.is_decl() {
441 n.visit_children_with(self);
442 }
443 }
444
445 fn visit_decl(&mut self, n: &Decl) {
446 if n.is_ts_module() {
447 n.visit_children_with(self);
448 }
449 }
450
451 fn visit_module_decl(&mut self, n: &ModuleDecl) {
452 if n.is_export_decl() {
453 n.visit_children_with(self);
454 }
455 }
456
457 fn visit_ts_module_decl(&mut self, n: &TsModuleDecl) {
458 n.visit_children_with(self);
459
460 if n.global || n.id.is_str() {
461 return;
462 }
463
464 let mut pos = n.span.lo;
465
466 if n.declare {
467 let declare_index = self
468 .tokens
469 .binary_search_by_key(&pos, |t| t.span.lo)
470 .unwrap();
471
472 debug_assert_eq!(
473 self.tokens[declare_index].token,
474 Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Declare)))
475 );
476
477 let TokenAndSpan { token, span, .. } = &self.tokens[declare_index + 1];
478 if let Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Namespace))) = token {
482 return;
483 }
484
485 pos = span.lo;
486 } else if self.src.as_bytes()[pos.0 as usize - 1] != b'm' {
487 return;
488 }
489
490 HANDLER.with(|handler| {
491 handler
492 .struct_span_err(
493 span(pos, pos + BytePos(6)),
494 "`module` keyword is not supported. Use `namespace` instead.",
495 )
496 .emit();
497 });
498 }
499}
500
501struct TsStrip {
502 src: Lrc<String>,
503
504 replacements: Vec<(BytePos, BytePos)>,
506
507 overwrites: Vec<(BytePos, u8)>,
509
510 tokens: std::vec::Vec<TokenAndSpan>,
511}
512
513impl TsStrip {
514 fn new(src: Lrc<String>, tokens: std::vec::Vec<TokenAndSpan>) -> Self {
515 TsStrip {
516 src,
517 replacements: Default::default(),
518 overwrites: Default::default(),
519 tokens,
520 }
521 }
522}
523
524impl TsStrip {
525 fn add_replacement(&mut self, span: Span) {
526 self.replacements.push((span.lo, span.hi));
527 }
528
529 fn add_overwrite(&mut self, pos: BytePos, value: u8) {
530 self.overwrites.push((pos, value));
531 }
532
533 fn get_src_slice(&self, span: Span) -> &str {
534 &self.src[(span.lo.0 - 1) as usize..(span.hi.0 - 1) as usize]
535 }
536
537 fn get_next_token_index(&self, pos: BytePos) -> usize {
538 let index = self.tokens.binary_search_by_key(&pos, |t| t.span.lo);
539 match index {
540 Ok(index) => index,
541 Err(index) => index,
542 }
543 }
544
545 fn get_next_token(&self, pos: BytePos) -> &TokenAndSpan {
546 &self.tokens[self.get_next_token_index(pos)]
547 }
548
549 fn get_prev_token_index(&self, pos: BytePos) -> usize {
550 let index = self.tokens.binary_search_by_key(&pos, |t| t.span.lo);
551 match index {
552 Ok(index) => index,
553 Err(index) => index - 1,
554 }
555 }
556
557 fn get_prev_token(&self, pos: BytePos) -> &TokenAndSpan {
558 &self.tokens[self.get_prev_token_index(pos)]
559 }
560
561 fn fix_asi(&mut self, span: Span) {
562 let index = self.get_prev_token_index(span.lo);
563 if index == 0 {
564 return;
566 }
567
568 let TokenAndSpan {
569 token: prev_token,
570 span: prev_span,
571 ..
572 } = &self.tokens[index - 1];
573
574 let index = self.get_prev_token_index(span.hi - BytePos(1));
575 if index == self.tokens.len() - 1 {
576 return;
578 }
579
580 let TokenAndSpan {
581 token,
582 had_line_break,
583 ..
584 } = &self.tokens[index + 1];
585
586 if !*had_line_break {
587 return;
588 }
589
590 match token {
593 Token::LParen
594 | Token::LBracket
595 | Token::BackQuote
596 | Token::BinOp(BinOpToken::Add | BinOpToken::Sub | BinOpToken::Div) => {
597 if prev_token == &Token::Semi {
598 self.add_overwrite(prev_span.lo, b';');
599 return;
600 }
601
602 self.add_overwrite(span.lo, b';');
603 }
604
605 _ => {}
606 }
607 }
608
609 fn fix_asi_in_expr(&mut self, span: Span) {
610 let index = self.get_prev_token_index(span.hi - BytePos(1));
611 if index == self.tokens.len() - 1 {
612 return;
613 }
614
615 if let TokenAndSpan {
616 token: Token::LParen | Token::LBracket | Token::BackQuote,
618 had_line_break: true,
619 ..
620 } = &self.tokens[index + 1]
621 {
622 self.add_overwrite(span.lo, b';');
623 }
624 }
625
626 fn strip_class_modifier(&mut self, mut start_pos: BytePos, key_pos: BytePos) {
627 let mut index = self.get_next_token_index(start_pos);
628
629 while start_pos < key_pos {
630 let TokenAndSpan { token, span, .. } = &self.tokens[index];
631 start_pos = span.hi;
632 index += 1;
633
634 let next = &self.tokens[index];
635
636 if next.had_line_break {
637 return;
638 }
639
640 if !matches!(
643 next.token,
644 Token::LBracket
645 | Token::LBrace
646 | Token::BinOp(BinOpToken::Mul)
647 | Token::DotDotDot
648 | Token::Hash
649 | Token::Word(_)
650 | Token::Str { .. }
651 | Token::Num { .. }
652 | Token::BigInt { .. }
653 ) {
654 return;
655 }
656
657 match token {
658 Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Static))) => {
659 continue;
660 }
661 Token::Word(Word::Ident(IdentLike::Known(
662 KnownIdent::Readonly
663 | KnownIdent::Public
664 | KnownIdent::Protected
665 | KnownIdent::Private,
666 ))) => {
667 self.add_replacement(*span);
668 }
669 Token::Word(Word::Ident(IdentLike::Other(o))) if *o == "override" => {
670 self.add_replacement(*span);
671 }
672 _ => {
673 return;
674 }
675 }
676 }
677 }
678
679 fn strip_definite_mark(&mut self, index: usize) {
680 self.strip_token(index, Token::Bang);
681 }
682
683 fn strip_optional_mark(&mut self, index: usize) {
684 self.strip_token(index, Token::QuestionMark);
685 }
686
687 fn strip_token(&mut self, index: usize, expected: Token) {
688 let TokenAndSpan { token, span, .. } = &self.tokens[index];
689 debug_assert_eq!(*token, expected);
690
691 self.add_replacement(*span);
692 }
693
694 fn fix_asi_in_arrow_expr(&mut self, arrow_expr: &ArrowExpr) {
704 if let Some(tp) = &arrow_expr.type_params {
705 let l_paren = self.get_next_token(tp.span.hi);
706 debug_assert_eq!(l_paren.token, Token::LParen);
707
708 let slice = self.get_src_slice(tp.span.with_hi(l_paren.span.lo));
709
710 if !slice.chars().any(is_new_line) {
711 return;
712 }
713
714 let l_paren_pos = l_paren.span.lo;
715 let l_lt_pos = tp.span.lo;
716
717 self.add_overwrite(l_paren_pos, b' ');
718 self.add_overwrite(l_lt_pos, b'(');
719 }
720 }
721}
722
723impl Visit for TsStrip {
724 fn visit_var_declarator(&mut self, n: &VarDeclarator) {
725 if n.definite {
726 if let Some(id) = n.name.as_ident() {
727 let mark_index = self.get_next_token_index(id.span.hi);
728 self.strip_definite_mark(mark_index);
729 };
730 }
731
732 n.visit_children_with(self);
733 }
734
735 fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
736 'type_params: {
737 if let Some(tp) = &n.type_params {
749 self.add_replacement(tp.span);
750
751 if !n.is_async {
752 break 'type_params;
753 }
754
755 let slice = self.get_src_slice(tp.span);
756 if !slice.chars().any(is_new_line) {
757 break 'type_params;
758 }
759
760 let l_paren = self.get_next_token(tp.span.hi);
761 debug_assert_eq!(l_paren.token, Token::LParen);
762 let l_paren_pos = l_paren.span.lo;
763 let l_lt_pos = tp.span.lo;
764
765 self.add_overwrite(l_paren_pos, b' ');
766 self.add_overwrite(l_lt_pos, b'(');
767 }
768 }
769
770 if let Some(ret) = &n.return_type {
771 self.add_replacement(ret.span);
772
773 let r_paren = self.get_prev_token(ret.span.lo - BytePos(1));
774 debug_assert_eq!(r_paren.token, Token::RParen);
775 let arrow = self.get_next_token(ret.span.hi);
776 debug_assert_eq!(arrow.token, Token::Arrow);
777 let span = span(r_paren.span.lo, arrow.span.lo);
778
779 let slice = self.get_src_slice(span);
780 if slice.chars().any(is_new_line) {
781 self.add_replacement(r_paren.span);
782
783 let mut pos = ret.span.hi - BytePos(1);
800 while !self.src.as_bytes()[pos.0 as usize - 1].is_utf8_char_boundary() {
801 self.add_overwrite(pos, b' ');
802 pos = pos - BytePos(1);
803 }
804
805 self.add_overwrite(pos, b')');
806 }
807 }
808
809 n.params.visit_with(self);
810 n.body.visit_with(self);
811 }
812
813 fn visit_return_stmt(&mut self, n: &ReturnStmt) {
814 let Some(arg) = n.arg.as_deref() else {
815 return;
816 };
817
818 arg.visit_with(self);
819
820 let Some(arrow_expr) = arg.as_arrow() else {
821 return;
822 };
823
824 if arrow_expr.is_async {
825 return;
827 }
828
829 self.fix_asi_in_arrow_expr(arrow_expr);
830 }
831
832 fn visit_yield_expr(&mut self, n: &YieldExpr) {
833 let Some(arg) = &n.arg else {
834 return;
835 };
836
837 arg.visit_with(self);
838
839 let Some(arrow_expr) = arg.as_arrow() else {
840 return;
841 };
842
843 if arrow_expr.is_async {
844 return;
846 }
847
848 self.fix_asi_in_arrow_expr(arrow_expr);
849 }
850
851 fn visit_throw_stmt(&mut self, n: &ThrowStmt) {
852 let arg = &n.arg;
853
854 arg.visit_with(self);
855
856 let Some(arrow_expr) = arg.as_arrow() else {
857 return;
858 };
859
860 if arrow_expr.is_async {
861 return;
863 }
864
865 self.fix_asi_in_arrow_expr(arrow_expr);
866 }
867
868 fn visit_binding_ident(&mut self, n: &BindingIdent) {
869 n.visit_children_with(self);
870
871 if n.optional {
872 let mark_index = if let Some(type_ann) = &n.type_ann {
873 self.get_prev_token_index(type_ann.span.lo - BytePos(1))
874 } else {
875 self.get_next_token_index(n.span.hi)
876 };
877
878 self.strip_optional_mark(mark_index);
879 }
880 }
881
882 fn visit_class(&mut self, n: &Class) {
883 if n.is_abstract {
884 let mark_pos = n.decorators.last().map_or(n.span.lo, |d| d.span.hi);
885 let r#abstract = self.get_next_token_index(mark_pos);
886
887 self.strip_token(
888 r#abstract,
889 Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Abstract))),
890 )
891 }
892
893 if !n.implements.is_empty() {
894 let implements =
895 self.get_prev_token(n.implements.first().unwrap().span.lo - BytePos(1));
896 debug_assert_eq!(
897 implements.token,
898 Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Implements)))
899 );
900
901 let last = n.implements.last().unwrap();
902 let span = span(implements.span.lo, last.span.hi);
903 self.add_replacement(span);
904 }
905
906 n.visit_children_with(self);
907 }
908
909 fn visit_constructor(&mut self, n: &Constructor) {
910 if n.body.is_none() {
911 self.add_replacement(n.span);
912 return;
913 }
914
915 debug_assert!(!n.is_optional);
917
918 if n.accessibility.is_some() {
919 self.strip_class_modifier(n.span.lo, n.key.span_lo());
920 }
921
922 n.visit_children_with(self);
923 }
924
925 fn visit_class_method(&mut self, n: &ClassMethod) {
926 if n.function.body.is_none() || n.is_abstract {
927 self.add_replacement(n.span);
928 return;
929 }
930
931 let has_modifier = n.is_override || n.accessibility.is_some();
932
933 let start_pos = n
935 .function
936 .decorators
937 .last()
938 .map_or(n.span.lo, |d| d.span.hi);
939
940 if has_modifier {
941 self.strip_class_modifier(start_pos, n.key.span_lo());
942 }
943
944 if n.is_optional {
945 let mark_index = self.get_next_token_index(n.key.span_hi());
946 self.strip_optional_mark(mark_index);
947 }
948
949 if has_modifier
962 && !n.is_static
963 && n.function.decorators.is_empty()
964 && (n.key.is_computed()
965 || n.function.is_generator
966 || n.key
967 .as_ident()
968 .filter(|k| matches!(k.sym.as_ref(), "in" | "instanceof"))
969 .is_some())
970 {
971 self.add_overwrite(start_pos, b';');
972 }
973
974 n.visit_children_with(self);
975 }
976
977 fn visit_class_prop(&mut self, n: &ClassProp) {
978 if n.declare || n.is_abstract {
979 self.add_replacement(n.span);
980 return;
981 }
982
983 let has_modifier = n.readonly || n.is_override || n.accessibility.is_some();
984 let start_pos = n.decorators.last().map_or(n.span.lo, |d| d.span.hi);
985
986 if has_modifier {
987 self.strip_class_modifier(start_pos, n.key.span_lo());
988 }
989
990 if n.is_optional {
991 let mark_index = self.get_next_token_index(n.key.span_hi());
992 self.strip_optional_mark(mark_index);
993 }
994 if n.definite {
995 let mark_index = self.get_next_token_index(n.key.span_hi());
996 self.strip_definite_mark(mark_index);
997 }
998
999 if n.value.is_none() {
1001 if let Some(key) = n.key.as_ident() {
1002 if matches!(key.sym.as_ref(), "get" | "set" | "static") {
1003 if let Some(type_ann) = &n.type_ann {
1006 self.add_overwrite(type_ann.span.lo, b';');
1007 }
1008 }
1009 }
1010 }
1011
1012 if !n.is_static
1018 && has_modifier
1019 && n.decorators.is_empty()
1020 && (n.key.is_computed()
1021 || n.key
1022 .as_ident()
1023 .filter(|k| matches!(k.sym.as_ref(), "in" | "instanceof"))
1024 .is_some())
1025 {
1026 self.add_overwrite(start_pos, b';');
1027 }
1028
1029 n.visit_children_with(self);
1030 }
1031
1032 fn visit_private_method(&mut self, n: &PrivateMethod) {
1033 debug_assert!(!n.is_override);
1034 debug_assert!(!n.is_abstract);
1035
1036 if n.accessibility.is_some() {
1038 let start_pos = n
1039 .function
1040 .decorators
1041 .last()
1042 .map_or(n.span.lo, |d| d.span.hi);
1043
1044 self.strip_class_modifier(start_pos, n.key.span.lo);
1045 }
1046
1047 if n.is_optional {
1048 let mark_index = self.get_next_token_index(n.key.span.hi);
1049 self.strip_optional_mark(mark_index);
1050 }
1051
1052 n.visit_children_with(self);
1053 }
1054
1055 fn visit_private_prop(&mut self, n: &PrivateProp) {
1056 debug_assert!(!n.is_override);
1057
1058 if n.readonly || n.accessibility.is_some() {
1059 let start_pos = n.decorators.last().map_or(n.span.lo, |d| d.span.hi);
1060 self.strip_class_modifier(start_pos, n.key.span.lo);
1061 }
1062
1063 if n.is_optional {
1064 let mark_index = self.get_next_token_index(n.key.span.hi);
1065 self.strip_optional_mark(mark_index);
1066 }
1067
1068 if n.definite {
1069 let mark_index = self.get_next_token_index(n.key.span.hi);
1070 self.strip_definite_mark(mark_index);
1071 }
1072
1073 n.visit_children_with(self);
1074 }
1075
1076 fn visit_auto_accessor(&mut self, n: &AutoAccessor) {
1077 if n.is_abstract {
1078 self.add_replacement(n.span);
1079 return;
1080 }
1081
1082 let start_pos = n.decorators.last().map_or(n.span.lo, |d| d.span.hi);
1083
1084 self.strip_class_modifier(start_pos, n.key.span_lo());
1085
1086 if n.definite {
1087 let mark_index = self.get_next_token_index(n.key.span_hi());
1088 self.strip_definite_mark(mark_index);
1089 }
1090
1091 n.visit_children_with(self);
1092 }
1093
1094 fn visit_array_pat(&mut self, n: &ArrayPat) {
1095 if n.optional {
1096 let mark_index = if let Some(type_ann) = &n.type_ann {
1097 self.get_prev_token_index(type_ann.span.lo - BytePos(1))
1098 } else {
1099 self.get_next_token_index(n.span.hi)
1100 };
1101 self.strip_optional_mark(mark_index);
1102 }
1103
1104 n.visit_children_with(self);
1105 }
1106
1107 fn visit_object_pat(&mut self, n: &ObjectPat) {
1108 if n.optional {
1109 let mark_index = if let Some(type_ann) = &n.type_ann {
1110 self.get_prev_token_index(type_ann.span.lo - BytePos(1))
1111 } else {
1112 self.get_next_token_index(n.span.hi)
1113 };
1114 self.strip_optional_mark(mark_index);
1115 }
1116
1117 n.visit_children_with(self);
1118 }
1119
1120 fn visit_export_all(&mut self, n: &ExportAll) {
1121 if n.type_only {
1122 self.add_replacement(n.span);
1123 self.fix_asi(n.span);
1124 return;
1125 }
1126
1127 n.visit_children_with(self);
1128 }
1129
1130 fn visit_export_decl(&mut self, n: &ExportDecl) {
1131 if n.decl.is_ts_declare() || n.decl.is_uninstantiated() {
1132 self.add_replacement(n.span);
1133 self.fix_asi(n.span);
1134 return;
1135 }
1136
1137 n.visit_children_with(self);
1138 }
1139
1140 fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
1141 if n.decl.is_ts_declare() || n.decl.is_uninstantiated() {
1142 self.add_replacement(n.span);
1143 self.fix_asi(n.span);
1144 return;
1145 }
1146
1147 n.visit_children_with(self);
1148 }
1149
1150 fn visit_decl(&mut self, n: &Decl) {
1151 if n.is_ts_declare() || n.is_uninstantiated() {
1152 self.add_replacement(n.span());
1153 self.fix_asi(n.span());
1154 return;
1155 }
1156
1157 n.visit_children_with(self);
1158 }
1159
1160 fn visit_import_decl(&mut self, n: &ImportDecl) {
1161 if n.type_only {
1162 self.add_replacement(n.span);
1163 self.fix_asi(n.span);
1164 return;
1165 }
1166
1167 n.visit_children_with(self);
1168 }
1169
1170 fn visit_import_specifiers(&mut self, n: &[ImportSpecifier]) {
1171 for import in n {
1172 if let ImportSpecifier::Named(import) = import {
1173 if import.is_type_only {
1174 let mut span = import.span;
1175 let comma = self.get_next_token(import.span.hi);
1176 if comma.token == Token::Comma {
1177 span = span.with_hi(comma.span.hi);
1178 } else {
1179 debug_assert_eq!(comma.token, Token::RBrace);
1180 }
1181 self.add_replacement(span);
1182 }
1183 }
1184 }
1185 }
1186
1187 fn visit_named_export(&mut self, n: &NamedExport) {
1188 if n.type_only {
1189 self.add_replacement(n.span);
1190 self.fix_asi(n.span);
1191 return;
1192 }
1193
1194 for export in n.specifiers.iter() {
1195 if let ExportSpecifier::Named(e) = export {
1196 if e.is_type_only {
1197 let mut span = e.span;
1198 let comma = self.get_next_token(e.span.hi);
1199 if comma.token == Token::Comma {
1200 span = span.with_hi(comma.span.hi);
1201 } else {
1202 debug_assert_eq!(comma.token, Token::RBrace);
1203 }
1204 self.add_replacement(span);
1205 }
1206 }
1207 }
1208 }
1209
1210 fn visit_params(&mut self, n: &[Param]) {
1211 if let Some(p) = n.first().filter(|param| {
1212 matches!(
1213 ¶m.pat,
1214 Pat::Ident(id) if id.sym == "this"
1215 )
1216 }) {
1217 let mut span = p.span;
1218
1219 let comma = self.get_next_token(span.hi);
1220 if comma.token == Token::Comma {
1221 span = span.with_hi(comma.span.hi);
1222 } else {
1223 debug_assert_eq!(comma.token, Token::RParen);
1224 }
1225 self.add_replacement(span);
1226
1227 n[1..].visit_children_with(self);
1228
1229 return;
1230 }
1231
1232 n.visit_children_with(self);
1233 }
1234
1235 fn visit_ts_as_expr(&mut self, n: &TsAsExpr) {
1236 self.add_replacement(span(n.expr.span().hi, n.span.hi));
1237 let TokenAndSpan {
1238 token,
1239 span: as_span,
1240 ..
1241 } = self.get_next_token(n.expr.span_hi());
1242 debug_assert_eq!(
1243 token,
1244 &Token::Word(Word::Ident(IdentLike::Known(KnownIdent::As)))
1245 );
1246 self.fix_asi_in_expr(span(as_span.lo, n.span.hi));
1247
1248 n.expr.visit_children_with(self);
1249 }
1250
1251 fn visit_ts_const_assertion(&mut self, n: &TsConstAssertion) {
1252 self.add_replacement(span(n.expr.span().hi, n.span.hi));
1253
1254 n.expr.visit_children_with(self);
1255 }
1256
1257 fn visit_ts_export_assignment(&mut self, n: &TsExportAssignment) {
1258 HANDLER.with(|handler| {
1259 handler
1260 .struct_span_err(
1261 n.span,
1262 "TypeScript export assignment is not supported in strip-only mode",
1263 )
1264 .emit();
1265 });
1266 }
1267
1268 fn visit_ts_import_equals_decl(&mut self, n: &TsImportEqualsDecl) {
1269 if n.is_type_only {
1270 self.add_replacement(n.span);
1271 self.fix_asi(n.span);
1272 return;
1273 }
1274
1275 HANDLER.with(|handler| {
1276 handler
1277 .struct_span_err(
1278 n.span,
1279 "TypeScript import equals declaration is not supported in strip-only mode",
1280 )
1281 .emit();
1282 });
1283 }
1284
1285 fn visit_ts_index_signature(&mut self, n: &TsIndexSignature) {
1286 self.add_replacement(n.span);
1287 }
1288
1289 fn visit_ts_instantiation(&mut self, n: &TsInstantiation) {
1290 self.add_replacement(span(n.expr.span().hi, n.span.hi));
1291
1292 n.expr.visit_children_with(self);
1293 }
1294
1295 fn visit_ts_enum_decl(&mut self, e: &TsEnumDecl) {
1296 HANDLER.with(|handler| {
1297 handler
1298 .struct_span_err(
1299 e.span,
1300 "TypeScript enum is not supported in strip-only mode",
1301 )
1302 .emit();
1303 });
1304 }
1305
1306 fn visit_ts_module_decl(&mut self, n: &TsModuleDecl) {
1307 HANDLER.with(|handler| {
1308 handler
1309 .struct_span_err(
1310 n.span(),
1311 "TypeScript namespace declaration is not supported in strip-only mode",
1312 )
1313 .emit();
1314 });
1315 }
1316
1317 fn visit_ts_non_null_expr(&mut self, n: &TsNonNullExpr) {
1318 self.add_replacement(span(n.span.hi - BytePos(1), n.span.hi));
1319
1320 n.expr.visit_children_with(self);
1321 }
1322
1323 fn visit_ts_param_prop_param(&mut self, n: &TsParamPropParam) {
1324 HANDLER.with(|handler| {
1325 handler
1326 .struct_span_err(
1327 n.span(),
1328 "TypeScript parameter property is not supported in strip-only mode",
1329 )
1330 .emit();
1331 });
1332 }
1333
1334 fn visit_ts_satisfies_expr(&mut self, n: &TsSatisfiesExpr) {
1335 self.add_replacement(span(n.expr.span().hi, n.span.hi));
1336
1337 let TokenAndSpan {
1338 token,
1339 span: as_span,
1340 ..
1341 } = self.get_next_token(n.expr.span_hi());
1342 debug_assert_eq!(
1343 token,
1344 &Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Satisfies)))
1345 );
1346 self.fix_asi_in_expr(span(as_span.lo, n.span.hi));
1347
1348 n.expr.visit_children_with(self);
1349 }
1350
1351 fn visit_ts_type_alias_decl(&mut self, n: &TsTypeAliasDecl) {
1352 self.add_replacement(n.span);
1353 self.fix_asi(n.span);
1354 }
1355
1356 fn visit_ts_type_ann(&mut self, n: &TsTypeAnn) {
1357 self.add_replacement(n.span);
1358 }
1359
1360 fn visit_ts_type_assertion(&mut self, n: &TsTypeAssertion) {
1364 HANDLER.with(|handler| {
1365 handler
1366 .struct_span_err(
1367 n.span,
1368 "The angle-bracket syntax for type assertions, `<T>expr`, is not supported in \
1369 type strip mode. Instead, use the 'as' syntax: `expr as T`.",
1370 )
1371 .emit();
1372 });
1373
1374 n.expr.visit_children_with(self);
1375 }
1376
1377 fn visit_ts_type_param_decl(&mut self, n: &TsTypeParamDecl) {
1378 self.add_replacement(n.span);
1379 }
1380
1381 fn visit_ts_type_param_instantiation(&mut self, n: &TsTypeParamInstantiation) {
1382 self.add_replacement(span(n.span.lo, n.span.hi));
1383 }
1384
1385 fn visit_if_stmt(&mut self, n: &IfStmt) {
1386 n.visit_children_with(self);
1387
1388 if n.cons.is_ts_declare() {
1389 self.add_overwrite(n.cons.span_lo(), b';');
1390 }
1391
1392 if let Some(alt) = &n.alt {
1393 if alt.is_ts_declare() {
1394 self.add_overwrite(alt.span_lo(), b';');
1395 }
1396 }
1397 }
1398
1399 fn visit_for_stmt(&mut self, n: &ForStmt) {
1400 n.visit_children_with(self);
1401
1402 if n.body.is_ts_declare() {
1403 self.add_overwrite(n.body.span_lo(), b';');
1404 }
1405 }
1406
1407 fn visit_for_in_stmt(&mut self, n: &ForInStmt) {
1408 n.visit_children_with(self);
1409
1410 if n.body.is_ts_declare() {
1411 self.add_overwrite(n.body.span_lo(), b';');
1412 }
1413 }
1414
1415 fn visit_for_of_stmt(&mut self, n: &ForOfStmt) {
1416 n.visit_children_with(self);
1417
1418 if n.body.is_ts_declare() {
1419 self.add_overwrite(n.body.span_lo(), b';');
1420 }
1421 }
1422
1423 fn visit_while_stmt(&mut self, n: &WhileStmt) {
1424 n.visit_children_with(self);
1425
1426 if n.body.is_ts_declare() {
1427 self.add_overwrite(n.body.span_lo(), b';');
1428 }
1429 }
1430
1431 fn visit_do_while_stmt(&mut self, n: &DoWhileStmt) {
1432 n.visit_children_with(self);
1433
1434 if n.body.is_ts_declare() {
1435 self.add_overwrite(n.body.span_lo(), b';');
1436 }
1437 }
1438
1439 fn visit_getter_prop(&mut self, n: &GetterProp) {
1440 let l_parern_index = self.get_next_token_index(n.key.span_hi());
1441 let l_parern = &self.tokens[l_parern_index];
1442 debug_assert_eq!(l_parern.token, Token::LParen);
1443
1444 let r_parern_pos = n.type_ann.as_ref().map_or(n.body.span_lo(), |t| t.span.lo) - BytePos(1);
1445 let r_parern = self.get_prev_token(r_parern_pos);
1446 debug_assert_eq!(r_parern.token, Token::RParen);
1447
1448 let span = span(l_parern.span.lo + BytePos(1), r_parern.span.hi - BytePos(1));
1449 self.add_replacement(span);
1450
1451 n.visit_children_with(self);
1452 }
1453
1454 fn visit_setter_prop(&mut self, n: &SetterProp) {
1455 if let Some(this_param) = &n.this_param {
1456 self.add_replacement(this_param.span());
1457
1458 let comma = self.get_prev_token(n.param.span_lo() - BytePos(1));
1459 debug_assert_eq!(comma.token, Token::Comma);
1460
1461 self.add_replacement(comma.span);
1462 }
1463
1464 n.visit_children_with(self);
1465 }
1466}
1467
1468#[inline(always)]
1469fn is_new_line(c: char) -> bool {
1470 matches!(c, '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}')
1471}
1472trait IsTsDecl {
1473 fn is_ts_declare(&self) -> bool;
1474}
1475
1476impl IsTsDecl for Decl {
1477 fn is_ts_declare(&self) -> bool {
1478 match self {
1479 Self::TsInterface(..) | Self::TsTypeAlias(..) => true,
1480
1481 Self::TsModule(module) => module.declare || matches!(module.id, TsModuleName::Str(..)),
1482 Self::TsEnum(ref r#enum) => r#enum.declare,
1483
1484 Self::Var(ref var) => var.declare,
1485 Self::Fn(FnDecl { declare: true, .. })
1486 | Self::Class(ClassDecl { declare: true, .. }) => true,
1487
1488 Self::Fn(FnDecl { function, .. }) => function.body.is_none(),
1489
1490 _ => false,
1491 }
1492 }
1493}
1494
1495impl IsTsDecl for Stmt {
1496 fn is_ts_declare(&self) -> bool {
1497 self.as_decl().map_or(false, IsTsDecl::is_ts_declare)
1498 }
1499}
1500
1501impl IsTsDecl for DefaultDecl {
1502 fn is_ts_declare(&self) -> bool {
1503 match self {
1504 Self::Class(..) => false,
1505 Self::Fn(r#fn) => r#fn.function.body.is_none(),
1506 Self::TsInterfaceDecl(..) => true,
1507 }
1508 }
1509}
1510
1511trait IsUninstantiated {
1512 fn is_uninstantiated(&self) -> bool;
1513}
1514
1515impl IsUninstantiated for TsNamespaceBody {
1516 fn is_uninstantiated(&self) -> bool {
1517 match self {
1518 Self::TsModuleBlock(block) => {
1519 block.body.iter().all(IsUninstantiated::is_uninstantiated)
1520 }
1521 Self::TsNamespaceDecl(decl) => decl.body.is_uninstantiated(),
1522 }
1523 }
1524}
1525
1526impl IsUninstantiated for ModuleItem {
1527 fn is_uninstantiated(&self) -> bool {
1528 match self {
1529 Self::Stmt(stmt) => stmt.is_uninstantiated(),
1530 Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. })) => {
1531 decl.is_uninstantiated()
1532 }
1533 _ => false,
1534 }
1535 }
1536}
1537
1538impl IsUninstantiated for Stmt {
1539 fn is_uninstantiated(&self) -> bool {
1540 matches!(self, Self::Decl(decl) if decl.is_uninstantiated())
1541 }
1542}
1543
1544impl IsUninstantiated for TsModuleDecl {
1545 fn is_uninstantiated(&self) -> bool {
1546 matches!(&self.body, Some(body) if body.is_uninstantiated())
1547 }
1548}
1549
1550impl IsUninstantiated for Decl {
1551 fn is_uninstantiated(&self) -> bool {
1552 match self {
1553 Self::TsInterface(..) | Self::TsTypeAlias(..) => true,
1554 Self::TsModule(module) => module.is_uninstantiated(),
1555 _ => false,
1556 }
1557 }
1558}
1559
1560impl IsUninstantiated for DefaultDecl {
1561 fn is_uninstantiated(&self) -> bool {
1562 matches!(self, Self::TsInterfaceDecl(..))
1563 }
1564}
1565
1566trait U8Helper {
1567 fn is_utf8_char_boundary(&self) -> bool;
1568}
1569
1570impl U8Helper for u8 {
1571 #[inline]
1573 fn is_utf8_char_boundary(&self) -> bool {
1574 (*self as i8) >= -0x40
1576 }
1577}
1578
1579fn span(lo: BytePos, hi: BytePos) -> Span {
1580 Span::new(lo, hi)
1581}