rspack_error/
error.rs

1use std::{fmt::Display, sync::Arc};
2
3use miette::{Diagnostic, LabeledSpan, MietteDiagnostic, Severity, SourceCode, SourceSpan};
4use swc_core::common::SourceFile;
5use thiserror::Error;
6
7use crate::RspackSeverity;
8
9#[derive(Debug, Error)]
10#[error(transparent)]
11pub struct InternalError(#[from] Box<dyn Diagnostic + Send + Sync + 'static>);
12
13impl InternalError {
14  pub fn new(message: String, severity: RspackSeverity) -> Self {
15    Self(Box::new(
16      MietteDiagnostic::new(message).with_severity(severity.into()),
17    ))
18  }
19}
20
21/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
22/// Errors. This is intended to be paired with [`IntoDiagnostic`].
23#[derive(Debug, Error)]
24#[error(transparent)]
25pub struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
26impl Diagnostic for DiagnosticError {}
27
28impl From<Box<dyn std::error::Error + Send + Sync + 'static>> for DiagnosticError {
29  fn from(value: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
30    Self(value)
31  }
32}
33
34/// Handle [anyhow::Error]
35/// Please try NOT to use this as much as possible.
36#[derive(Debug, Error, Diagnostic)]
37#[error(transparent)]
38pub struct AnyhowError(#[from] anyhow::Error);
39
40/// ## Warning
41/// For a [TraceableError], the path is required.
42/// Because if the source code is missing when you construct a [TraceableError], we could read it from file system later
43/// when convert it into [crate::Diagnostic], but the reverse will not working.
44#[derive(Debug, Clone, Error)]
45#[error("{title}: {message}")]
46pub struct TraceableError {
47  title: String,
48  kind: DiagnosticKind,
49  message: String,
50  severity: Severity,
51  #[allow(clippy::rc_buffer)]
52  src: Option<Arc<String>>,
53  label: SourceSpan,
54  help: Option<String>,
55  url: Option<String>,
56  hide_stack: Option<bool>,
57}
58
59impl Diagnostic for TraceableError {
60  fn severity(&self) -> Option<Severity> {
61    Some(self.severity)
62  }
63
64  fn help(&self) -> Option<Box<dyn Display + '_>> {
65    self
66      .help
67      .as_ref()
68      .map(Box::new)
69      .map(|c| c as Box<dyn Display>)
70  }
71
72  fn url(&self) -> Option<Box<dyn Display + '_>> {
73    self
74      .url
75      .as_ref()
76      .map(Box::new)
77      .map(|c| c as Box<dyn Display>)
78  }
79
80  fn source_code(&self) -> Option<&dyn SourceCode> {
81    self.src.as_ref().map(|s| &**s as &dyn SourceCode)
82  }
83
84  fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
85    use miette::macro_helpers::{OptionalWrapper, ToOption};
86    std::option::Option::Some(Box::new(
87      vec![
88        OptionalWrapper::<SourceSpan>::new()
89          .to_option(&self.label)
90          .map(|label| miette::LabeledSpan::new_with_span(None, *label)),
91      ]
92      .into_iter()
93      .filter(Option::is_some)
94      .flatten(),
95    ))
96  }
97}
98
99impl TraceableError {
100  pub fn with_severity(mut self, severity: impl Into<Severity>) -> Self {
101    self.severity = severity.into();
102    self
103  }
104
105  pub fn with_kind(mut self, kind: DiagnosticKind) -> Self {
106    self.kind = kind;
107    self
108  }
109
110  pub fn with_help(mut self, help: Option<impl Into<String>>) -> Self {
111    self.help = help.map(|h| h.into());
112    self
113  }
114
115  pub fn with_url(mut self, url: Option<impl Into<String>>) -> Self {
116    self.url = url.map(|u| u.into());
117    self
118  }
119
120  pub fn with_hide_stack(mut self, hide_stack: Option<bool>) -> Self {
121    self.hide_stack = hide_stack;
122    self
123  }
124
125  pub fn from_source_file(
126    source_file: &SourceFile,
127    start: usize,
128    end: usize,
129    title: String,
130    message: String,
131  ) -> Self {
132    Self::from_arc_string(
133      Some(source_file.src.clone().into_string().into()),
134      start,
135      end,
136      title,
137      message,
138    )
139  }
140
141  pub fn from_file(
142    file_src: String,
143    start: usize,
144    end: usize,
145    title: String,
146    message: String,
147  ) -> Self {
148    Self::from_arc_string(Some(Arc::new(file_src)), start, end, title, message)
149  }
150  // lazy set source_file if we can't know the source content in advance
151  pub fn from_lazy_file(start: usize, end: usize, title: String, message: String) -> Self {
152    Self::from_arc_string(None, start, end, title, message)
153  }
154
155  pub fn from_arc_string(
156    src: Option<Arc<String>>,
157    start: usize,
158    end: usize,
159    title: String,
160    message: String,
161  ) -> Self {
162    Self {
163      title,
164      kind: Default::default(),
165      message,
166      severity: Default::default(),
167      src,
168      label: SourceSpan::new(start.into(), end.saturating_sub(start)),
169      help: None,
170      url: None,
171      hide_stack: None,
172    }
173  }
174
175  pub fn hide_stack(&self) -> Option<bool> {
176    self.hide_stack
177  }
178}
179
180/// Multiple errors to represent different kinds of errors.
181/// NEVER implement this with [miette::Diagnostic],
182/// because it makes code hard to maintain.
183#[derive(Debug, Default)]
184pub struct BatchErrors(pub Vec<miette::Error>);
185
186impl BatchErrors {
187  pub fn into_inner(self) -> Vec<miette::Error> {
188    self.0
189  }
190}
191
192impl From<BatchErrors> for Vec<crate::Diagnostic> {
193  fn from(value: BatchErrors) -> Self {
194    value.0.into_iter().map(crate::Diagnostic::from).collect()
195  }
196}
197
198impl From<miette::Error> for BatchErrors {
199  fn from(value: miette::Error) -> Self {
200    Self(vec![value])
201  }
202}
203
204impl From<Vec<miette::Error>> for BatchErrors {
205  fn from(value: Vec<miette::Error>) -> Self {
206    Self(value)
207  }
208}
209
210#[macro_export]
211macro_rules! impl_diagnostic_transparent {
212  (code = $value:expr, $ty:ty) => {
213    impl miette::Diagnostic for $ty {
214      fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
215        Some(Box::new($value))
216      }
217
218      fn severity(&self) -> Option<miette::Severity> {
219        self.0.severity()
220      }
221
222      fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
223        self.0.help()
224      }
225
226      fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
227        self.0.url()
228      }
229
230      fn source_code(&self) -> Option<&dyn miette::SourceCode> {
231        self.0.source_code()
232      }
233
234      fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
235        self.0.labels()
236      }
237
238      fn related<'a>(
239        &'a self,
240      ) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
241        self.0.related()
242      }
243
244      fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
245        self.0.diagnostic_source()
246      }
247    }
248  };
249  ($ty:ty) => {
250    impl miette::Diagnostic for $ty {
251      fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
252        self.0.code()
253      }
254
255      fn severity(&self) -> Option<miette::Severity> {
256        self.0.severity()
257      }
258
259      fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
260        self.0.help()
261      }
262
263      fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
264        self.0.url()
265      }
266
267      fn source_code(&self) -> Option<&dyn miette::SourceCode> {
268        self.0.source_code()
269      }
270
271      fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
272        self.0.labels()
273      }
274
275      fn related<'a>(
276        &'a self,
277      ) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
278        self.0.related()
279      }
280
281      fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
282        self.0.diagnostic_source()
283      }
284    }
285  };
286}
287
288impl_diagnostic_transparent!(InternalError);
289
290#[macro_export]
291macro_rules! impl_error_transparent {
292  ($ty:ty) => {
293    impl std::error::Error for ModuleBuildError {
294      fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> {
295        std::error::Error::source(<Error as AsRef<dyn std::error::Error>>::as_ref(&self.0))
296      }
297    }
298
299    #[allow(unused_qualifications)]
300    impl ::core::fmt::Display for ModuleBuildError {
301      #[allow(clippy::used_underscore_binding)]
302      fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
303        ::core::fmt::Display::fmt(&self.0, __formatter)
304      }
305    }
306  };
307}
308
309#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
310pub enum DiagnosticKind {
311  JavaScript,
312  Typescript,
313  Jsx,
314  Tsx,
315  Scss,
316  Css,
317  #[default]
318  Internal,
319  Io,
320  Json,
321  Html,
322}
323
324/// About the manually implementation,
325/// display string should be snake, for consistency.
326impl std::fmt::Display for DiagnosticKind {
327  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328    match self {
329      DiagnosticKind::JavaScript => write!(f, "javascript"),
330      DiagnosticKind::Typescript => write!(f, "typescript"),
331      DiagnosticKind::Jsx => write!(f, "jsx"),
332      DiagnosticKind::Tsx => write!(f, "tsx"),
333      DiagnosticKind::Scss => write!(f, "scss"),
334      DiagnosticKind::Css => write!(f, "css"),
335      DiagnosticKind::Internal => write!(f, "internal"),
336      DiagnosticKind::Io => write!(f, "io"),
337      DiagnosticKind::Json => write!(f, "json"),
338      DiagnosticKind::Html => write!(f, "html"),
339    }
340  }
341}
342
343fn _assert() {
344  fn _assert_send_sync<T: Send + Sync>() {}
345  _assert_send_sync::<InternalError>();
346  _assert_send_sync::<DiagnosticError>();
347}
348
349/// (message, stack, backtrace, hide_stack)
350#[derive(Debug, Error, Diagnostic)]
351#[diagnostic()]
352#[error("{reason}\n{backtrace}")]
353pub struct NodeError {
354  pub reason: String,
355  pub stack: Option<String>,
356  pub backtrace: String,
357  pub hide_stack: Option<bool>,
358}