use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub file: String,
pub line: usize,
pub column: usize,
pub message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}: {}",
self.file, self.line, self.column, self.message
)
}
}
impl std::error::Error for ParseError {}
impl ParseError {
pub fn new(file: &str, line: usize, column: usize, message: impl Into<String>) -> Self {
ParseError {
file: file.to_string(),
line,
column,
message: message.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Error,
Warning,
}
impl fmt::Display for DiagnosticSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiagnosticSeverity::Error => write!(f, "error"),
DiagnosticSeverity::Warning => write!(f, "warning"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LintDiagnostic {
pub file: String,
pub line: usize,
pub column: usize,
pub severity: DiagnosticSeverity,
pub message: String,
}
impl fmt::Display for LintDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}: {}: {}",
self.file, self.line, self.column, self.severity, self.message
)
}
}
impl LintDiagnostic {
pub fn error(file: &str, line: usize, column: usize, message: impl Into<String>) -> Self {
LintDiagnostic {
file: file.to_string(),
line,
column,
severity: DiagnosticSeverity::Error,
message: message.into(),
}
}
pub fn warning(file: &str, line: usize, column: usize, message: impl Into<String>) -> Self {
LintDiagnostic {
file: file.to_string(),
line,
column,
severity: DiagnosticSeverity::Warning,
message: message.into(),
}
}
}
impl From<ParseError> for LintDiagnostic {
fn from(err: ParseError) -> Self {
LintDiagnostic {
file: err.file,
line: err.line,
column: err.column,
severity: DiagnosticSeverity::Error,
message: err.message,
}
}
}
pub fn format_lint_output(errors: &[ParseError]) -> String {
errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display_format() {
let err = ParseError::new("test.7", 5, 3, "unexpected token");
assert_eq!(format!("{}", err), "test.7:5:3: unexpected token");
}
#[test]
fn test_error_new() {
let err = ParseError::new("main.7", 10, 1, "missing colon");
assert_eq!(err.file, "main.7");
assert_eq!(err.line, 10);
assert_eq!(err.column, 1);
assert_eq!(err.message, "missing colon");
}
#[test]
fn test_error_is_std_error() {
let err = ParseError::new("test.7", 1, 1, "bad");
let _: &dyn std::error::Error = &err;
}
#[test]
fn test_lint_diagnostic_error() {
let diag = LintDiagnostic::error("test.7", 5, 3, "unexpected token");
assert_eq!(format!("{}", diag), "test.7:5:3: error: unexpected token");
}
#[test]
fn test_lint_diagnostic_warning() {
let diag = LintDiagnostic::warning("test.7", 12, 1, "unused variable");
assert_eq!(format!("{}", diag), "test.7:12:1: warning: unused variable");
}
#[test]
fn test_lint_diagnostic_from_parse_error() {
let err = ParseError::new("test.7", 5, 3, "unexpected token");
let diag: LintDiagnostic = err.into();
assert_eq!(diag.severity, DiagnosticSeverity::Error);
assert_eq!(format!("{}", diag), "test.7:5:3: error: unexpected token");
}
#[test]
fn test_format_lint_output() {
let errors = vec![
ParseError::new("a.7", 1, 1, "first error"),
ParseError::new("b.7", 2, 5, "second error"),
];
let output = format_lint_output(&errors);
assert_eq!(output, "a.7:1:1: first error\nb.7:2:5: second error");
}
#[test]
fn test_format_lint_output_empty() {
let errors: Vec<ParseError> = vec![];
let output = format_lint_output(&errors);
assert_eq!(output, "");
}
}