1use crate::tokens::Span;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SyncToken {
10 Semicolon,
12 End,
14 Declaration,
16 RightBrace,
18 RightParen,
20 Eof,
22}
23#[allow(dead_code)]
25pub struct DiagnosticRenderer {
26 pub source: String,
28 pub use_color: bool,
30 pub show_fixes: bool,
32 pub context_lines: usize,
34}
35#[allow(dead_code)]
36impl DiagnosticRenderer {
37 pub fn new(source: impl Into<String>) -> Self {
39 Self {
40 source: source.into(),
41 use_color: false,
42 show_fixes: true,
43 context_lines: 1,
44 }
45 }
46 pub fn with_color(mut self, v: bool) -> Self {
48 self.use_color = v;
49 self
50 }
51 pub fn with_show_fixes(mut self, v: bool) -> Self {
53 self.show_fixes = v;
54 self
55 }
56 pub fn with_context_lines(mut self, n: usize) -> Self {
58 self.context_lines = n;
59 self
60 }
61 pub fn render(&self, diag: &Diagnostic) -> String {
63 let mut out = String::new();
64 let severity_tag = match diag.severity {
65 Severity::Error => "error",
66 Severity::Warning => "warning",
67 Severity::Info => "info",
68 Severity::Hint => "hint",
69 };
70 if let Some(code) = &diag.code {
71 out.push_str(&format!("{}[{}]: {}\n", severity_tag, code, diag.message));
72 } else {
73 out.push_str(&format!("{}: {}\n", severity_tag, diag.message));
74 }
75 out.push_str(&format!(" --> {}:{}\n", diag.span.line, diag.span.column));
76 let ctx = self.extract_context(diag.span.line);
77 out.push_str(&ctx);
78 let col = if diag.span.column > 0 {
79 diag.span.column - 1
80 } else {
81 0
82 };
83 let len = (diag.span.end.saturating_sub(diag.span.start)).max(1);
84 out.push_str(&format!("{}^\n", " ".repeat(col)));
85 let _ = len;
86 for label in &diag.labels {
87 out.push_str(&format!(" note: {}\n", label.text));
88 }
89 if let Some(h) = &diag.help {
90 out.push_str(&format!(" help: {}\n", h));
91 }
92 if self.show_fixes {
93 for fix in &diag.fixes {
94 out.push_str(&format!(
95 " suggestion: {} → `{}`\n",
96 fix.message, fix.replacement
97 ));
98 }
99 }
100 out
101 }
102 pub fn render_all(&self, diags: &[Diagnostic]) -> String {
104 diags
105 .iter()
106 .map(|d| self.render(d))
107 .collect::<Vec<_>>()
108 .join("\n")
109 }
110 pub fn render_errors(&self, collector: &DiagnosticCollector) -> String {
112 let errors: Vec<&Diagnostic> = collector
113 .diagnostics()
114 .iter()
115 .filter(|d| d.is_error())
116 .collect();
117 errors
118 .iter()
119 .map(|d| self.render(d))
120 .collect::<Vec<_>>()
121 .join("\n")
122 }
123 fn extract_context(&self, line: usize) -> String {
124 if line == 0 {
125 return String::new();
126 }
127 let start_line = line.saturating_sub(self.context_lines);
128 let end_line = line + self.context_lines;
129 let lines: Vec<&str> = self.source.lines().collect();
130 let mut out = String::new();
131 for (idx, l) in lines.iter().enumerate() {
132 let lnum = idx + 1;
133 if lnum >= start_line && lnum <= end_line {
134 out.push_str(&format!("{:4} | {}\n", lnum, l));
135 }
136 }
137 out
138 }
139}
140#[allow(dead_code)]
142pub struct DiagnosticExporter;
143#[allow(dead_code)]
144impl DiagnosticExporter {
145 pub fn to_json(d: &Diagnostic) -> String {
147 let severity = match d.severity {
148 Severity::Error => "error",
149 Severity::Warning => "warning",
150 Severity::Info => "info",
151 Severity::Hint => "hint",
152 };
153 let code = d
154 .code
155 .map(|c| format!("\"{}\"", c))
156 .unwrap_or_else(|| "null".to_string());
157 format!(
158 r#"{{"severity":"{}","code":{},"message":"{}","line":{},"col":{}}}"#,
159 severity,
160 code,
161 d.message.replace('"', "\\\""),
162 d.span.line,
163 d.span.column
164 )
165 }
166 pub fn collector_to_json(c: &DiagnosticCollector) -> String {
168 let items: Vec<String> = c.diagnostics().iter().map(Self::to_json).collect();
169 format!("[{}]", items.join(","))
170 }
171 pub fn to_oneliner(d: &Diagnostic) -> String {
173 format!(
174 "{}:{}: {}: {}",
175 d.span.line,
176 d.span.column,
177 match d.severity {
178 Severity::Error => "error",
179 Severity::Warning => "warning",
180 Severity::Info => "info",
181 Severity::Hint => "hint",
182 },
183 d.message
184 )
185 }
186 pub fn collector_to_oneliners(c: &DiagnosticCollector) -> String {
188 c.diagnostics()
189 .iter()
190 .map(Self::to_oneliner)
191 .collect::<Vec<_>>()
192 .join("\n")
193 }
194 pub fn to_csv(d: &Diagnostic) -> String {
196 let severity = match d.severity {
197 Severity::Error => "error",
198 Severity::Warning => "warning",
199 Severity::Info => "info",
200 Severity::Hint => "hint",
201 };
202 format!(
203 "{},{},{},\"{}\"",
204 d.span.line,
205 d.span.column,
206 severity,
207 d.message.replace('"', "\"\"")
208 )
209 }
210 pub fn collector_to_csv(c: &DiagnosticCollector) -> String {
212 let mut out = "line,col,severity,message\n".to_string();
213 for d in c.diagnostics() {
214 out.push_str(&Self::to_csv(d));
215 out.push('\n');
216 }
217 out
218 }
219}
220#[allow(dead_code)]
222#[derive(Debug, Clone, Default)]
223pub struct DiagnosticStats {
224 pub errors: usize,
226 pub warnings: usize,
228 pub infos: usize,
230 pub hints: usize,
232 pub with_code: usize,
234 pub with_fix: usize,
236 pub with_help: usize,
238}
239#[allow(dead_code)]
240impl DiagnosticStats {
241 pub fn from_collector(c: &DiagnosticCollector) -> Self {
243 let mut s = Self::default();
244 for d in c.diagnostics() {
245 match d.severity {
246 Severity::Error => s.errors += 1,
247 Severity::Warning => s.warnings += 1,
248 Severity::Info => s.infos += 1,
249 Severity::Hint => s.hints += 1,
250 }
251 if d.code.is_some() {
252 s.with_code += 1;
253 }
254 if !d.fixes.is_empty() {
255 s.with_fix += 1;
256 }
257 if d.help.is_some() {
258 s.with_help += 1;
259 }
260 }
261 s
262 }
263 pub fn total(&self) -> usize {
265 self.errors + self.warnings + self.infos + self.hints
266 }
267 pub fn has_errors(&self) -> bool {
269 self.errors > 0
270 }
271 pub fn summary(&self) -> String {
273 format!(
274 "{} errors, {} warnings, {} infos, {} hints",
275 self.errors, self.warnings, self.infos, self.hints
276 )
277 }
278}
279#[allow(dead_code)]
281#[derive(Clone, Copy, Debug, PartialEq, Eq)]
282pub enum DiagnosticPolicy {
283 FailFast,
285 CollectAll,
287 WarningsAsErrors,
289 Permissive,
291}
292#[allow(dead_code)]
293impl DiagnosticPolicy {
294 pub fn should_fail(&self, c: &DiagnosticCollector) -> bool {
296 match self {
297 DiagnosticPolicy::FailFast => c.has_errors(),
298 DiagnosticPolicy::CollectAll => c.has_errors(),
299 DiagnosticPolicy::WarningsAsErrors => c.has_errors() || c.warning_count() > 0,
300 DiagnosticPolicy::Permissive => false,
301 }
302 }
303 pub fn name(&self) -> &'static str {
305 match self {
306 DiagnosticPolicy::FailFast => "fail-fast",
307 DiagnosticPolicy::CollectAll => "collect-all",
308 DiagnosticPolicy::WarningsAsErrors => "warnings-as-errors",
309 DiagnosticPolicy::Permissive => "permissive",
310 }
311 }
312}
313#[allow(dead_code)]
315#[allow(missing_docs)]
316#[derive(Debug, Clone)]
317pub struct DiagnosticEvent {
318 pub message: String,
320 pub id: u64,
322}
323impl DiagnosticEvent {
324 #[allow(dead_code)]
326 pub fn new(id: u64, message: &str) -> Self {
327 DiagnosticEvent {
328 id,
329 message: message.to_string(),
330 }
331 }
332}
333#[derive(Debug, Clone)]
335pub struct DiagnosticLabel {
336 pub text: String,
338 pub span: Span,
340}
341#[allow(dead_code)]
343pub struct SyncTokenInfo {
344 pub kind: SyncToken,
346 pub name: &'static str,
348 pub is_statement_end: bool,
350}
351#[allow(dead_code)]
352impl SyncTokenInfo {
353 pub fn all() -> &'static [SyncTokenInfo] {
355 &[
356 SyncTokenInfo {
357 kind: SyncToken::Semicolon,
358 name: "semicolon",
359 is_statement_end: true,
360 },
361 SyncTokenInfo {
362 kind: SyncToken::End,
363 name: "end",
364 is_statement_end: true,
365 },
366 SyncTokenInfo {
367 kind: SyncToken::Declaration,
368 name: "declaration keyword",
369 is_statement_end: true,
370 },
371 SyncTokenInfo {
372 kind: SyncToken::RightBrace,
373 name: "right brace",
374 is_statement_end: false,
375 },
376 SyncTokenInfo {
377 kind: SyncToken::RightParen,
378 name: "right paren",
379 is_statement_end: false,
380 },
381 SyncTokenInfo {
382 kind: SyncToken::Eof,
383 name: "end of file",
384 is_statement_end: true,
385 },
386 ]
387 }
388}
389#[allow(dead_code)]
391pub struct SpanUtils;
392#[allow(dead_code)]
393impl SpanUtils {
394 pub fn contains(outer: &Span, inner: &Span) -> bool {
396 outer.start <= inner.start && inner.end <= outer.end
397 }
398 pub fn overlaps(a: &Span, b: &Span) -> bool {
400 a.start < b.end && b.start < a.end
401 }
402 pub fn merge(a: &Span, b: &Span) -> Span {
404 let start = a.start.min(b.start);
405 let end = a.end.max(b.end);
406 let line = a.line.min(b.line);
407 let column = if a.line < b.line {
408 a.column
409 } else if b.line < a.line {
410 b.column
411 } else {
412 a.column.min(b.column)
413 };
414 Span::new(start, end, line, column)
415 }
416 pub fn byte_len(span: &Span) -> usize {
418 span.end.saturating_sub(span.start)
419 }
420 pub fn is_empty(span: &Span) -> bool {
422 Self::byte_len(span) == 0
423 }
424 pub fn expand(span: &Span, n: usize, max_end: usize) -> Span {
426 let start = span.start.saturating_sub(n);
427 let end = (span.end + n).min(max_end);
428 Span::new(start, end, span.line, span.column)
429 }
430 pub fn extract<'a>(span: &Span, source: &'a str) -> &'a str {
432 source.get(span.start..span.end).unwrap_or("")
433 }
434 pub fn from_byte_range(start: usize, end: usize, source: &str) -> Span {
436 let before = &source[..start.min(source.len())];
437 let line = before.chars().filter(|&c| c == '\n').count() + 1;
438 let col = before.rfind('\n').map(|p| start - p).unwrap_or(start + 1);
439 Span::new(start, end, line, col)
440 }
441}
442#[allow(dead_code)]
444#[allow(missing_docs)]
445pub struct SeverityFilter {
446 pub min_severity: u8,
448}
449impl SeverityFilter {
450 #[allow(dead_code)]
452 pub fn all() -> Self {
453 SeverityFilter { min_severity: 0 }
454 }
455 #[allow(dead_code)]
457 pub fn errors_only() -> Self {
458 SeverityFilter { min_severity: 2 }
459 }
460}
461#[allow(dead_code)]
463pub struct DiagnosticPrinter {
464 policy: DiagnosticPolicy,
465}
466#[allow(dead_code)]
467impl DiagnosticPrinter {
468 pub fn new(policy: DiagnosticPolicy) -> Self {
470 Self { policy }
471 }
472 pub fn print(&self, c: &DiagnosticCollector) -> String {
474 let mut out = String::new();
475 for d in c.diagnostics() {
476 out.push_str(&format!("{}\n", d));
477 }
478 out
479 }
480 pub fn should_fail(&self, c: &DiagnosticCollector) -> bool {
482 self.policy.should_fail(c)
483 }
484}
485#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
487pub enum Severity {
488 Error,
490 Warning,
492 Info,
494 Hint,
496}
497#[derive(Debug, Clone, Copy, PartialEq, Eq)]
499pub enum DiagnosticCode {
500 E0001,
502 E0002,
504 E0003,
506 E0004,
508 E0005,
510 E0100,
512 E0101,
514 E0102,
516 E0103,
518 E0104,
520 E0200,
522 E0201,
524 E0202,
526 E0900,
528 E0901,
530}
531#[allow(dead_code)]
533pub struct DiagnosticBuilder {
534 severity: Severity,
535 message: String,
536 span: Span,
537 labels: Vec<DiagnosticLabel>,
538 help: Option<String>,
539 code: Option<DiagnosticCode>,
540 fixes: Vec<CodeFix>,
541}
542#[allow(dead_code)]
543impl DiagnosticBuilder {
544 pub fn error(message: impl Into<String>, span: Span) -> Self {
546 Self {
547 severity: Severity::Error,
548 message: message.into(),
549 span,
550 labels: Vec::new(),
551 help: None,
552 code: None,
553 fixes: Vec::new(),
554 }
555 }
556 pub fn warning(message: impl Into<String>, span: Span) -> Self {
558 Self {
559 severity: Severity::Warning,
560 message: message.into(),
561 span,
562 labels: Vec::new(),
563 help: None,
564 code: None,
565 fixes: Vec::new(),
566 }
567 }
568 pub fn info(message: impl Into<String>, span: Span) -> Self {
570 Self {
571 severity: Severity::Info,
572 message: message.into(),
573 span,
574 labels: Vec::new(),
575 help: None,
576 code: None,
577 fixes: Vec::new(),
578 }
579 }
580 pub fn hint(message: impl Into<String>, span: Span) -> Self {
582 Self {
583 severity: Severity::Hint,
584 message: message.into(),
585 span,
586 labels: Vec::new(),
587 help: None,
588 code: None,
589 fixes: Vec::new(),
590 }
591 }
592 pub fn label(mut self, text: impl Into<String>, span: Span) -> Self {
594 self.labels.push(DiagnosticLabel {
595 text: text.into(),
596 span,
597 });
598 self
599 }
600 pub fn help(mut self, h: impl Into<String>) -> Self {
602 self.help = Some(h.into());
603 self
604 }
605 pub fn code(mut self, c: DiagnosticCode) -> Self {
607 self.code = Some(c);
608 self
609 }
610 pub fn fix(
612 mut self,
613 message: impl Into<String>,
614 span: Span,
615 replacement: impl Into<String>,
616 ) -> Self {
617 self.fixes.push(CodeFix {
618 message: message.into(),
619 span,
620 replacement: replacement.into(),
621 });
622 self
623 }
624 pub fn build(self) -> Diagnostic {
626 Diagnostic {
627 severity: self.severity,
628 message: self.message,
629 span: self.span,
630 labels: self.labels,
631 help: self.help,
632 code: self.code,
633 fixes: self.fixes,
634 }
635 }
636}
637#[derive(Debug, Clone)]
639pub struct Diagnostic {
640 pub severity: Severity,
642 pub message: String,
644 pub span: Span,
646 pub labels: Vec<DiagnosticLabel>,
648 pub help: Option<String>,
650 pub code: Option<DiagnosticCode>,
652 pub fixes: Vec<CodeFix>,
654}
655impl Diagnostic {
656 pub fn error(message: String, span: Span) -> Self {
658 Self {
659 severity: Severity::Error,
660 message,
661 span,
662 labels: Vec::new(),
663 help: None,
664 code: None,
665 fixes: Vec::new(),
666 }
667 }
668 pub fn warning(message: String, span: Span) -> Self {
670 Self {
671 severity: Severity::Warning,
672 message,
673 span,
674 labels: Vec::new(),
675 help: None,
676 code: None,
677 fixes: Vec::new(),
678 }
679 }
680 pub fn info(message: String, span: Span) -> Self {
682 Self {
683 severity: Severity::Info,
684 message,
685 span,
686 labels: Vec::new(),
687 help: None,
688 code: None,
689 fixes: Vec::new(),
690 }
691 }
692 #[allow(dead_code)]
694 pub fn note(message: String, span: Span) -> Self {
695 Self {
696 severity: Severity::Info,
697 message,
698 span,
699 labels: Vec::new(),
700 help: None,
701 code: None,
702 fixes: Vec::new(),
703 }
704 }
705 pub fn with_label(mut self, text: String, span: Span) -> Self {
707 self.labels.push(DiagnosticLabel { text, span });
708 self
709 }
710 pub fn with_help(mut self, help: String) -> Self {
712 self.help = Some(help);
713 self
714 }
715 #[allow(dead_code)]
717 pub fn with_code(mut self, code: DiagnosticCode) -> Self {
718 self.code = Some(code);
719 self
720 }
721 #[allow(dead_code)]
723 pub fn with_fix(mut self, fix: CodeFix) -> Self {
724 self.fixes.push(fix);
725 self
726 }
727 pub fn is_error(&self) -> bool {
729 self.severity == Severity::Error
730 }
731 pub fn is_warning(&self) -> bool {
733 self.severity == Severity::Warning
734 }
735 #[allow(dead_code)]
740 pub fn format_rich(&self, source: &str) -> String {
741 let severity_str = match self.severity {
742 Severity::Error => "error",
743 Severity::Warning => "warning",
744 Severity::Info => "info",
745 Severity::Hint => "hint",
746 };
747 let mut output = String::new();
748 if let Some(code) = &self.code {
749 output.push_str(&format!("{}[{}]: {}\n", severity_str, code, self.message));
750 } else {
751 output.push_str(&format!("{}: {}\n", severity_str, self.message));
752 }
753 output.push_str(&format!(" --> {}:{}\n", self.span.line, self.span.column));
754 let highlight = Self::format_line_highlight(source, &self.span);
755 if !highlight.is_empty() {
756 output.push_str(&highlight);
757 }
758 for label in &self.labels {
759 output.push_str(&format!(" = {}\n", label.text));
760 }
761 if let Some(help) = &self.help {
762 output.push_str(&format!(" = help: {}\n", help));
763 }
764 for fix in &self.fixes {
765 output.push_str(&format!(
766 " = fix: {} -> `{}`\n",
767 fix.message, fix.replacement
768 ));
769 }
770 output
771 }
772 #[allow(dead_code)]
777 pub fn format_line_highlight(source: &str, span: &Span) -> String {
778 let lines: Vec<&str> = source.lines().collect();
779 if span.line == 0 || span.line > lines.len() {
780 return String::new();
781 }
782 let line_content = lines[span.line - 1];
783 let line_num = span.line;
784 let line_num_width = format!("{}", line_num).len();
785 let mut output = String::new();
786 output.push_str(&format!("{} |\n", " ".repeat(line_num_width)));
787 output.push_str(&format!("{} | {}\n", line_num, line_content));
788 let col = if span.column > 0 { span.column - 1 } else { 0 };
789 let underline_len = if span.end > span.start {
790 span.end - span.start
791 } else {
792 1
793 };
794 let underline_len = underline_len.min(line_content.len().saturating_sub(col));
795 let underline_len = if underline_len == 0 { 1 } else { underline_len };
796 output.push_str(&format!(
797 "{} | {}{}",
798 " ".repeat(line_num_width),
799 " ".repeat(col),
800 "^".repeat(underline_len)
801 ));
802 output.push('\n');
803 output
804 }
805}
806#[allow(dead_code)]
808pub struct DiagnosticAggregator {
809 collectors: Vec<DiagnosticCollector>,
810 label: String,
811}
812#[allow(dead_code)]
813impl DiagnosticAggregator {
814 pub fn new(label: impl Into<String>) -> Self {
816 Self {
817 collectors: Vec::new(),
818 label: label.into(),
819 }
820 }
821 pub fn add_collector(&mut self, c: DiagnosticCollector) {
823 self.collectors.push(c);
824 }
825 pub fn total_errors(&self) -> usize {
827 self.collectors.iter().map(|c| c.error_count()).sum()
828 }
829 pub fn total_warnings(&self) -> usize {
831 self.collectors.iter().map(|c| c.warning_count()).sum()
832 }
833 pub fn total_count(&self) -> usize {
835 self.collectors.iter().map(|c| c.diagnostics().len()).sum()
836 }
837 pub fn flat_sorted(&self) -> Vec<Diagnostic> {
839 let mut all: Vec<Diagnostic> = self
840 .collectors
841 .iter()
842 .flat_map(|c| c.diagnostics().iter().cloned())
843 .collect();
844 all.sort_by(|a, b| {
845 a.span
846 .line
847 .cmp(&b.span.line)
848 .then(a.span.column.cmp(&b.span.column))
849 });
850 all
851 }
852 pub fn has_errors(&self) -> bool {
854 self.collectors.iter().any(|c| c.has_errors())
855 }
856 pub fn summary(&self) -> String {
858 format!(
859 "DiagnosticAggregator [{}]: {} errors, {} warnings across {} collectors",
860 self.label,
861 self.total_errors(),
862 self.total_warnings(),
863 self.collectors.len()
864 )
865 }
866 pub fn collector_count(&self) -> usize {
868 self.collectors.len()
869 }
870}
871#[allow(dead_code)]
873pub struct DiagnosticSuppressor {
874 suppressed_codes: Vec<DiagnosticCode>,
875 suppress_warnings: bool,
876 suppress_hints: bool,
877}
878#[allow(dead_code)]
879impl DiagnosticSuppressor {
880 pub fn new() -> Self {
882 Self {
883 suppressed_codes: Vec::new(),
884 suppress_warnings: false,
885 suppress_hints: false,
886 }
887 }
888 pub fn suppress_code(mut self, code: DiagnosticCode) -> Self {
890 self.suppressed_codes.push(code);
891 self
892 }
893 pub fn suppress_all_warnings(mut self) -> Self {
895 self.suppress_warnings = true;
896 self
897 }
898 pub fn suppress_all_hints(mut self) -> Self {
900 self.suppress_hints = true;
901 self
902 }
903 pub fn should_suppress(&self, d: &Diagnostic) -> bool {
905 if self.suppress_warnings && d.severity == Severity::Warning {
906 return true;
907 }
908 if self.suppress_hints && d.severity == Severity::Hint {
909 return true;
910 }
911 if let Some(code) = d.code {
912 return self.suppressed_codes.contains(&code);
913 }
914 false
915 }
916 pub fn filter(&self, diags: Vec<Diagnostic>) -> Vec<Diagnostic> {
918 diags
919 .into_iter()
920 .filter(|d| !self.should_suppress(d))
921 .collect()
922 }
923 pub fn filter_collector(&self, c: &DiagnosticCollector) -> DiagnosticCollector {
925 let mut new_c = DiagnosticCollector::new();
926 for d in c.diagnostics() {
927 if !self.should_suppress(d) {
928 new_c.add(d.clone());
929 }
930 }
931 new_c
932 }
933}
934#[allow(dead_code)]
936pub struct DiagnosticFilter<'a> {
937 collector: &'a DiagnosticCollector,
938}
939#[allow(dead_code)]
940impl<'a> DiagnosticFilter<'a> {
941 pub fn new(collector: &'a DiagnosticCollector) -> Self {
943 Self { collector }
944 }
945 pub fn with_code(&self, code: DiagnosticCode) -> Vec<&Diagnostic> {
947 self.collector
948 .diagnostics()
949 .iter()
950 .filter(|d| d.code == Some(code))
951 .collect()
952 }
953 pub fn message_contains(&self, needle: &str) -> Vec<&Diagnostic> {
955 self.collector
956 .diagnostics()
957 .iter()
958 .filter(|d| d.message.contains(needle))
959 .collect()
960 }
961 pub fn in_line_range(&self, from_line: usize, to_line: usize) -> Vec<&Diagnostic> {
963 self.collector
964 .diagnostics()
965 .iter()
966 .filter(|d| d.span.line >= from_line && d.span.line <= to_line)
967 .collect()
968 }
969 pub fn with_fixes(&self) -> Vec<&Diagnostic> {
971 self.collector
972 .diagnostics()
973 .iter()
974 .filter(|d| !d.fixes.is_empty())
975 .collect()
976 }
977 pub fn with_help(&self) -> Vec<&Diagnostic> {
979 self.collector
980 .diagnostics()
981 .iter()
982 .filter(|d| d.help.is_some())
983 .collect()
984 }
985 pub fn errors(&self) -> Vec<&Diagnostic> {
987 self.collector.filter_severity(Severity::Error)
988 }
989 pub fn warnings(&self) -> Vec<&Diagnostic> {
991 self.collector.filter_severity(Severity::Warning)
992 }
993 pub fn infos(&self) -> Vec<&Diagnostic> {
995 self.collector.filter_severity(Severity::Info)
996 }
997 pub fn hints(&self) -> Vec<&Diagnostic> {
999 self.collector.filter_severity(Severity::Hint)
1000 }
1001}
1002pub struct DiagnosticCollector {
1004 diagnostics: Vec<Diagnostic>,
1006 error_count: usize,
1008 warning_count: usize,
1010}
1011impl DiagnosticCollector {
1012 pub fn new() -> Self {
1014 Self {
1015 diagnostics: Vec::new(),
1016 error_count: 0,
1017 warning_count: 0,
1018 }
1019 }
1020 pub fn add(&mut self, diagnostic: Diagnostic) {
1022 if diagnostic.is_error() {
1023 self.error_count += 1;
1024 } else if diagnostic.is_warning() {
1025 self.warning_count += 1;
1026 }
1027 self.diagnostics.push(diagnostic);
1028 }
1029 pub fn diagnostics(&self) -> &[Diagnostic] {
1031 &self.diagnostics
1032 }
1033 pub fn error_count(&self) -> usize {
1035 self.error_count
1036 }
1037 pub fn warning_count(&self) -> usize {
1039 self.warning_count
1040 }
1041 pub fn has_errors(&self) -> bool {
1043 self.error_count > 0
1044 }
1045 pub fn clear(&mut self) {
1047 self.diagnostics.clear();
1048 self.error_count = 0;
1049 self.warning_count = 0;
1050 }
1051 #[allow(dead_code)]
1053 pub fn diagnostics_at(&self, line: usize) -> Vec<&Diagnostic> {
1054 self.diagnostics
1055 .iter()
1056 .filter(|d| d.span.line == line)
1057 .collect()
1058 }
1059 #[allow(dead_code)]
1061 pub fn info_count(&self) -> usize {
1062 self.diagnostics
1063 .iter()
1064 .filter(|d| d.severity == Severity::Info)
1065 .count()
1066 }
1067 #[allow(dead_code)]
1069 pub fn sort_by_severity(&mut self) {
1070 self.diagnostics.sort_by_key(|d| d.severity);
1071 }
1072 #[allow(dead_code)]
1074 pub fn sort_by_position(&mut self) {
1075 self.diagnostics.sort_by(|a, b| {
1076 a.span
1077 .line
1078 .cmp(&b.span.line)
1079 .then(a.span.column.cmp(&b.span.column))
1080 });
1081 }
1082 #[allow(dead_code)]
1084 pub fn filter_severity(&self, severity: Severity) -> Vec<&Diagnostic> {
1085 self.diagnostics
1086 .iter()
1087 .filter(|d| d.severity == severity)
1088 .collect()
1089 }
1090 #[allow(dead_code)]
1092 pub fn merge(&mut self, other: &DiagnosticCollector) {
1093 for diag in &other.diagnostics {
1094 self.add(diag.clone());
1095 }
1096 }
1097 #[allow(dead_code)]
1101 pub fn summary(&self) -> String {
1102 let info = self.info_count();
1103 let mut parts = Vec::new();
1104 if self.error_count > 0 {
1105 parts.push(format!(
1106 "{} error{}",
1107 self.error_count,
1108 if self.error_count == 1 { "" } else { "s" }
1109 ));
1110 }
1111 if self.warning_count > 0 {
1112 parts.push(format!(
1113 "{} warning{}",
1114 self.warning_count,
1115 if self.warning_count == 1 { "" } else { "s" }
1116 ));
1117 }
1118 if info > 0 {
1119 parts.push(format!("{} info{}", info, if info == 1 { "" } else { "s" }));
1120 }
1121 if parts.is_empty() {
1122 "no diagnostics".to_string()
1123 } else {
1124 parts.join(", ")
1125 }
1126 }
1127}
1128#[derive(Debug, Clone)]
1130pub struct CodeFix {
1131 pub message: String,
1133 pub span: Span,
1135 pub replacement: String,
1137}
1138#[allow(dead_code)]
1140#[derive(Debug, Default, Clone)]
1141pub struct DiagnosticGroup {
1142 pub name: String,
1144 pub diagnostics: Vec<Diagnostic>,
1146}
1147#[allow(dead_code)]
1148impl DiagnosticGroup {
1149 pub fn new(name: impl Into<String>) -> Self {
1151 Self {
1152 name: name.into(),
1153 diagnostics: Vec::new(),
1154 }
1155 }
1156 pub fn add(&mut self, d: Diagnostic) {
1158 self.diagnostics.push(d);
1159 }
1160 pub fn error_count(&self) -> usize {
1162 self.diagnostics.iter().filter(|d| d.is_error()).count()
1163 }
1164 pub fn warning_count(&self) -> usize {
1166 self.diagnostics.iter().filter(|d| d.is_warning()).count()
1167 }
1168 pub fn has_errors(&self) -> bool {
1170 self.diagnostics.iter().any(|d| d.is_error())
1171 }
1172 pub fn len(&self) -> usize {
1174 self.diagnostics.len()
1175 }
1176 pub fn is_empty(&self) -> bool {
1178 self.diagnostics.is_empty()
1179 }
1180 pub fn summary(&self) -> String {
1182 format!(
1183 "[{}]: {} errors, {} warnings",
1184 self.name,
1185 self.error_count(),
1186 self.warning_count()
1187 )
1188 }
1189 pub fn sort_by_position(&mut self) {
1191 self.diagnostics.sort_by(|a, b| {
1192 a.span
1193 .line
1194 .cmp(&b.span.line)
1195 .then(a.span.column.cmp(&b.span.column))
1196 });
1197 }
1198}