swc_ts_fast_strip/
lib.rs

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