Introduce Diagnostic trait (#14130)

This commit is contained in:
Micha Reiser 2024-11-07 13:26:21 +01:00 committed by GitHub
parent b8188b2262
commit 59c0dacea0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 639 additions and 255 deletions

View file

@ -0,0 +1,180 @@
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<str>;
fn file(&self) -> File;
fn range(&self) -> Option<TextRange>;
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<T> Diagnostic for Box<T>
where
T: Diagnostic,
{
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> Cow<str> {
(**self).message()
}
fn file(&self) -> File {
(**self).file()
}
fn range(&self) -> Option<TextRange> {
(**self).range()
}
fn severity(&self) -> Severity {
(**self).severity()
}
}
impl<T> Diagnostic for std::sync::Arc<T>
where
T: Diagnostic,
{
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> std::borrow::Cow<str> {
(**self).message()
}
fn file(&self) -> File {
(**self).file()
}
fn range(&self) -> Option<TextRange> {
(**self).range()
}
fn severity(&self) -> Severity {
(**self).severity()
}
}
impl Diagnostic for Box<dyn Diagnostic> {
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> Cow<str> {
(**self).message()
}
fn file(&self) -> File {
(**self).file()
}
fn range(&self) -> Option<TextRange> {
(**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<str> {
self.error.error.to_string().into()
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<TextRange> {
Some(self.error.location)
}
fn severity(&self) -> Severity {
Severity::Error
}
}

View file

@ -6,6 +6,7 @@ use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
pub mod diagnostic;
pub mod display;
pub mod file_revision;
pub mod files;

View file

@ -1,9 +1,7 @@
use std::fmt::Formatter;
use std::ops::Deref;
use std::sync::Arc;
use countme::Count;
use salsa::Accumulator;
use ruff_notebook::Notebook;
use ruff_python_ast::PySourceType;
@ -17,16 +15,14 @@ use crate::Db;
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
let path = file.path(db);
let _span = tracing::trace_span!("source_text", file = %path).entered();
let mut has_read_error = false;
let mut read_error = None;
let kind = if is_notebook(file.path(db)) {
file.read_to_notebook(db)
.unwrap_or_else(|error| {
tracing::debug!("Failed to read notebook '{path}': {error}");
has_read_error = true;
SourceDiagnostic(Arc::new(SourceTextError::FailedToReadNotebook(error)))
.accumulate(db);
read_error = Some(SourceTextError::FailedToReadNotebook(error.to_string()));
Notebook::empty()
})
.into()
@ -35,8 +31,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
.unwrap_or_else(|error| {
tracing::debug!("Failed to read file '{path}': {error}");
has_read_error = true;
SourceDiagnostic(Arc::new(SourceTextError::FailedToReadFile(error))).accumulate(db);
read_error = Some(SourceTextError::FailedToReadFile(error.to_string()));
String::new()
})
.into()
@ -45,7 +40,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
SourceText {
inner: Arc::new(SourceTextInner {
kind,
has_read_error,
read_error,
count: Count::new(),
}),
}
@ -98,8 +93,8 @@ impl SourceText {
}
/// Returns `true` if there was an error when reading the content of the file.
pub fn has_read_error(&self) -> bool {
self.inner.has_read_error
pub fn read_error(&self) -> Option<&SourceTextError> {
self.inner.read_error.as_ref()
}
}
@ -132,7 +127,7 @@ impl std::fmt::Debug for SourceText {
struct SourceTextInner {
count: Count<SourceText>,
kind: SourceTextKind,
has_read_error: bool,
read_error: Option<SourceTextError>,
}
#[derive(Eq, PartialEq)]
@ -153,21 +148,12 @@ impl From<Notebook> for SourceTextKind {
}
}
#[salsa::accumulator]
pub struct SourceDiagnostic(Arc<SourceTextError>);
impl std::fmt::Display for SourceDiagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(#[from] ruff_notebook::NotebookError),
FailedToReadNotebook(String),
#[error("Failed to read file: {0}")]
FailedToReadFile(#[from] std::io::Error),
FailedToReadFile(String),
}
/// Computes the [`LineIndex`] for `file`.