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 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}