use crate::{ files::File, source::{line_index, source_text}, Db, }; use ruff_python_parser::ParseError; use ruff_text_size::TextRange; use std::borrow::Cow; pub trait Diagnostic: Send + Sync + std::fmt::Debug { fn rule(&self) -> &str; fn message(&self) -> std::borrow::Cow; fn file(&self) -> File; fn range(&self) -> Option; fn severity(&self) -> Severity; fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a> where Self: Sized, { DisplayDiagnostic { db, diagnostic: self, } } } #[derive(Debug, Clone, Copy)] pub enum Severity { Info, Error, } pub struct DisplayDiagnostic<'db> { db: &'db dyn Db, diagnostic: &'db dyn Diagnostic, } impl<'db> DisplayDiagnostic<'db> { pub fn new(db: &'db dyn Db, diagnostic: &'db dyn Diagnostic) -> Self { Self { db, diagnostic } } } impl std::fmt::Display for DisplayDiagnostic<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.diagnostic.severity() { Severity::Info => f.write_str("info")?, Severity::Error => f.write_str("error")?, } write!( f, "[{rule}] {path}", rule = self.diagnostic.rule(), path = self.diagnostic.file().path(self.db) )?; if let Some(range) = self.diagnostic.range() { let index = line_index(self.db, self.diagnostic.file()); let source = source_text(self.db, self.diagnostic.file()); let start = index.source_location(range.start(), &source); write!(f, ":{line}:{col}", line = start.row, col = start.column)?; } write!(f, " {message}", message = self.diagnostic.message()) } } impl Diagnostic for Box where T: Diagnostic, { fn rule(&self) -> &str { (**self).rule() } fn message(&self) -> Cow { (**self).message() } fn file(&self) -> File { (**self).file() } fn range(&self) -> Option { (**self).range() } fn severity(&self) -> Severity { (**self).severity() } } impl Diagnostic for std::sync::Arc where T: Diagnostic, { fn rule(&self) -> &str { (**self).rule() } fn message(&self) -> std::borrow::Cow { (**self).message() } fn file(&self) -> File { (**self).file() } fn range(&self) -> Option { (**self).range() } fn severity(&self) -> Severity { (**self).severity() } } impl Diagnostic for Box { fn rule(&self) -> &str { (**self).rule() } fn message(&self) -> Cow { (**self).message() } fn file(&self) -> File { (**self).file() } fn range(&self) -> Option { (**self).range() } fn severity(&self) -> Severity { (**self).severity() } } #[derive(Debug)] pub struct ParseDiagnostic { file: File, error: ParseError, } impl ParseDiagnostic { pub fn new(file: File, error: ParseError) -> Self { Self { file, error } } } impl Diagnostic for ParseDiagnostic { fn rule(&self) -> &str { "invalid-syntax" } fn message(&self) -> Cow { self.error.error.to_string().into() } fn file(&self) -> File { self.file } fn range(&self) -> Option { Some(self.error.location) } fn severity(&self) -> Severity { Severity::Error } }