mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-22 19:34:23 +00:00
Move fix suggestion to subdiagnostic (#19464)
Summary -- This PR tweaks Ruff's internal usage of the new diagnostic model to more closely match the intended use, as I understand it. Specifically, it moves the fix/help suggestion from the primary annotation's message to a subdiagnostic. In turn, it adds the secondary/noqa code as the new primary annotation message. As shown in the new `ruff_db` tests, this more closely mirrors Ruff's current diagnostic output. I also added `Severity::Help` to render the fix suggestion with a `help:` prefix instead of `info:`. These changes don't have any external impact now but should help a bit with #19415. Test Plan -- New full output format tests in `ruff_db` Rendered Diagnostics -- Full diagnostic output from `annotate-snippets` in this PR: ``` error[unused-import]: `os` imported but unused --> fib.py:1:8 | 1 | import os | ^^ | help: Remove unused import: `os` ``` Current Ruff output for the same code: ``` fib.py:1:8: F401 [*] `os` imported but unused | 1 | import os | ^^ F401 | = help: Remove unused import: `os` ``` Proposed final output after #19415: ``` F401 [*] `os` imported but unused --> fib.py:1:8 | 1 | import os | ^^ | help: Remove unused import: `os` ``` These are slightly updated from https://github.com/astral-sh/ruff/pull/19464#issuecomment-3097377634 below to remove the extra noqa codes in the primary annotation messages for the first and third cases.
This commit is contained in:
parent
c82fa94e0a
commit
fd335eb8b7
20 changed files with 235 additions and 106 deletions
|
@ -454,7 +454,7 @@ impl LintCacheData {
|
|||
CacheMessage {
|
||||
rule,
|
||||
body: msg.body().to_string(),
|
||||
suggestion: msg.suggestion().map(ToString::to_string),
|
||||
suggestion: msg.first_help_text().map(ToString::to_string),
|
||||
range: msg.expect_range(),
|
||||
parent: msg.parent(),
|
||||
fix: msg.fix().cloned(),
|
||||
|
|
|
@ -122,7 +122,14 @@ impl Diagnostic {
|
|||
/// directly. If callers want or need to avoid cloning the diagnostic
|
||||
/// message, then they can also pass a `DiagnosticMessage` directly.
|
||||
pub fn info<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
|
||||
self.sub(SubDiagnostic::new(Severity::Info, message));
|
||||
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Info, message));
|
||||
}
|
||||
|
||||
/// Adds a "help" sub-diagnostic with the given message.
|
||||
///
|
||||
/// See the closely related [`Diagnostic::info`] method for more details.
|
||||
pub fn help<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
|
||||
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Help, message));
|
||||
}
|
||||
|
||||
/// Adds a "sub" diagnostic to this diagnostic.
|
||||
|
@ -377,9 +384,15 @@ impl Diagnostic {
|
|||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the fix suggestion for the violation.
|
||||
pub fn suggestion(&self) -> Option<&str> {
|
||||
self.primary_annotation()?.get_message()
|
||||
/// Returns the message of the first sub-diagnostic with a `Help` severity.
|
||||
///
|
||||
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
|
||||
/// general this is not the guaranteed meaning of such a message.
|
||||
pub fn first_help_text(&self) -> Option<&str> {
|
||||
self.sub_diagnostics()
|
||||
.iter()
|
||||
.find(|sub| matches!(sub.inner.severity, SubDiagnosticSeverity::Help))
|
||||
.map(|sub| sub.inner.message.as_str())
|
||||
}
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
|
@ -565,7 +578,10 @@ impl SubDiagnostic {
|
|||
/// Callers can pass anything that implements `std::fmt::Display`
|
||||
/// directly. If callers want or need to avoid cloning the diagnostic
|
||||
/// message, then they can also pass a `DiagnosticMessage` directly.
|
||||
pub fn new<'a>(severity: Severity, message: impl IntoDiagnosticMessage + 'a) -> SubDiagnostic {
|
||||
pub fn new<'a>(
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: impl IntoDiagnosticMessage + 'a,
|
||||
) -> SubDiagnostic {
|
||||
let inner = Box::new(SubDiagnosticInner {
|
||||
severity,
|
||||
message: message.into_diagnostic_message(),
|
||||
|
@ -643,7 +659,7 @@ impl SubDiagnostic {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: Severity,
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: DiagnosticMessage,
|
||||
annotations: Vec<Annotation>,
|
||||
}
|
||||
|
@ -1170,6 +1186,30 @@ impl Severity {
|
|||
}
|
||||
}
|
||||
|
||||
/// Like [`Severity`] but exclusively for sub-diagnostics.
|
||||
///
|
||||
/// This supports an additional `Help` severity that may not be needed in main diagnostics.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
pub enum SubDiagnosticSeverity {
|
||||
Help,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
impl SubDiagnosticSeverity {
|
||||
fn to_annotate(self) -> AnnotateLevel {
|
||||
match self {
|
||||
SubDiagnosticSeverity::Help => AnnotateLevel::Help,
|
||||
SubDiagnosticSeverity::Info => AnnotateLevel::Info,
|
||||
SubDiagnosticSeverity::Warning => AnnotateLevel::Warning,
|
||||
SubDiagnosticSeverity::Error => AnnotateLevel::Error,
|
||||
SubDiagnosticSeverity::Fatal => AnnotateLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for rendering diagnostics.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DisplayDiagnosticConfig {
|
||||
|
|
|
@ -26,6 +26,7 @@ use azure::AzureRenderer;
|
|||
use pylint::PylintRenderer;
|
||||
|
||||
mod azure;
|
||||
mod full;
|
||||
#[cfg(feature = "serde")]
|
||||
mod json;
|
||||
#[cfg(feature = "serde")]
|
||||
|
@ -256,7 +257,7 @@ impl<'a> Resolved<'a> {
|
|||
/// both.)
|
||||
#[derive(Debug)]
|
||||
struct ResolvedDiagnostic<'a> {
|
||||
severity: Severity,
|
||||
level: AnnotateLevel,
|
||||
id: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
|
@ -281,7 +282,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
|||
let id = Some(diag.inner.id.to_string());
|
||||
let message = diag.inner.message.as_str().to_string();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
id,
|
||||
message,
|
||||
annotations,
|
||||
|
@ -304,7 +305,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
|||
})
|
||||
.collect();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
id: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
|
@ -371,7 +372,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
|||
snippets_by_input
|
||||
.sort_by(|snips1, snips2| snips1.has_primary.cmp(&snips2.has_primary).reverse());
|
||||
RenderableDiagnostic {
|
||||
severity: self.severity,
|
||||
level: self.level,
|
||||
id: self.id.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
|
@ -459,7 +460,7 @@ struct Renderable<'r> {
|
|||
#[derive(Debug)]
|
||||
struct RenderableDiagnostic<'r> {
|
||||
/// The severity of the diagnostic.
|
||||
severity: Severity,
|
||||
level: AnnotateLevel,
|
||||
/// The ID of the diagnostic. The ID can usually be used on the CLI or in a
|
||||
/// config file to change the severity of a lint.
|
||||
///
|
||||
|
@ -478,7 +479,6 @@ struct RenderableDiagnostic<'r> {
|
|||
impl RenderableDiagnostic<'_> {
|
||||
/// Convert this to an "annotate" snippet.
|
||||
fn to_annotate(&self) -> AnnotateMessage<'_> {
|
||||
let level = self.severity.to_annotate();
|
||||
let snippets = self.snippets_by_input.iter().flat_map(|snippets| {
|
||||
let path = snippets.path;
|
||||
snippets
|
||||
|
@ -486,7 +486,7 @@ impl RenderableDiagnostic<'_> {
|
|||
.iter()
|
||||
.map(|snippet| snippet.to_annotate(path))
|
||||
});
|
||||
let mut message = level.title(self.message);
|
||||
let mut message = self.level.title(self.message);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id(id);
|
||||
}
|
||||
|
@ -864,7 +864,10 @@ mod tests {
|
|||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
|
||||
use crate::diagnostic::{Annotation, DiagnosticId, SecondaryCode, Severity, Span};
|
||||
use crate::diagnostic::{
|
||||
Annotation, DiagnosticId, IntoDiagnosticMessage, SecondaryCode, Severity, Span,
|
||||
SubDiagnosticSeverity,
|
||||
};
|
||||
use crate::files::system_path_to_file;
|
||||
use crate::system::{DbWithWritableSystem, SystemPath};
|
||||
use crate::tests::TestDb;
|
||||
|
@ -1548,7 +1551,7 @@ watermelon
|
|||
|
||||
let mut diag = env.err().primary("animals", "3", "3", "").build();
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "this is a helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "this is a helpful note")
|
||||
.build(),
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
|
@ -1577,15 +1580,15 @@ watermelon
|
|||
|
||||
let mut diag = env.err().primary("animals", "3", "3", "").build();
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "this is a helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "this is a helpful note")
|
||||
.build(),
|
||||
);
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "another helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "another helpful note")
|
||||
.build(),
|
||||
);
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "and another helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "and another helpful note")
|
||||
.build(),
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
|
@ -2370,7 +2373,7 @@ watermelon
|
|||
/// sub-diagnostic with "error" severity and canned values for
|
||||
/// its identifier and message.
|
||||
fn sub_warn(&mut self) -> SubDiagnosticBuilder<'_> {
|
||||
self.sub_builder(Severity::Warning, "sub-diagnostic message")
|
||||
self.sub_builder(SubDiagnosticSeverity::Warning, "sub-diagnostic message")
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing diagnostics.
|
||||
|
@ -2391,7 +2394,11 @@ watermelon
|
|||
}
|
||||
|
||||
/// Returns a builder for tersely constructing sub-diagnostics.
|
||||
fn sub_builder(&mut self, severity: Severity, message: &str) -> SubDiagnosticBuilder<'_> {
|
||||
fn sub_builder(
|
||||
&mut self,
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: &str,
|
||||
) -> SubDiagnosticBuilder<'_> {
|
||||
let subdiag = SubDiagnostic::new(severity, message);
|
||||
SubDiagnosticBuilder { env: self, subdiag }
|
||||
}
|
||||
|
@ -2494,6 +2501,12 @@ watermelon
|
|||
self.diag.set_noqa_offset(noqa_offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a "help" sub-diagnostic with the given message.
|
||||
fn help(mut self, message: impl IntoDiagnosticMessage) -> DiagnosticBuilder<'e> {
|
||||
self.diag.help(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper builder for tersely populating a `SubDiagnostic`.
|
||||
|
@ -2600,7 +2613,8 @@ def fibonacci(n):
|
|||
|
||||
let diagnostics = vec![
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("fib.py", "1:7", "1:9", "Remove unused import: `os`")
|
||||
.primary("fib.py", "1:7", "1:9", "")
|
||||
.help("Remove unused import: `os`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(0),
|
||||
|
@ -2613,12 +2627,8 @@ def fibonacci(n):
|
|||
Severity::Error,
|
||||
"Local variable `x` is assigned to but never used",
|
||||
)
|
||||
.primary(
|
||||
"fib.py",
|
||||
"6:4",
|
||||
"6:5",
|
||||
"Remove assignment to unused variable `x`",
|
||||
)
|
||||
.primary("fib.py", "6:4", "6:5", "")
|
||||
.help("Remove assignment to unused variable `x`")
|
||||
.secondary_code("F841")
|
||||
.fix(Fix::unsafe_edit(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
|
@ -2720,7 +2730,8 @@ if call(foo
|
|||
|
||||
let diagnostics = vec![
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "Remove unused import: `os`")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "")
|
||||
.help("Remove unused import: `os`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(9),
|
||||
|
@ -2733,12 +2744,8 @@ if call(foo
|
|||
Severity::Error,
|
||||
"`math` imported but unused",
|
||||
)
|
||||
.primary(
|
||||
"notebook.ipynb",
|
||||
"4:7",
|
||||
"4:11",
|
||||
"Remove unused import: `math`",
|
||||
)
|
||||
.primary("notebook.ipynb", "4:7", "4:11", "")
|
||||
.help("Remove unused import: `math`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(28),
|
||||
|
@ -2751,12 +2758,8 @@ if call(foo
|
|||
Severity::Error,
|
||||
"Local variable `x` is assigned to but never used",
|
||||
)
|
||||
.primary(
|
||||
"notebook.ipynb",
|
||||
"10:4",
|
||||
"10:5",
|
||||
"Remove assignment to unused variable `x`",
|
||||
)
|
||||
.primary("notebook.ipynb", "10:4", "10:5", "")
|
||||
.help("Remove assignment to unused variable `x`")
|
||||
.secondary_code("F841")
|
||||
.fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(94),
|
||||
|
|
66
crates/ruff_db/src/diagnostic/render/full.rs
Normal file
66
crates/ruff_db/src/diagnostic/render/full.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r#"
|
||||
error[unused-import]: `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
error[unused-variable]: Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
error[undefined-name]: Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[invalid-syntax]: SyntaxError: Expected one or more symbol names after import
|
||||
--> syntax_errors.py:1:15
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
2 |
|
||||
3 | if call(foo
|
||||
|
|
||||
|
||||
error[invalid-syntax]: SyntaxError: Expected ')', found newline
|
||||
--> syntax_errors.py:3:12
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
3 | if call(foo
|
||||
| ^
|
||||
4 | def bar():
|
||||
5 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
|||
|
||||
let fix = diagnostic.fix().map(|fix| JsonFix {
|
||||
applicability: fix.applicability(),
|
||||
message: diagnostic.suggestion(),
|
||||
message: diagnostic.first_help_text(),
|
||||
edits: ExpandedEdits {
|
||||
edits: fix.edits(),
|
||||
notebook_index,
|
||||
|
|
|
@ -75,12 +75,13 @@ where
|
|||
);
|
||||
|
||||
let span = Span::from(file).with_range(range);
|
||||
let mut annotation = Annotation::primary(span);
|
||||
if let Some(suggestion) = suggestion {
|
||||
annotation = annotation.message(suggestion);
|
||||
}
|
||||
let annotation = Annotation::primary(span);
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
if let Some(suggestion) = suggestion {
|
||||
diagnostic.help(suggestion);
|
||||
}
|
||||
|
||||
if let Some(fix) = fix {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ pub(super) struct MessageCodeFrame<'a> {
|
|||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let suggestion = self.message.suggestion();
|
||||
let suggestion = self.message.first_help_text();
|
||||
let footers = if let Some(suggestion) = suggestion {
|
||||
vec![Level::Help.title(suggestion)]
|
||||
} else {
|
||||
|
|
|
@ -272,7 +272,7 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e
|
|||
}
|
||||
|
||||
assert!(
|
||||
!(fixable && diagnostic.suggestion().is_none()),
|
||||
!(fixable && diagnostic.first_help_text().is_none()),
|
||||
"Diagnostic emitted by {rule:?} is fixable but \
|
||||
`Violation::fix_title` returns `None`"
|
||||
);
|
||||
|
|
|
@ -238,7 +238,7 @@ fn to_lsp_diagnostic(
|
|||
let name = diagnostic.name();
|
||||
let body = diagnostic.body().to_string();
|
||||
let fix = diagnostic.fix();
|
||||
let suggestion = diagnostic.suggestion();
|
||||
let suggestion = diagnostic.first_help_text();
|
||||
let code = diagnostic.secondary_code();
|
||||
|
||||
let fix = fix.and_then(|fix| fix.applies(Applicability::Unsafe).then_some(fix));
|
||||
|
|
|
@ -234,7 +234,7 @@ impl Workspace {
|
|||
start_location: source_code.line_column(msg.expect_range().start()).into(),
|
||||
end_location: source_code.line_column(msg.expect_range().end()).into(),
|
||||
fix: msg.fix().map(|fix| ExpandedFix {
|
||||
message: msg.suggestion().map(ToString::to_string),
|
||||
message: msg.first_help_text().map(ToString::to_string),
|
||||
edits: fix
|
||||
.edits()
|
||||
.iter()
|
||||
|
|
|
@ -32,6 +32,7 @@ mod tests {
|
|||
use insta::assert_snapshot;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
||||
SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_text_size::Ranged;
|
||||
|
@ -1349,7 +1350,7 @@ class MyClass:
|
|||
|
||||
impl IntoDiagnostic for GotoDeclarationDiagnostic {
|
||||
fn into_diagnostic(self) -> Diagnostic {
|
||||
let mut source = SubDiagnostic::new(Severity::Info, "Source");
|
||||
let mut source = SubDiagnostic::new(SubDiagnosticSeverity::Info, "Source");
|
||||
source.annotate(Annotation::primary(
|
||||
Span::from(self.source.file()).with_range(self.source.range()),
|
||||
));
|
||||
|
|
|
@ -37,6 +37,7 @@ mod test {
|
|||
use insta::assert_snapshot;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
||||
SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_text_size::Ranged;
|
||||
|
@ -575,7 +576,7 @@ class MyClass: ...
|
|||
|
||||
impl IntoDiagnostic for GotoDefinitionDiagnostic {
|
||||
fn into_diagnostic(self) -> Diagnostic {
|
||||
let mut source = SubDiagnostic::new(Severity::Info, "Source");
|
||||
let mut source = SubDiagnostic::new(SubDiagnosticSeverity::Info, "Source");
|
||||
source.annotate(Annotation::primary(
|
||||
Span::from(self.source.file()).with_range(self.source.range()),
|
||||
));
|
||||
|
|
|
@ -33,6 +33,7 @@ mod tests {
|
|||
use insta::assert_snapshot;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
||||
SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_text_size::Ranged;
|
||||
|
@ -640,7 +641,7 @@ f(**kwargs<CURSOR>)
|
|||
|
||||
impl IntoDiagnostic for GotoTypeDefinitionDiagnostic {
|
||||
fn into_diagnostic(self) -> Diagnostic {
|
||||
let mut source = SubDiagnostic::new(Severity::Info, "Source");
|
||||
let mut source = SubDiagnostic::new(SubDiagnosticSeverity::Info, "Source");
|
||||
source.annotate(Annotation::primary(
|
||||
Span::from(self.source.file()).with_range(self.source.range()),
|
||||
));
|
||||
|
|
|
@ -5,7 +5,9 @@ pub use db::{ChangeResult, CheckMode, Db, ProjectDatabase, SalsaMemoryDump};
|
|||
use files::{Index, Indexed, IndexedFiles};
|
||||
use metadata::settings::Settings;
|
||||
pub use metadata::{ProjectMetadata, ProjectMetadataError};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic, SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::files::{File, FileRootKind};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::{SourceTextError, source_text};
|
||||
|
@ -674,14 +676,17 @@ where
|
|||
|
||||
let mut diagnostic = Diagnostic::new(DiagnosticId::Panic, Severity::Fatal, message);
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"This indicates a bug in ty.",
|
||||
));
|
||||
|
||||
let report_message = "If you could open an issue at https://github.com/astral-sh/ty/issues/new?title=%5Bpanic%5D, we'd be very appreciative!";
|
||||
diagnostic.sub(SubDiagnostic::new(Severity::Info, report_message));
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
report_message,
|
||||
));
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!(
|
||||
"Platform: {os} {arch}",
|
||||
os = std::env::consts::OS,
|
||||
|
@ -690,13 +695,13 @@ where
|
|||
));
|
||||
if let Some(version) = ruff_db::program_version() {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!("Version: {version}"),
|
||||
));
|
||||
}
|
||||
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!(
|
||||
"Args: {args:?}",
|
||||
args = std::env::args().collect::<Vec<_>>()
|
||||
|
@ -707,13 +712,13 @@ where
|
|||
match backtrace.status() {
|
||||
BacktraceStatus::Disabled => {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"run with `RUST_BACKTRACE=1` environment variable to show the full backtrace information",
|
||||
));
|
||||
}
|
||||
BacktraceStatus::Captured => {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!("Backtrace:\n{backtrace}"),
|
||||
));
|
||||
}
|
||||
|
@ -723,7 +728,10 @@ where
|
|||
|
||||
if let Some(backtrace) = error.salsa_backtrace {
|
||||
salsa::attach(db, || {
|
||||
diagnostic.sub(SubDiagnostic::new(Severity::Info, backtrace.to_string()));
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
backtrace.to_string(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ use ordermap::OrderMap;
|
|||
use ruff_db::RustDoc;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, Severity,
|
||||
Span, SubDiagnostic,
|
||||
Span, SubDiagnostic, SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||
|
@ -318,7 +318,7 @@ impl Options {
|
|||
|
||||
if self.environment.or_default().root.is_some() {
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The `src.root` setting was ignored in favor of the `environment.root` setting",
|
||||
));
|
||||
}
|
||||
|
@ -811,7 +811,7 @@ fn build_include_filter(
|
|||
Severity::Warning,
|
||||
)
|
||||
.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Remove the `include` option to match all files or add a pattern to match specific files",
|
||||
));
|
||||
|
||||
|
@ -853,13 +853,13 @@ fn build_include_filter(
|
|||
))
|
||||
} else {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!("The pattern is defined in the `{}` option in your configuration file", context.include_name()),
|
||||
))
|
||||
}
|
||||
}
|
||||
ValueSource::Cli => diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified on the CLI",
|
||||
)),
|
||||
ValueSource::PythonVSCodeExtension => unreachable!("Can't configure includes from the Python VSCode extension"),
|
||||
|
@ -883,7 +883,7 @@ fn build_include_filter(
|
|||
Severity::Error,
|
||||
);
|
||||
Box::new(diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Please open an issue on the ty repository and share the patterns that caused the error.",
|
||||
)))
|
||||
})
|
||||
|
@ -936,13 +936,13 @@ fn build_exclude_filter(
|
|||
))
|
||||
} else {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!("The pattern is defined in the `{}` option in your configuration file", context.exclude_name()),
|
||||
))
|
||||
}
|
||||
}
|
||||
ValueSource::Cli => diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified on the CLI",
|
||||
)),
|
||||
ValueSource::PythonVSCodeExtension => unreachable!(
|
||||
|
@ -960,7 +960,7 @@ fn build_exclude_filter(
|
|||
Severity::Error,
|
||||
);
|
||||
Box::new(diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Please open an issue on the ty repository and share the patterns that caused the error.",
|
||||
)))
|
||||
})
|
||||
|
@ -1197,26 +1197,26 @@ impl RangedValue<OverrideOptions> {
|
|||
|
||||
diagnostic = if self.rules.is_none() {
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"It has no `rules` table",
|
||||
));
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Add a `[overrides.rules]` table...",
|
||||
))
|
||||
} else {
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The rules table is empty",
|
||||
));
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Add a rule to `[overrides.rules]` to override specific rules...",
|
||||
))
|
||||
};
|
||||
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"or remove the `[[overrides]]` section if there's nothing to override",
|
||||
));
|
||||
|
||||
|
@ -1251,23 +1251,23 @@ impl RangedValue<OverrideOptions> {
|
|||
|
||||
diagnostic = if self.exclude.is_none() {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"It has no `include` or `exclude` option restricting the files",
|
||||
))
|
||||
} else {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"It has no `include` option and `exclude` is empty",
|
||||
))
|
||||
};
|
||||
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Restrict the files by adding a pattern to `include` or `exclude`...",
|
||||
));
|
||||
|
||||
diagnostic = diagnostic.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"or remove the `[[overrides]]` section and merge the configuration into the root `[rules]` table if the configuration should apply to all files",
|
||||
));
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use diagnostic::{
|
|||
INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL,
|
||||
UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
|
@ -7077,7 +7077,7 @@ impl<'db> BoolError<'db> {
|
|||
not_boolable_type.display(context.db())
|
||||
));
|
||||
let mut sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"`__bool__` methods must only have a `self` parameter",
|
||||
);
|
||||
if let Some((func_span, parameter_span)) = not_boolable_type
|
||||
|
@ -7102,7 +7102,7 @@ impl<'db> BoolError<'db> {
|
|||
not_boolable = not_boolable_type.display(context.db()),
|
||||
));
|
||||
let mut sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"`{return_type}` is not assignable to `bool`",
|
||||
return_type = return_type.display(context.db()),
|
||||
|
@ -7128,7 +7128,7 @@ impl<'db> BoolError<'db> {
|
|||
not_boolable_type.display(context.db())
|
||||
));
|
||||
let sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"`__bool__` on `{}` must be callable",
|
||||
not_boolable_type.display(context.db())
|
||||
|
|
|
@ -30,7 +30,7 @@ use crate::types::{
|
|||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeMapping, UnionType,
|
||||
WrapperDescriptorKind, enums, ide_support, todo_type,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
/// Binding information for a possible union of callables. At a call site, the arguments must be
|
||||
|
@ -1668,8 +1668,10 @@ impl<'db> CallableBinding<'db> {
|
|||
.first()
|
||||
.and_then(|overload| overload.spans(context.db()))
|
||||
{
|
||||
let mut sub =
|
||||
SubDiagnostic::new(Severity::Info, "First overload defined here");
|
||||
let mut sub = SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"First overload defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
|
@ -1696,7 +1698,7 @@ impl<'db> CallableBinding<'db> {
|
|||
implementation.and_then(|function| function.spans(context.db()))
|
||||
{
|
||||
let mut sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Overload implementation defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
|
@ -2570,8 +2572,10 @@ impl<'db> BindingError<'db> {
|
|||
overload.parameter_span(context.db(), Some(parameter.index))
|
||||
})
|
||||
{
|
||||
let mut sub =
|
||||
SubDiagnostic::new(Severity::Info, "Matching overload defined here");
|
||||
let mut sub = SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Matching overload defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(name_span));
|
||||
sub.annotate(
|
||||
Annotation::secondary(parameter_span)
|
||||
|
@ -2607,7 +2611,8 @@ impl<'db> BindingError<'db> {
|
|||
} else if let Some((name_span, parameter_span)) =
|
||||
callable_ty.parameter_span(context.db(), Some(parameter.index))
|
||||
{
|
||||
let mut sub = SubDiagnostic::new(Severity::Info, "Function defined here");
|
||||
let mut sub =
|
||||
SubDiagnostic::new(SubDiagnosticSeverity::Info, "Function defined here");
|
||||
sub.annotate(Annotation::primary(name_span));
|
||||
sub.annotate(
|
||||
Annotation::secondary(parameter_span).message("Parameter declared here"),
|
||||
|
@ -2733,7 +2738,10 @@ impl<'db> BindingError<'db> {
|
|||
let module = parsed_module(context.db(), typevar_definition.file(context.db()))
|
||||
.load(context.db());
|
||||
let typevar_range = typevar_definition.full_range(context.db(), &module);
|
||||
let mut sub = SubDiagnostic::new(Severity::Info, "Type variable defined here");
|
||||
let mut sub = SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Type variable defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(typevar_range.into()));
|
||||
diag.sub(sub);
|
||||
}
|
||||
|
@ -2801,7 +2809,7 @@ impl UnionDiagnostic<'_, '_> {
|
|||
/// diagnostic.
|
||||
fn add_union_context(&self, db: &'_ dyn Db, diag: &mut Diagnostic) {
|
||||
let sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"Union variant `{callable_ty}` is incompatible with this call site",
|
||||
callable_ty = self.binding.callable_type.display(db),
|
||||
|
@ -2810,7 +2818,7 @@ impl UnionDiagnostic<'_, '_> {
|
|||
diag.sub(sub);
|
||||
|
||||
let sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"Attempted to call union type `{}`",
|
||||
self.callable_type.display(db)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt;
|
||||
|
||||
use drop_bomb::DebugDropBomb;
|
||||
use ruff_db::diagnostic::{DiagnosticTag, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{DiagnosticTag, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
use ruff_db::{
|
||||
diagnostic::{Annotation, Diagnostic, DiagnosticId, IntoDiagnosticMessage, Severity, Span},
|
||||
|
@ -330,7 +330,7 @@ impl Drop for LintDiagnosticGuard<'_, '_> {
|
|||
let mut diag = self.diag.take().unwrap();
|
||||
|
||||
diag.sub(SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
match self.source {
|
||||
LintSource::Default => format!("rule `{}` is enabled by default", diag.id()),
|
||||
LintSource::Cli => format!("rule `{}` was selected on the command line", diag.id()),
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
|
|||
use crate::util::diagnostics::format_enumeration;
|
||||
use crate::{Db, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
||||
use itertools::Itertools;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
@ -1929,7 +1929,7 @@ pub(super) fn report_implicit_return_type(
|
|||
));
|
||||
|
||||
let mut sub_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Only classes that directly inherit from `typing.Protocol` \
|
||||
or `typing_extensions.Protocol` are considered protocol classes",
|
||||
);
|
||||
|
@ -2037,7 +2037,7 @@ pub(crate) fn report_instance_layout_conflict(
|
|||
));
|
||||
|
||||
let mut subdiagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
"Two classes cannot coexist in a class's MRO if their instances \
|
||||
have incompatible memory layouts",
|
||||
);
|
||||
|
@ -2230,7 +2230,7 @@ pub(crate) fn report_bad_argument_to_get_protocol_members(
|
|||
diagnostic.info("Only protocol classes can be passed to `get_protocol_members`");
|
||||
|
||||
let mut class_def_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"`{}` is declared here, but it is not a protocol class:",
|
||||
class.name(db)
|
||||
|
@ -2292,7 +2292,7 @@ pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol(
|
|||
diagnostic.set_primary_message("This call will raise `TypeError` at runtime");
|
||||
|
||||
let mut class_def_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"`{class_name}` is declared as a protocol class, \
|
||||
but it is not declared as runtime-checkable"
|
||||
|
@ -2326,7 +2326,7 @@ pub(crate) fn report_attempted_protocol_instantiation(
|
|||
diagnostic.set_primary_message("This call will raise `TypeError` at runtime");
|
||||
|
||||
let mut class_def_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!("Protocol classes cannot be instantiated"),
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
|
@ -2360,7 +2360,7 @@ pub(crate) fn report_duplicate_bases(
|
|||
builder.into_diagnostic(format_args!("Duplicate base class `{duplicate_name}`",));
|
||||
|
||||
let mut sub_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"The definition of class `{}` will raise `TypeError` at runtime",
|
||||
class.name(db)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{Db, Program, PythonVersionWithSource};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use std::fmt::Write;
|
||||
|
||||
/// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred.
|
||||
|
@ -23,7 +23,7 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
|
|||
crate::PythonVersionSource::ConfigFile(source) => {
|
||||
if let Some(span) = source.span(db) {
|
||||
let mut sub_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!("Python {version} was assumed when {action}"),
|
||||
);
|
||||
sub_diagnostic.annotate(Annotation::primary(span).message(format_args!(
|
||||
|
@ -39,7 +39,7 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
|
|||
crate::PythonVersionSource::PyvenvCfgFile(source) => {
|
||||
if let Some(span) = source.span(db) {
|
||||
let mut sub_diagnostic = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!(
|
||||
"Python {version} was assumed when {action} because of your virtual environment"
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue