swc_ts_fast_strip_binding/
lib.rs1use 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 #[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}