Skip to main content

swc_ts_fast_strip_binding/
lib.rs

1use std::{
2    iter::once,
3    mem::take,
4    sync::{Arc, Mutex},
5};
6
7use anyhow::Error;
8use error_reporter::SwcReportHandler;
9use miette::{GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles};
10use serde::Serialize;
11use swc_common::{
12    errors::{DiagnosticBuilder, DiagnosticId, Emitter, Handler, HANDLER},
13    sync::Lrc,
14    SourceMap, Span, GLOBALS,
15};
16use swc_error_reporters::{convert_span, to_pretty_source_code};
17use swc_ts_fast_strip::{Options, TransformOutput};
18
19mod error_reporter;
20
21pub fn transform(input: String, options: Options) -> Result<TransformOutput, Vec<JsonDiagnostic>> {
22    GLOBALS.set(&Default::default(), || operate(input, options))
23}
24
25fn operate(input: String, options: Options) -> Result<TransformOutput, Vec<JsonDiagnostic>> {
26    let cm = Lrc::new(SourceMap::default());
27
28    try_with_json_handler(cm.clone(), |handler| {
29        swc_ts_fast_strip::operate(&cm, handler, input, options).map_err(anyhow::Error::new)
30    })
31}
32
33#[derive(Clone)]
34struct JsonErrorWriter {
35    errors: Arc<Mutex<Vec<JsonDiagnostic>>>,
36    reporter: SwcReportHandler,
37    cm: Lrc<SourceMap>,
38}
39
40fn try_with_json_handler<F, Ret>(cm: Lrc<SourceMap>, op: F) -> Result<Ret, Vec<JsonDiagnostic>>
41where
42    F: FnOnce(&Handler) -> Result<Ret, Error>,
43{
44    let wr = JsonErrorWriter {
45        errors: Default::default(),
46        reporter: SwcReportHandler::default().with_theme(GraphicalTheme {
47            characters: ThemeCharacters {
48                hbar: ' ',
49                vbar: ' ',
50                xbar: ' ',
51                vbar_break: ' ',
52                ltop: ' ',
53                rtop: ' ',
54                mtop: ' ',
55                lbot: ' ',
56                rbot: ' ',
57                mbot: ' ',
58                error: "".into(),
59                warning: "".into(),
60                advice: "".into(),
61                ..ThemeCharacters::ascii()
62            },
63            styles: ThemeStyles::none(),
64        }),
65        cm,
66    };
67    let emitter: Box<dyn Emitter> = Box::new(wr.clone());
68
69    let handler = Handler::with_emitter(true, false, emitter);
70
71    let ret = HANDLER.set(&handler, || op(&handler));
72
73    if handler.has_errors() {
74        let mut lock = wr.errors.lock().unwrap();
75        let error = take(&mut *lock);
76
77        Err(error)
78    } else {
79        Ok(ret.expect("it should not fail without emitting errors to handler"))
80    }
81}
82
83impl Emitter for JsonErrorWriter {
84    fn emit(&mut self, db: &mut DiagnosticBuilder) {
85        let d = &**db;
86
87        let snippet = d.span.primary_span().and_then(|span| {
88            let mut snippet = String::new();
89            match self.reporter.render_report(
90                &mut snippet,
91                &Snippet {
92                    source_code: &to_pretty_source_code(&self.cm, true),
93                    span,
94                },
95            ) {
96                Ok(()) => Some(snippet),
97                Err(_) => None,
98            }
99        });
100
101        let children = d
102            .children
103            .iter()
104            .map(|d| todo!("json subdiagnostic: {d:?}"))
105            .collect::<Vec<_>>();
106
107        let error_code = match &d.code {
108            Some(DiagnosticId::Error(s)) => Some(&**s),
109            Some(DiagnosticId::Lint(s)) => Some(&**s),
110            None => None,
111        };
112
113        let start = d
114            .span
115            .primary_span()
116            .and_then(|span| self.cm.try_lookup_char_pos(span.lo()).ok());
117
118        let end = d
119            .span
120            .primary_span()
121            .and_then(|span| self.cm.try_lookup_char_pos(span.hi()).ok());
122
123        let filename = start.as_ref().map(|loc| loc.file.name.to_string());
124
125        let error = JsonDiagnostic {
126            code: error_code.map(|s| s.to_string()),
127            message: d.message[0].0.to_string(),
128            snippet,
129            filename,
130            start_line: start.as_ref().map(|loc| loc.line),
131            start_column: start.as_ref().map(|loc| loc.col_display),
132            end_line: end.as_ref().map(|loc| loc.line),
133            end_column: end.as_ref().map(|loc| loc.col_display),
134            children,
135        };
136
137        self.errors.lock().unwrap().push(error);
138    }
139
140    fn take_diagnostics(&mut self) -> Vec<String> {
141        Default::default()
142    }
143}
144
145#[derive(Debug, Serialize)]
146#[serde(rename_all = "camelCase")]
147pub struct JsonDiagnostic {
148    /// Error code
149    #[serde(skip_serializing_if = "Option::is_none")]
150    code: Option<String>,
151    message: String,
152
153    #[serde(skip_serializing_if = "Option::is_none")]
154    snippet: Option<String>,
155
156    #[serde(skip_serializing_if = "Option::is_none")]
157    filename: Option<String>,
158
159    #[serde(skip_serializing_if = "Option::is_none")]
160    start_line: Option<usize>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    start_column: Option<usize>,
163
164    #[serde(skip_serializing_if = "Option::is_none")]
165    end_line: Option<usize>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    end_column: Option<usize>,
168
169    #[serde(skip_serializing_if = "Vec::is_empty")]
170    children: Vec<JsonSubdiagnostic>,
171}
172
173#[derive(Debug, Serialize)]
174#[serde(rename_all = "camelCase")]
175struct JsonSubdiagnostic {
176    message: String,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    snippet: Option<String>,
179    filename: String,
180    line: usize,
181}
182
183struct Snippet<'a> {
184    source_code: &'a dyn miette::SourceCode,
185    span: Span,
186}
187
188impl miette::Diagnostic for Snippet<'_> {
189    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
190        if self.span.lo().is_dummy() || self.span.hi().is_dummy() {
191            return None;
192        }
193
194        Some(self.source_code)
195    }
196
197    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
198        Some(Box::new(once(LabeledSpan::new_with_span(
199            None,
200            convert_span(self.span),
201        ))))
202    }
203}
204
205impl std::error::Error for Snippet<'_> {}
206
207impl std::fmt::Display for Snippet<'_> {
208    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        Ok(())
210    }
211}
212
213impl std::fmt::Debug for Snippet<'_> {
214    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        Ok(())
216    }
217}