rspack_error/
diagnostic.rs

1use std::{borrow::Cow, fmt, ops::Deref, sync::Arc};
2
3use cow_utils::CowUtils;
4use miette::{GraphicalTheme, IntoDiagnostic, MietteDiagnostic};
5use rspack_cacheable::{cacheable, with::Unsupported};
6use rspack_collections::Identifier;
7use rspack_location::DependencyLocation;
8use rspack_paths::{Utf8Path, Utf8PathBuf};
9
10use crate::{Error, graphical::GraphicalReportHandler};
11
12#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
13pub enum RspackSeverity {
14  #[default]
15  Error,
16  Warn,
17}
18
19pub type Severity = RspackSeverity;
20
21impl From<RspackSeverity> for miette::Severity {
22  fn from(value: RspackSeverity) -> Self {
23    match value {
24      RspackSeverity::Error => miette::Severity::Error,
25      RspackSeverity::Warn => miette::Severity::Warning,
26    }
27  }
28}
29
30impl From<miette::Severity> for RspackSeverity {
31  fn from(value: miette::Severity) -> Self {
32    match value {
33      miette::Severity::Error => RspackSeverity::Error,
34      miette::Severity::Warning => RspackSeverity::Warn,
35      miette::Severity::Advice => unimplemented!("Not supported miette severity"),
36    }
37  }
38}
39
40impl From<&str> for RspackSeverity {
41  fn from(value: &str) -> Self {
42    let s = value.cow_to_ascii_lowercase();
43    match s.as_ref() {
44      "warning" => RspackSeverity::Warn,
45      _ => RspackSeverity::Error,
46    }
47  }
48}
49
50impl fmt::Display for RspackSeverity {
51  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52    write!(
53      f,
54      "{}",
55      match self {
56        RspackSeverity::Error => "error",
57        RspackSeverity::Warn => "warning",
58      }
59    )
60  }
61}
62
63#[cacheable]
64#[derive(Debug, Clone, Copy)]
65pub struct SourcePosition {
66  pub line: usize,
67  pub column: usize,
68}
69
70#[cacheable(with=Unsupported)]
71#[derive(Debug, Clone)]
72pub struct Diagnostic {
73  inner: Arc<miette::Error>,
74
75  // The following fields are only used to restore Diagnostic for Rspack.
76  // If the current Diagnostic originates from Rust, these fields will be None.
77  details: Option<String>,
78  module_identifier: Option<Identifier>,
79  loc: Option<DependencyLocation>,
80  file: Option<Utf8PathBuf>,
81  hide_stack: Option<bool>,
82  chunk: Option<u32>,
83  stack: Option<String>,
84}
85
86impl From<Box<dyn miette::Diagnostic + Send + Sync>> for Diagnostic {
87  fn from(value: Box<dyn miette::Diagnostic + Send + Sync>) -> Self {
88    Diagnostic::from(miette::Error::new_boxed(value))
89  }
90}
91
92impl From<Arc<miette::Error>> for Diagnostic {
93  fn from(value: Arc<miette::Error>) -> Self {
94    Self {
95      inner: value,
96      details: None,
97      module_identifier: None,
98      loc: None,
99      file: None,
100      hide_stack: None,
101      chunk: None,
102      stack: None,
103    }
104  }
105}
106
107impl From<miette::Error> for Diagnostic {
108  fn from(value: miette::Error) -> Self {
109    Self {
110      inner: Arc::new(value),
111      details: None,
112      module_identifier: None,
113      loc: None,
114      file: None,
115      hide_stack: None,
116      chunk: None,
117      stack: None,
118    }
119  }
120}
121
122impl Deref for Diagnostic {
123  type Target = miette::Error;
124
125  fn deref(&self) -> &Self::Target {
126    &self.inner
127  }
128}
129
130impl Diagnostic {
131  pub fn warn(title: String, message: String) -> Self {
132    Self {
133      inner: Error::from(
134        MietteDiagnostic::new(message)
135          .with_code(title)
136          .with_severity(miette::Severity::Warning),
137      )
138      .into(),
139      details: None,
140      module_identifier: None,
141      loc: None,
142      file: None,
143      hide_stack: None,
144      chunk: None,
145      stack: None,
146    }
147  }
148
149  pub fn error(title: String, message: String) -> Self {
150    Self {
151      inner: Error::from(
152        MietteDiagnostic::new(message)
153          .with_code(title)
154          .with_severity(miette::Severity::Error),
155      )
156      .into(),
157      details: None,
158      module_identifier: None,
159      loc: None,
160      file: None,
161      hide_stack: None,
162      chunk: None,
163      stack: None,
164    }
165  }
166}
167
168impl Diagnostic {
169  pub fn render_report(&self, colored: bool) -> crate::Result<String> {
170    let mut buf = String::new();
171    let theme = if colored {
172      GraphicalTheme::unicode()
173    } else {
174      GraphicalTheme::unicode_nocolor()
175    };
176    let h = GraphicalReportHandler::new()
177      .with_theme(theme)
178      .with_context_lines(2)
179      .with_width(usize::MAX);
180    h.render_report(&mut buf, self.as_ref()).into_diagnostic()?;
181    Ok(buf)
182  }
183
184  pub fn as_miette_error(&self) -> &Arc<miette::Error> {
185    &self.inner
186  }
187
188  pub fn message(&self) -> String {
189    self.inner.to_string()
190  }
191
192  pub fn severity(&self) -> Severity {
193    self.inner.severity().unwrap_or_default().into()
194  }
195
196  pub fn module_identifier(&self) -> Option<Identifier> {
197    self.module_identifier
198  }
199
200  pub fn with_module_identifier(mut self, module_identifier: Option<Identifier>) -> Self {
201    self.module_identifier = module_identifier;
202    self
203  }
204
205  pub fn loc(&self) -> Option<&DependencyLocation> {
206    self.loc.as_ref()
207  }
208
209  pub fn with_loc(mut self, loc: Option<DependencyLocation>) -> Self {
210    self.loc = loc;
211    self
212  }
213
214  pub fn file(&self) -> Option<&Utf8Path> {
215    self.file.as_deref()
216  }
217
218  pub fn with_file(mut self, file: Option<Utf8PathBuf>) -> Self {
219    self.file = file;
220    self
221  }
222
223  pub fn hide_stack(&self) -> Option<bool> {
224    self.hide_stack
225  }
226
227  pub fn with_hide_stack(mut self, hide_stack: Option<bool>) -> Self {
228    self.hide_stack = hide_stack;
229    self
230  }
231
232  pub fn chunk(&self) -> Option<u32> {
233    self.chunk
234  }
235
236  pub fn with_chunk(mut self, chunk: Option<u32>) -> Self {
237    self.chunk = chunk;
238    self
239  }
240
241  pub fn stack(&self) -> Option<String> {
242    self.stack.clone()
243  }
244
245  pub fn with_stack(mut self, stack: Option<String>) -> Self {
246    self.stack = stack;
247    self
248  }
249
250  pub fn details(&self) -> Option<String> {
251    self.details.clone()
252  }
253
254  pub fn with_details(mut self, details: Option<String>) -> Self {
255    self.details = details;
256    self
257  }
258}
259
260pub trait Diagnosable {
261  fn add_diagnostic(&mut self, _diagnostic: Diagnostic);
262
263  fn add_diagnostics(&mut self, _diagnostics: Vec<Diagnostic>);
264
265  fn diagnostics(&self) -> Cow<'_, [Diagnostic]>;
266
267  fn first_error(&self) -> Option<Cow<'_, Diagnostic>> {
268    match self.diagnostics() {
269      Cow::Borrowed(diagnostics) => diagnostics
270        .iter()
271        .find(|d| d.severity() == Severity::Error)
272        .map(Cow::Borrowed),
273      Cow::Owned(diagnostics) => diagnostics
274        .into_iter()
275        .find(|d| d.severity() == Severity::Error)
276        .map(Cow::Owned),
277    }
278  }
279}
280
281#[macro_export]
282macro_rules! impl_empty_diagnosable_trait {
283  ($ty:ty) => {
284    impl $crate::Diagnosable for $ty {
285      fn add_diagnostic(&mut self, _diagnostic: $crate::Diagnostic) {
286        unimplemented!(
287          "`<{ty} as Diagnosable>::add_diagnostic` is not implemented",
288          ty = stringify!($ty)
289        )
290      }
291      fn add_diagnostics(&mut self, _diagnostics: Vec<$crate::Diagnostic>) {
292        unimplemented!(
293          "`<{ty} as Diagnosable>::add_diagnostics` is not implemented",
294          ty = stringify!($ty)
295        )
296      }
297      fn diagnostics(&self) -> std::borrow::Cow<'_, [$crate::Diagnostic]> {
298        std::borrow::Cow::Owned(vec![])
299      }
300    }
301  };
302}
303
304pub fn errors_to_diagnostics(errs: Vec<Error>) -> Vec<Diagnostic> {
305  errs.into_iter().map(Diagnostic::from).collect()
306}