mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
Introduce Diagnostic
trait (#14130)
This commit is contained in:
parent
b8188b2262
commit
59c0dacea0
20 changed files with 639 additions and 255 deletions
180
crates/ruff_db/src/diagnostic.rs
Normal file
180
crates/ruff_db/src/diagnostic.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue