use crate::tokens::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncToken {
Semicolon,
End,
Declaration,
RightBrace,
RightParen,
Eof,
}
#[allow(dead_code)]
pub struct DiagnosticRenderer {
pub source: String,
pub use_color: bool,
pub show_fixes: bool,
pub context_lines: usize,
}
#[allow(dead_code)]
impl DiagnosticRenderer {
pub fn new(source: impl Into<String>) -> Self {
Self {
source: source.into(),
use_color: false,
show_fixes: true,
context_lines: 1,
}
}
pub fn with_color(mut self, v: bool) -> Self {
self.use_color = v;
self
}
pub fn with_show_fixes(mut self, v: bool) -> Self {
self.show_fixes = v;
self
}
pub fn with_context_lines(mut self, n: usize) -> Self {
self.context_lines = n;
self
}
pub fn render(&self, diag: &Diagnostic) -> String {
let mut out = String::new();
let severity_tag = match diag.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
};
if let Some(code) = &diag.code {
out.push_str(&format!("{}[{}]: {}\n", severity_tag, code, diag.message));
} else {
out.push_str(&format!("{}: {}\n", severity_tag, diag.message));
}
out.push_str(&format!(" --> {}:{}\n", diag.span.line, diag.span.column));
let ctx = self.extract_context(diag.span.line);
out.push_str(&ctx);
let col = if diag.span.column > 0 {
diag.span.column - 1
} else {
0
};
let len = (diag.span.end.saturating_sub(diag.span.start)).max(1);
out.push_str(&format!("{}^\n", " ".repeat(col)));
let _ = len;
for label in &diag.labels {
out.push_str(&format!(" note: {}\n", label.text));
}
if let Some(h) = &diag.help {
out.push_str(&format!(" help: {}\n", h));
}
if self.show_fixes {
for fix in &diag.fixes {
out.push_str(&format!(
" suggestion: {} → `{}`\n",
fix.message, fix.replacement
));
}
}
out
}
pub fn render_all(&self, diags: &[Diagnostic]) -> String {
diags
.iter()
.map(|d| self.render(d))
.collect::<Vec<_>>()
.join("\n")
}
pub fn render_errors(&self, collector: &DiagnosticCollector) -> String {
let errors: Vec<&Diagnostic> = collector
.diagnostics()
.iter()
.filter(|d| d.is_error())
.collect();
errors
.iter()
.map(|d| self.render(d))
.collect::<Vec<_>>()
.join("\n")
}
fn extract_context(&self, line: usize) -> String {
if line == 0 {
return String::new();
}
let start_line = line.saturating_sub(self.context_lines);
let end_line = line + self.context_lines;
let lines: Vec<&str> = self.source.lines().collect();
let mut out = String::new();
for (idx, l) in lines.iter().enumerate() {
let lnum = idx + 1;
if lnum >= start_line && lnum <= end_line {
out.push_str(&format!("{:4} | {}\n", lnum, l));
}
}
out
}
}
#[allow(dead_code)]
pub struct DiagnosticExporter;
#[allow(dead_code)]
impl DiagnosticExporter {
pub fn to_json(d: &Diagnostic) -> String {
let severity = match d.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
};
let code = d
.code
.map(|c| format!("\"{}\"", c))
.unwrap_or_else(|| "null".to_string());
format!(
r#"{{"severity":"{}","code":{},"message":"{}","line":{},"col":{}}}"#,
severity,
code,
d.message.replace('"', "\\\""),
d.span.line,
d.span.column
)
}
pub fn collector_to_json(c: &DiagnosticCollector) -> String {
let items: Vec<String> = c.diagnostics().iter().map(Self::to_json).collect();
format!("[{}]", items.join(","))
}
pub fn to_oneliner(d: &Diagnostic) -> String {
format!(
"{}:{}: {}: {}",
d.span.line,
d.span.column,
match d.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
},
d.message
)
}
pub fn collector_to_oneliners(c: &DiagnosticCollector) -> String {
c.diagnostics()
.iter()
.map(Self::to_oneliner)
.collect::<Vec<_>>()
.join("\n")
}
pub fn to_csv(d: &Diagnostic) -> String {
let severity = match d.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
};
format!(
"{},{},{},\"{}\"",
d.span.line,
d.span.column,
severity,
d.message.replace('"', "\"\"")
)
}
pub fn collector_to_csv(c: &DiagnosticCollector) -> String {
let mut out = "line,col,severity,message\n".to_string();
for d in c.diagnostics() {
out.push_str(&Self::to_csv(d));
out.push('\n');
}
out
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct DiagnosticStats {
pub errors: usize,
pub warnings: usize,
pub infos: usize,
pub hints: usize,
pub with_code: usize,
pub with_fix: usize,
pub with_help: usize,
}
#[allow(dead_code)]
impl DiagnosticStats {
pub fn from_collector(c: &DiagnosticCollector) -> Self {
let mut s = Self::default();
for d in c.diagnostics() {
match d.severity {
Severity::Error => s.errors += 1,
Severity::Warning => s.warnings += 1,
Severity::Info => s.infos += 1,
Severity::Hint => s.hints += 1,
}
if d.code.is_some() {
s.with_code += 1;
}
if !d.fixes.is_empty() {
s.with_fix += 1;
}
if d.help.is_some() {
s.with_help += 1;
}
}
s
}
pub fn total(&self) -> usize {
self.errors + self.warnings + self.infos + self.hints
}
pub fn has_errors(&self) -> bool {
self.errors > 0
}
pub fn summary(&self) -> String {
format!(
"{} errors, {} warnings, {} infos, {} hints",
self.errors, self.warnings, self.infos, self.hints
)
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DiagnosticPolicy {
FailFast,
CollectAll,
WarningsAsErrors,
Permissive,
}
#[allow(dead_code)]
impl DiagnosticPolicy {
pub fn should_fail(&self, c: &DiagnosticCollector) -> bool {
match self {
DiagnosticPolicy::FailFast => c.has_errors(),
DiagnosticPolicy::CollectAll => c.has_errors(),
DiagnosticPolicy::WarningsAsErrors => c.has_errors() || c.warning_count() > 0,
DiagnosticPolicy::Permissive => false,
}
}
pub fn name(&self) -> &'static str {
match self {
DiagnosticPolicy::FailFast => "fail-fast",
DiagnosticPolicy::CollectAll => "collect-all",
DiagnosticPolicy::WarningsAsErrors => "warnings-as-errors",
DiagnosticPolicy::Permissive => "permissive",
}
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct DiagnosticEvent {
pub message: String,
pub id: u64,
}
impl DiagnosticEvent {
#[allow(dead_code)]
pub fn new(id: u64, message: &str) -> Self {
DiagnosticEvent {
id,
message: message.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticLabel {
pub text: String,
pub span: Span,
}
#[allow(dead_code)]
pub struct SyncTokenInfo {
pub kind: SyncToken,
pub name: &'static str,
pub is_statement_end: bool,
}
#[allow(dead_code)]
impl SyncTokenInfo {
pub fn all() -> &'static [SyncTokenInfo] {
&[
SyncTokenInfo {
kind: SyncToken::Semicolon,
name: "semicolon",
is_statement_end: true,
},
SyncTokenInfo {
kind: SyncToken::End,
name: "end",
is_statement_end: true,
},
SyncTokenInfo {
kind: SyncToken::Declaration,
name: "declaration keyword",
is_statement_end: true,
},
SyncTokenInfo {
kind: SyncToken::RightBrace,
name: "right brace",
is_statement_end: false,
},
SyncTokenInfo {
kind: SyncToken::RightParen,
name: "right paren",
is_statement_end: false,
},
SyncTokenInfo {
kind: SyncToken::Eof,
name: "end of file",
is_statement_end: true,
},
]
}
}
#[allow(dead_code)]
pub struct SpanUtils;
#[allow(dead_code)]
impl SpanUtils {
pub fn contains(outer: &Span, inner: &Span) -> bool {
outer.start <= inner.start && inner.end <= outer.end
}
pub fn overlaps(a: &Span, b: &Span) -> bool {
a.start < b.end && b.start < a.end
}
pub fn merge(a: &Span, b: &Span) -> Span {
let start = a.start.min(b.start);
let end = a.end.max(b.end);
let line = a.line.min(b.line);
let column = if a.line < b.line {
a.column
} else if b.line < a.line {
b.column
} else {
a.column.min(b.column)
};
Span::new(start, end, line, column)
}
pub fn byte_len(span: &Span) -> usize {
span.end.saturating_sub(span.start)
}
pub fn is_empty(span: &Span) -> bool {
Self::byte_len(span) == 0
}
pub fn expand(span: &Span, n: usize, max_end: usize) -> Span {
let start = span.start.saturating_sub(n);
let end = (span.end + n).min(max_end);
Span::new(start, end, span.line, span.column)
}
pub fn extract<'a>(span: &Span, source: &'a str) -> &'a str {
source.get(span.start..span.end).unwrap_or("")
}
pub fn from_byte_range(start: usize, end: usize, source: &str) -> Span {
let before = &source[..start.min(source.len())];
let line = before.chars().filter(|&c| c == '\n').count() + 1;
let col = before.rfind('\n').map(|p| start - p).unwrap_or(start + 1);
Span::new(start, end, line, col)
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct SeverityFilter {
pub min_severity: u8,
}
impl SeverityFilter {
#[allow(dead_code)]
pub fn all() -> Self {
SeverityFilter { min_severity: 0 }
}
#[allow(dead_code)]
pub fn errors_only() -> Self {
SeverityFilter { min_severity: 2 }
}
}
#[allow(dead_code)]
pub struct DiagnosticPrinter {
policy: DiagnosticPolicy,
}
#[allow(dead_code)]
impl DiagnosticPrinter {
pub fn new(policy: DiagnosticPolicy) -> Self {
Self { policy }
}
pub fn print(&self, c: &DiagnosticCollector) -> String {
let mut out = String::new();
for d in c.diagnostics() {
out.push_str(&format!("{}\n", d));
}
out
}
pub fn should_fail(&self, c: &DiagnosticCollector) -> bool {
self.policy.should_fail(c)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Error,
Warning,
Info,
Hint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticCode {
E0001,
E0002,
E0003,
E0004,
E0005,
E0100,
E0101,
E0102,
E0103,
E0104,
E0200,
E0201,
E0202,
E0900,
E0901,
}
#[allow(dead_code)]
pub struct DiagnosticBuilder {
severity: Severity,
message: String,
span: Span,
labels: Vec<DiagnosticLabel>,
help: Option<String>,
code: Option<DiagnosticCode>,
fixes: Vec<CodeFix>,
}
#[allow(dead_code)]
impl DiagnosticBuilder {
pub fn error(message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Error,
message: message.into(),
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn warning(message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Warning,
message: message.into(),
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn info(message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Info,
message: message.into(),
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn hint(message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Hint,
message: message.into(),
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn label(mut self, text: impl Into<String>, span: Span) -> Self {
self.labels.push(DiagnosticLabel {
text: text.into(),
span,
});
self
}
pub fn help(mut self, h: impl Into<String>) -> Self {
self.help = Some(h.into());
self
}
pub fn code(mut self, c: DiagnosticCode) -> Self {
self.code = Some(c);
self
}
pub fn fix(
mut self,
message: impl Into<String>,
span: Span,
replacement: impl Into<String>,
) -> Self {
self.fixes.push(CodeFix {
message: message.into(),
span,
replacement: replacement.into(),
});
self
}
pub fn build(self) -> Diagnostic {
Diagnostic {
severity: self.severity,
message: self.message,
span: self.span,
labels: self.labels,
help: self.help,
code: self.code,
fixes: self.fixes,
}
}
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub severity: Severity,
pub message: String,
pub span: Span,
pub labels: Vec<DiagnosticLabel>,
pub help: Option<String>,
pub code: Option<DiagnosticCode>,
pub fixes: Vec<CodeFix>,
}
impl Diagnostic {
pub fn error(message: String, span: Span) -> Self {
Self {
severity: Severity::Error,
message,
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn warning(message: String, span: Span) -> Self {
Self {
severity: Severity::Warning,
message,
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn info(message: String, span: Span) -> Self {
Self {
severity: Severity::Info,
message,
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
#[allow(dead_code)]
pub fn note(message: String, span: Span) -> Self {
Self {
severity: Severity::Info,
message,
span,
labels: Vec::new(),
help: None,
code: None,
fixes: Vec::new(),
}
}
pub fn with_label(mut self, text: String, span: Span) -> Self {
self.labels.push(DiagnosticLabel { text, span });
self
}
pub fn with_help(mut self, help: String) -> Self {
self.help = Some(help);
self
}
#[allow(dead_code)]
pub fn with_code(mut self, code: DiagnosticCode) -> Self {
self.code = Some(code);
self
}
#[allow(dead_code)]
pub fn with_fix(mut self, fix: CodeFix) -> Self {
self.fixes.push(fix);
self
}
pub fn is_error(&self) -> bool {
self.severity == Severity::Error
}
pub fn is_warning(&self) -> bool {
self.severity == Severity::Warning
}
#[allow(dead_code)]
pub fn format_rich(&self, source: &str) -> String {
let severity_str = match self.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
};
let mut output = String::new();
if let Some(code) = &self.code {
output.push_str(&format!("{}[{}]: {}\n", severity_str, code, self.message));
} else {
output.push_str(&format!("{}: {}\n", severity_str, self.message));
}
output.push_str(&format!(" --> {}:{}\n", self.span.line, self.span.column));
let highlight = Self::format_line_highlight(source, &self.span);
if !highlight.is_empty() {
output.push_str(&highlight);
}
for label in &self.labels {
output.push_str(&format!(" = {}\n", label.text));
}
if let Some(help) = &self.help {
output.push_str(&format!(" = help: {}\n", help));
}
for fix in &self.fixes {
output.push_str(&format!(
" = fix: {} -> `{}`\n",
fix.message, fix.replacement
));
}
output
}
#[allow(dead_code)]
pub fn format_line_highlight(source: &str, span: &Span) -> String {
let lines: Vec<&str> = source.lines().collect();
if span.line == 0 || span.line > lines.len() {
return String::new();
}
let line_content = lines[span.line - 1];
let line_num = span.line;
let line_num_width = format!("{}", line_num).len();
let mut output = String::new();
output.push_str(&format!("{} |\n", " ".repeat(line_num_width)));
output.push_str(&format!("{} | {}\n", line_num, line_content));
let col = if span.column > 0 { span.column - 1 } else { 0 };
let underline_len = if span.end > span.start {
span.end - span.start
} else {
1
};
let underline_len = underline_len.min(line_content.len().saturating_sub(col));
let underline_len = if underline_len == 0 { 1 } else { underline_len };
output.push_str(&format!(
"{} | {}{}",
" ".repeat(line_num_width),
" ".repeat(col),
"^".repeat(underline_len)
));
output.push('\n');
output
}
}
#[allow(dead_code)]
pub struct DiagnosticAggregator {
collectors: Vec<DiagnosticCollector>,
label: String,
}
#[allow(dead_code)]
impl DiagnosticAggregator {
pub fn new(label: impl Into<String>) -> Self {
Self {
collectors: Vec::new(),
label: label.into(),
}
}
pub fn add_collector(&mut self, c: DiagnosticCollector) {
self.collectors.push(c);
}
pub fn total_errors(&self) -> usize {
self.collectors.iter().map(|c| c.error_count()).sum()
}
pub fn total_warnings(&self) -> usize {
self.collectors.iter().map(|c| c.warning_count()).sum()
}
pub fn total_count(&self) -> usize {
self.collectors.iter().map(|c| c.diagnostics().len()).sum()
}
pub fn flat_sorted(&self) -> Vec<Diagnostic> {
let mut all: Vec<Diagnostic> = self
.collectors
.iter()
.flat_map(|c| c.diagnostics().iter().cloned())
.collect();
all.sort_by(|a, b| {
a.span
.line
.cmp(&b.span.line)
.then(a.span.column.cmp(&b.span.column))
});
all
}
pub fn has_errors(&self) -> bool {
self.collectors.iter().any(|c| c.has_errors())
}
pub fn summary(&self) -> String {
format!(
"DiagnosticAggregator [{}]: {} errors, {} warnings across {} collectors",
self.label,
self.total_errors(),
self.total_warnings(),
self.collectors.len()
)
}
pub fn collector_count(&self) -> usize {
self.collectors.len()
}
}
#[allow(dead_code)]
pub struct DiagnosticSuppressor {
suppressed_codes: Vec<DiagnosticCode>,
suppress_warnings: bool,
suppress_hints: bool,
}
#[allow(dead_code)]
impl DiagnosticSuppressor {
pub fn new() -> Self {
Self {
suppressed_codes: Vec::new(),
suppress_warnings: false,
suppress_hints: false,
}
}
pub fn suppress_code(mut self, code: DiagnosticCode) -> Self {
self.suppressed_codes.push(code);
self
}
pub fn suppress_all_warnings(mut self) -> Self {
self.suppress_warnings = true;
self
}
pub fn suppress_all_hints(mut self) -> Self {
self.suppress_hints = true;
self
}
pub fn should_suppress(&self, d: &Diagnostic) -> bool {
if self.suppress_warnings && d.severity == Severity::Warning {
return true;
}
if self.suppress_hints && d.severity == Severity::Hint {
return true;
}
if let Some(code) = d.code {
return self.suppressed_codes.contains(&code);
}
false
}
pub fn filter(&self, diags: Vec<Diagnostic>) -> Vec<Diagnostic> {
diags
.into_iter()
.filter(|d| !self.should_suppress(d))
.collect()
}
pub fn filter_collector(&self, c: &DiagnosticCollector) -> DiagnosticCollector {
let mut new_c = DiagnosticCollector::new();
for d in c.diagnostics() {
if !self.should_suppress(d) {
new_c.add(d.clone());
}
}
new_c
}
}
#[allow(dead_code)]
pub struct DiagnosticFilter<'a> {
collector: &'a DiagnosticCollector,
}
#[allow(dead_code)]
impl<'a> DiagnosticFilter<'a> {
pub fn new(collector: &'a DiagnosticCollector) -> Self {
Self { collector }
}
pub fn with_code(&self, code: DiagnosticCode) -> Vec<&Diagnostic> {
self.collector
.diagnostics()
.iter()
.filter(|d| d.code == Some(code))
.collect()
}
pub fn message_contains(&self, needle: &str) -> Vec<&Diagnostic> {
self.collector
.diagnostics()
.iter()
.filter(|d| d.message.contains(needle))
.collect()
}
pub fn in_line_range(&self, from_line: usize, to_line: usize) -> Vec<&Diagnostic> {
self.collector
.diagnostics()
.iter()
.filter(|d| d.span.line >= from_line && d.span.line <= to_line)
.collect()
}
pub fn with_fixes(&self) -> Vec<&Diagnostic> {
self.collector
.diagnostics()
.iter()
.filter(|d| !d.fixes.is_empty())
.collect()
}
pub fn with_help(&self) -> Vec<&Diagnostic> {
self.collector
.diagnostics()
.iter()
.filter(|d| d.help.is_some())
.collect()
}
pub fn errors(&self) -> Vec<&Diagnostic> {
self.collector.filter_severity(Severity::Error)
}
pub fn warnings(&self) -> Vec<&Diagnostic> {
self.collector.filter_severity(Severity::Warning)
}
pub fn infos(&self) -> Vec<&Diagnostic> {
self.collector.filter_severity(Severity::Info)
}
pub fn hints(&self) -> Vec<&Diagnostic> {
self.collector.filter_severity(Severity::Hint)
}
}
pub struct DiagnosticCollector {
diagnostics: Vec<Diagnostic>,
error_count: usize,
warning_count: usize,
}
impl DiagnosticCollector {
pub fn new() -> Self {
Self {
diagnostics: Vec::new(),
error_count: 0,
warning_count: 0,
}
}
pub fn add(&mut self, diagnostic: Diagnostic) {
if diagnostic.is_error() {
self.error_count += 1;
} else if diagnostic.is_warning() {
self.warning_count += 1;
}
self.diagnostics.push(diagnostic);
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn error_count(&self) -> usize {
self.error_count
}
pub fn warning_count(&self) -> usize {
self.warning_count
}
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn clear(&mut self) {
self.diagnostics.clear();
self.error_count = 0;
self.warning_count = 0;
}
#[allow(dead_code)]
pub fn diagnostics_at(&self, line: usize) -> Vec<&Diagnostic> {
self.diagnostics
.iter()
.filter(|d| d.span.line == line)
.collect()
}
#[allow(dead_code)]
pub fn info_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity == Severity::Info)
.count()
}
#[allow(dead_code)]
pub fn sort_by_severity(&mut self) {
self.diagnostics.sort_by_key(|d| d.severity);
}
#[allow(dead_code)]
pub fn sort_by_position(&mut self) {
self.diagnostics.sort_by(|a, b| {
a.span
.line
.cmp(&b.span.line)
.then(a.span.column.cmp(&b.span.column))
});
}
#[allow(dead_code)]
pub fn filter_severity(&self, severity: Severity) -> Vec<&Diagnostic> {
self.diagnostics
.iter()
.filter(|d| d.severity == severity)
.collect()
}
#[allow(dead_code)]
pub fn merge(&mut self, other: &DiagnosticCollector) {
for diag in &other.diagnostics {
self.add(diag.clone());
}
}
#[allow(dead_code)]
pub fn summary(&self) -> String {
let info = self.info_count();
let mut parts = Vec::new();
if self.error_count > 0 {
parts.push(format!(
"{} error{}",
self.error_count,
if self.error_count == 1 { "" } else { "s" }
));
}
if self.warning_count > 0 {
parts.push(format!(
"{} warning{}",
self.warning_count,
if self.warning_count == 1 { "" } else { "s" }
));
}
if info > 0 {
parts.push(format!("{} info{}", info, if info == 1 { "" } else { "s" }));
}
if parts.is_empty() {
"no diagnostics".to_string()
} else {
parts.join(", ")
}
}
}
#[derive(Debug, Clone)]
pub struct CodeFix {
pub message: String,
pub span: Span,
pub replacement: String,
}
#[allow(dead_code)]
#[derive(Debug, Default, Clone)]
pub struct DiagnosticGroup {
pub name: String,
pub diagnostics: Vec<Diagnostic>,
}
#[allow(dead_code)]
impl DiagnosticGroup {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
diagnostics: Vec::new(),
}
}
pub fn add(&mut self, d: Diagnostic) {
self.diagnostics.push(d);
}
pub fn error_count(&self) -> usize {
self.diagnostics.iter().filter(|d| d.is_error()).count()
}
pub fn warning_count(&self) -> usize {
self.diagnostics.iter().filter(|d| d.is_warning()).count()
}
pub fn has_errors(&self) -> bool {
self.diagnostics.iter().any(|d| d.is_error())
}
pub fn len(&self) -> usize {
self.diagnostics.len()
}
pub fn is_empty(&self) -> bool {
self.diagnostics.is_empty()
}
pub fn summary(&self) -> String {
format!(
"[{}]: {} errors, {} warnings",
self.name,
self.error_count(),
self.warning_count()
)
}
pub fn sort_by_position(&mut self) {
self.diagnostics.sort_by(|a, b| {
a.span
.line
.cmp(&b.span.line)
.then(a.span.column.cmp(&b.span.column))
});
}
}