mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
ruff_db: add new Diagnostic
type
... with supporting types. This is meant to give us a base to work with in terms of our new diagnostic data model. I expect the representations to be tweaked over time, but I think this is a decent start. I would also like to add doctest examples, but I think it's better if we wait until an initial version of the renderer is done for that.
This commit is contained in:
parent
80be0a0115
commit
cc324abcc2
1 changed files with 287 additions and 0 deletions
|
@ -14,6 +14,293 @@ use crate::files::File;
|
|||
// the APIs in this module.
|
||||
mod old;
|
||||
|
||||
/// A collection of information that can be rendered into a diagnostic.
|
||||
///
|
||||
/// A diagnostic is a collection of information gathered by a tool intended
|
||||
/// for presentation to an end user, and which describes a group of related
|
||||
/// characteristics in the inputs given to the tool. Typically, but not always,
|
||||
/// a characteristic is a deficiency. An example of a characteristic that is
|
||||
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
/// We box the diagnostic since it is somewhat big.
|
||||
inner: Box<DiagnosticInner>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Create a new diagnostic with the given identifier, severity and
|
||||
/// message.
|
||||
///
|
||||
/// The identifier should be something that uniquely identifies the _type_
|
||||
/// of diagnostic being reported. It should be usable as a reference point
|
||||
/// for humans communicating about diagnostic categories. It will also
|
||||
/// appear in the output when this diagnostic is rendered.
|
||||
///
|
||||
/// The severity should describe the assumed level of importance to an end
|
||||
/// user.
|
||||
///
|
||||
/// The message is meant to be read by end users. The primary message
|
||||
/// is meant to be a single terse description (usually a short phrase)
|
||||
/// describing the group of related characteristics that the diagnostic
|
||||
/// describes. Stated differently, if only one thing from a diagnostic can
|
||||
/// be shown to an end user in a particular context, it is the primary
|
||||
/// message.
|
||||
pub fn new<'a>(
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
message: impl std::fmt::Display + 'a,
|
||||
) -> Diagnostic {
|
||||
let message = message.to_string().into_boxed_str();
|
||||
let inner = Box::new(DiagnosticInner {
|
||||
id,
|
||||
severity,
|
||||
message,
|
||||
annotations: vec![],
|
||||
subs: vec![],
|
||||
#[cfg(debug_assertions)]
|
||||
printed: false,
|
||||
});
|
||||
Diagnostic { inner }
|
||||
}
|
||||
|
||||
/// Add an annotation to this diagnostic.
|
||||
///
|
||||
/// Annotations for a diagnostic are optional, but if any are added,
|
||||
/// callers should strive to make at least one of them primary. That is, it
|
||||
/// should be constructed via [`Annotation::primary`]. A diagnostic with no
|
||||
/// primary annotations is allowed, but its rendering may be sub-optimal.
|
||||
pub fn annotate(&mut self, ann: Annotation) {
|
||||
self.inner.annotations.push(ann);
|
||||
}
|
||||
|
||||
/// Adds an "info" sub-diagnostic with the given message.
|
||||
///
|
||||
/// If callers want to add an "info" sub-diagnostic with annotations, then
|
||||
/// create a [`SubDiagnostic`] manually and use [`Diagnostic::sub`] to
|
||||
/// attach it to a parent diagnostic.
|
||||
///
|
||||
/// An "info" diagnostic is useful when contextualizing or otherwise
|
||||
/// helpful information can be added to help end users understand the
|
||||
/// main diagnostic message better. For example, if a the main diagnostic
|
||||
/// message is about a function call being invalid, a useful "info"
|
||||
/// sub-diagnostic could show the function definition (or only the relevant
|
||||
/// parts of it).
|
||||
pub fn info<'a>(&mut self, message: impl std::fmt::Display + 'a) {
|
||||
self.sub(SubDiagnostic::new(Severity::Info, message));
|
||||
}
|
||||
|
||||
/// Adds a "sub" diagnostic to this diagnostic.
|
||||
///
|
||||
/// This is useful when a sub diagnostic has its own annotations attached
|
||||
/// to it. For the simpler case of a sub-diagnostic with only a message,
|
||||
/// using a method like [`Diagnostic::info`] may be more convenient.
|
||||
pub fn sub(&mut self, sub: SubDiagnostic) {
|
||||
self.inner.subs.push(sub);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
message: Box<str>,
|
||||
annotations: Vec<Annotation>,
|
||||
subs: Vec<SubDiagnostic>,
|
||||
/// This will make the `Drop` impl panic if a `Diagnostic` hasn't
|
||||
/// been printed to stderr. This is usually a bug, so we want it to
|
||||
/// be loud. But only when `debug_assertions` is enabled.
|
||||
#[cfg(debug_assertions)]
|
||||
printed: bool,
|
||||
}
|
||||
|
||||
impl Drop for DiagnosticInner {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if self.printed || std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
panic!(
|
||||
"diagnostic `{id}` with severity `{severity:?}` and message `{message}` \
|
||||
did not get printed to stderr before being dropped",
|
||||
id = self.id,
|
||||
severity = self.severity,
|
||||
message = self.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of information subservient to a diagnostic.
|
||||
///
|
||||
/// A sub-diagnostic is always rendered after the parent diagnostic it is
|
||||
/// attached to. A parent diagnostic may have many sub-diagnostics, and it is
|
||||
/// guaranteed that they will not interleave with one another in rendering.
|
||||
///
|
||||
/// Currently, the order in which sub-diagnostics are rendered relative to one
|
||||
/// another (for a single parent diagnostic) is the order in which they were
|
||||
/// attached to the diagnostic.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
inner: Box<SubDiagnosticInner>,
|
||||
}
|
||||
|
||||
impl SubDiagnostic {
|
||||
/// Create a new sub-diagnostic with the given severity and message.
|
||||
///
|
||||
/// The severity should describe the assumed level of importance to an end
|
||||
/// user.
|
||||
///
|
||||
/// The message is meant to be read by end users. The primary message
|
||||
/// is meant to be a single terse description (usually a short phrase)
|
||||
/// describing the group of related characteristics that the sub-diagnostic
|
||||
/// describes. Stated differently, if only one thing from a diagnostic can
|
||||
/// be shown to an end user in a particular context, it is the primary
|
||||
/// message.
|
||||
pub fn new<'a>(severity: Severity, message: impl std::fmt::Display + 'a) -> SubDiagnostic {
|
||||
let message = message.to_string().into_boxed_str();
|
||||
let inner = Box::new(SubDiagnosticInner {
|
||||
severity,
|
||||
message,
|
||||
annotations: vec![],
|
||||
#[cfg(debug_assertions)]
|
||||
printed: false,
|
||||
});
|
||||
SubDiagnostic { inner }
|
||||
}
|
||||
|
||||
/// Add an annotation to this sub-diagnostic.
|
||||
///
|
||||
/// Annotations for a sub-diagnostic, like for a diagnostic, are optional.
|
||||
/// If any are added, callers should strive to make at least one of them
|
||||
/// primary. That is, it should be constructed via [`Annotation::primary`].
|
||||
/// A diagnostic with no primary annotations is allowed, but its rendering
|
||||
/// may be sub-optimal.
|
||||
///
|
||||
/// Note that it is expected to be somewhat more common for sub-diagnostics
|
||||
/// to have no annotations (e.g., a simple note) than for a diagnostic to
|
||||
/// have no annotations.
|
||||
pub fn annotate(&mut self, ann: Annotation) {
|
||||
self.inner.annotations.push(ann);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: Severity,
|
||||
message: Box<str>,
|
||||
annotations: Vec<Annotation>,
|
||||
/// This will make the `Drop` impl panic if a `SubDiagnostic` hasn't
|
||||
/// been printed to stderr. This is usually a bug, so we want it to
|
||||
/// be loud. But only when `debug_assertions` is enabled.
|
||||
#[cfg(debug_assertions)]
|
||||
printed: bool,
|
||||
}
|
||||
|
||||
impl Drop for SubDiagnosticInner {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if self.printed || std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
panic!(
|
||||
"sub-diagnostic with severity `{severity:?}` and message `{message}` \
|
||||
did not get printed to stderr before being dropped",
|
||||
severity = self.severity,
|
||||
message = self.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer to a subsequence in the end user's input.
|
||||
///
|
||||
/// Also known as an annotation, the pointer can optionally contain a short
|
||||
/// message, typically describing in general terms what is being pointed to.
|
||||
///
|
||||
/// An annotation is either primary or secondary, depending on whether it was
|
||||
/// constructed via [`Annotation::primary`] or [`Annotation::secondary`].
|
||||
/// Semantically, a primary annotation is meant to point to the "locus" of a
|
||||
/// diagnostic. Visually, the difference between a primary and a secondary
|
||||
/// annotation is usually just a different form of highlighting on the
|
||||
/// corresponding span.
|
||||
///
|
||||
/// # Advice
|
||||
///
|
||||
/// The span on an annotation should be as _specific_ as possible. For example,
|
||||
/// if there is a problem with a function call because one of its arguments has
|
||||
/// an invalid type, then the span should point to the specific argument and
|
||||
/// not to the entire function call.
|
||||
///
|
||||
/// Messages attached to annotations should also be as brief and specific as
|
||||
/// possible. Long messages could negative impact the quality of rendering.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
span: Span,
|
||||
/// An optional message associated with this annotation's span.
|
||||
///
|
||||
/// When present, rendering will include this message in the output and
|
||||
/// draw a line between the highlighted span and the message.
|
||||
message: Option<Box<str>>,
|
||||
/// Whether this annotation is "primary" or not. When it isn't primary, an
|
||||
/// annotation is said to be "secondary."
|
||||
is_primary: bool,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
/// Create a "primary" annotation.
|
||||
///
|
||||
/// A primary annotation is meant to highlight the "locus" of a diagnostic.
|
||||
/// That is, it should point to something in the end user's input that is
|
||||
/// the subject or "point" of a diagnostic.
|
||||
///
|
||||
/// A diagnostic may have many primary annotations. A diagnostic may not
|
||||
/// have any annotations, but if it does, at least one _ought_ to be
|
||||
/// primary.
|
||||
pub fn primary(span: Span) -> Annotation {
|
||||
Annotation {
|
||||
span,
|
||||
message: None,
|
||||
is_primary: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a "secondary" annotation.
|
||||
///
|
||||
/// A secondary annotation is meant to highlight relevant context for a
|
||||
/// diagnostic, but not to point to the "locus" of the diagnostic.
|
||||
///
|
||||
/// A diagnostic with only secondary annotations is usually not sensible,
|
||||
/// but it is allowed and will produce a reasonable rendering.
|
||||
pub fn secondary(span: Span) -> Annotation {
|
||||
Annotation {
|
||||
span,
|
||||
message: None,
|
||||
is_primary: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a message to this annotation.
|
||||
///
|
||||
/// An annotation without a message will still have a presence in
|
||||
/// rendering. In particular, it will highlight the span association with
|
||||
/// this annotation in some way.
|
||||
///
|
||||
/// When a message is attached to an annotation, then it will be associated
|
||||
/// with the highlighted span in some way during rendering.
|
||||
pub fn message<'a>(self, message: impl std::fmt::Display + 'a) -> Annotation {
|
||||
let message = Some(message.to_string().into_boxed_str());
|
||||
Annotation { message, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// A string identifier for a lint rule.
|
||||
///
|
||||
/// This string is used in command line and configuration interfaces. The name should always
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue