1use std::{path::PathBuf, vec};
2
3use sway_types::{SourceEngine, Span};
4
5#[derive(Debug, Default)]
7pub struct Diagnostic {
8 pub reason: Option<Reason>, pub issue: Issue,
10 pub hints: Vec<Hint>,
11 pub help: Vec<String>,
12}
13
14impl Diagnostic {
15 pub fn is_old_style(&self) -> bool {
19 self.reason.is_none() && self.hints.is_empty() && self.help.is_empty()
20 }
21
22 pub fn level(&self) -> Level {
23 match self.issue.label_type {
24 LabelType::Error => Level::Error,
25 LabelType::Warning => Level::Warning,
26 LabelType::Info => Level::Info,
27 _ => unreachable!("The diagnostic level can be only Error, Warning, or Info, and this is enforced via Diagnostics API.")
28 }
29 }
30
31 pub fn reason(&self) -> Option<&Reason> {
32 self.reason.as_ref()
33 }
34
35 pub fn issue(&self) -> &Issue {
36 &self.issue
37 }
38
39 pub fn labels(&self) -> Vec<&Label> {
41 let mut labels = Vec::<&Label>::new();
42
43 if self.issue.is_in_source() {
44 labels.push(&self.issue);
45 }
46
47 for hint in self.hints.iter().filter(|hint| hint.is_in_source()) {
48 labels.push(hint);
49 }
50
51 labels
52 }
53
54 pub fn labels_in_source(&self, source_path: &SourcePath) -> Vec<&Label> {
56 self.labels()
57 .iter()
58 .filter(|&label| label.source_path().unwrap() == source_path)
60 .copied()
61 .collect()
62 }
63
64 pub fn labels_in_issue_source(&self) -> Vec<&Label> {
66 if !self.issue.is_in_source() {
67 return vec![];
68 }
69
70 self.labels_in_source(self.issue.source_path().unwrap())
72 }
73
74 pub fn help(&self) -> impl Iterator<Item = &String> + '_ {
75 self.help.iter().filter(|help| !help.is_empty())
76 }
77
78 pub fn help_none() -> String {
81 String::new()
82 }
83
84 pub fn help_empty_line() -> String {
87 String::from(" ")
88 }
89
90 pub fn related_sources(&self, include_issue_source: bool) -> Vec<&SourcePath> {
94 let mut source_files = vec![];
95
96 let issue_is_in_source = self.issue.is_in_source();
97
98 if issue_is_in_source && include_issue_source {
102 source_files.push(self.issue.source_path().unwrap());
103 }
104
105 for hint in self.labels() {
106 let file = hint.source_path().unwrap();
107
108 if !include_issue_source
109 && issue_is_in_source
110 && file == self.issue.source_path().unwrap()
111 {
112 continue;
113 }
114
115 if !source_files.contains(&file) {
116 source_files.push(file)
117 }
118 }
119
120 source_files
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
125pub enum Level {
126 Info,
127 Warning,
128 #[default]
129 Error,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
133pub enum LabelType {
134 #[default]
135 Info,
136 Help,
137 Warning,
138 Error,
139}
140
141#[derive(Debug)]
155pub struct Label {
156 label_type: LabelType,
157 span: Span,
158 text: String,
159 source_path: Option<SourcePath>,
160}
161
162impl Label {
163 pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Label {
164 Self::new(source_engine, LabelType::Info, span, text)
165 }
166
167 pub fn help(source_engine: &SourceEngine, span: Span, text: String) -> Label {
168 Self::new(source_engine, LabelType::Help, span, text)
169 }
170
171 pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Label {
172 Self::new(source_engine, LabelType::Warning, span, text)
173 }
174
175 pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Label {
176 Self::new(source_engine, LabelType::Error, span, text)
177 }
178
179 fn new(source_engine: &SourceEngine, label_type: LabelType, span: Span, text: String) -> Label {
180 let source_path = Self::get_source_path(source_engine, &span);
181 Label {
182 label_type,
183 span,
184 text,
185 source_path,
186 }
187 }
188
189 pub fn is_in_source(&self) -> bool {
191 self.source_path.is_some() && (self.span.start() < self.span.end())
192 }
193
194 pub fn label_type(&self) -> LabelType {
195 self.label_type
196 }
197
198 pub fn span(&self) -> &Span {
199 &self.span
200 }
201
202 pub fn text(&self) -> &str {
203 self.text.as_ref()
204 }
205
206 pub fn source_path(&self) -> Option<&SourcePath> {
207 self.source_path.as_ref()
208 }
209
210 fn get_source_path(source_engine: &SourceEngine, span: &Span) -> Option<SourcePath> {
211 let path_buf = span
212 .source_id()
213 .cloned()
214 .map(|id| source_engine.get_path(&id));
215 let path_string = path_buf.as_ref().map(|p| p.to_string_lossy().to_string());
216
217 match (path_buf, path_string) {
218 (Some(path_buf), Some(path_string)) => Some(SourcePath {
219 path_buf,
220 path_string,
221 }),
222 _ => None,
223 }
224 }
225}
226
227impl Default for Label {
228 fn default() -> Self {
229 Self {
230 label_type: LabelType::Info,
231 span: Span::dummy(),
232 text: "".to_string(),
233 source_path: None,
234 }
235 }
236}
237
238#[derive(Debug)]
239pub struct Issue {
240 label: Label,
241}
242
243impl Issue {
244 pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Self {
245 Self {
246 label: Label::warning(source_engine, span, text),
247 }
248 }
249
250 pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Self {
251 Self {
252 label: Label::error(source_engine, span, text),
253 }
254 }
255
256 pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Self {
257 Self {
258 label: Label::info(source_engine, span, text),
259 }
260 }
261}
262
263impl Default for Issue {
264 fn default() -> Self {
265 Self {
266 label: Label {
267 label_type: LabelType::Error,
268 ..Default::default()
269 },
270 }
271 }
272}
273
274impl std::ops::Deref for Issue {
275 type Target = Label;
276 fn deref(&self) -> &Self::Target {
277 &self.label
278 }
279}
280
281#[derive(Debug, Default)]
282pub struct Hint {
283 label: Label,
284}
285
286impl Hint {
287 pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Self {
288 Self {
289 label: Label::info(source_engine, span, text),
290 }
291 }
292
293 pub fn underscored_info(source_engine: &SourceEngine, span: Span) -> Self {
294 Self::info(source_engine, span, "".to_string())
295 }
296
297 pub fn multi_info(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
298 hints
299 .into_iter()
300 .map(|hint| Self::info(source_engine, span.clone(), hint))
301 .collect()
302 }
303
304 pub fn help(source_engine: &SourceEngine, span: Span, text: String) -> Self {
305 Self {
306 label: Label::help(source_engine, span, text),
307 }
308 }
309
310 pub fn multi_help(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
311 hints
312 .into_iter()
313 .map(|hint| Self::help(source_engine, span.clone(), hint))
314 .collect()
315 }
316
317 pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Self {
318 Self {
319 label: Label::warning(source_engine, span, text),
320 }
321 }
322
323 pub fn multi_warning(
324 source_engine: &SourceEngine,
325 span: &Span,
326 hints: Vec<String>,
327 ) -> Vec<Self> {
328 hints
329 .into_iter()
330 .map(|hint| Self::warning(source_engine, span.clone(), hint))
331 .collect()
332 }
333
334 pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Self {
335 Self {
336 label: Label::error(source_engine, span, text),
337 }
338 }
339
340 pub fn multi_error(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
341 hints
342 .into_iter()
343 .map(|hint| Self::error(source_engine, span.clone(), hint))
344 .collect()
345 }
346
347 pub fn none() -> Self {
350 Self {
351 label: Label::default(),
352 }
353 }
354}
355
356impl std::ops::Deref for Hint {
357 type Target = Label;
358 fn deref(&self) -> &Self::Target {
359 &self.label
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Default)]
364pub struct SourcePath {
365 path_buf: PathBuf,
366 path_string: String,
367}
368
369impl SourcePath {
370 pub fn as_path_buf(&self) -> &PathBuf {
371 &self.path_buf
372 }
373
374 pub fn as_str(&self) -> &str {
375 self.path_string.as_ref()
376 }
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
384pub enum DiagnosticArea {
385 #[default]
386 LexicalAnalysis,
387 Parsing,
388 ParseTreeConversion,
389 TypeChecking,
390 SemanticAnalysis,
391 Warnings,
392 Migrations,
393}
394
395impl DiagnosticArea {
396 pub fn prefix(&self) -> &'static str {
397 match self {
398 Self::LexicalAnalysis => "E0",
399 Self::Parsing => "E1",
400 Self::ParseTreeConversion => "E2",
401 Self::TypeChecking => "E3",
402 Self::SemanticAnalysis => "E4",
403 Self::Warnings => "W0",
404 Self::Migrations => "M0",
405 }
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Default)]
410pub struct Code {
411 area: DiagnosticArea,
412 number: u16,
413 text: String,
414}
415
416impl Code {
417 pub fn lexical_analysis(number: u16) -> Code {
418 Self::new(DiagnosticArea::LexicalAnalysis, number)
419 }
420
421 pub fn parsing(number: u16) -> Code {
422 Self::new(DiagnosticArea::Parsing, number)
423 }
424
425 pub fn parse_tree_conversion(number: u16) -> Code {
426 Self::new(DiagnosticArea::ParseTreeConversion, number)
427 }
428
429 pub fn type_checking(number: u16) -> Code {
430 Self::new(DiagnosticArea::TypeChecking, number)
431 }
432
433 pub fn semantic_analysis(number: u16) -> Self {
434 Self::new(DiagnosticArea::SemanticAnalysis, number)
435 }
436
437 pub fn warnings(number: u16) -> Code {
438 Self::new(DiagnosticArea::Warnings, number)
439 }
440
441 pub fn migrations(number: u16) -> Code {
442 Self::new(DiagnosticArea::Migrations, number)
443 }
444
445 fn new(area: DiagnosticArea, number: u16) -> Self {
446 debug_assert!(
447 0 < number && number < 999,
448 "The diagnostic code number must be greater then zero and smaller then 999."
449 );
450 Self {
451 area,
452 number,
453 text: format!("{}{:03}", area.prefix(), number),
454 }
455 }
456
457 pub fn as_str(&self) -> &str {
458 self.text.as_ref()
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Default)]
463pub struct Reason {
464 code: Code,
465 description: String,
466}
467
468impl Reason {
469 pub fn new(code: Code, description: String) -> Self {
470 Self { code, description }
471 }
472
473 pub fn code(&self) -> &str {
474 self.code.as_str()
475 }
476
477 pub fn description(&self) -> &str {
478 self.description.as_ref()
479 }
480}
481
482pub trait ToDiagnostic {
483 fn to_diagnostic(&self, source_engine: &SourceEngine) -> Diagnostic;
484}