From e7a74fbe2c9e5c7b669716f941ca0abee9965998 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 24 Jan 2025 16:02:58 +0100 Subject: [PATCH] Add dedicated warning message for `lint:rule` for suppressions and CLI arguments --- .../red_knot_project/src/metadata/options.rs | 10 +++++ .../mdtest/suppressions/knot_ignore.md | 9 +++++ crates/red_knot_python_semantic/src/lint.rs | 37 +++++++++++++++++-- .../src/suppression.rs | 14 ++++++- crates/ruff_db/src/diagnostic.rs | 4 ++ 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/crates/red_knot_project/src/metadata/options.rs b/crates/red_knot_project/src/metadata/options.rs index 33c3dddf3d966d..13c27837c75f6b 100644 --- a/crates/red_knot_project/src/metadata/options.rs +++ b/crates/red_knot_project/src/metadata/options.rs @@ -149,6 +149,16 @@ impl Options { format!("Unknown lint rule `{rule_name}`"), Severity::Warning, ), + GetLintError::PrefixedWithCategory { suggestion, .. } => { + OptionDiagnostic::new( + DiagnosticId::UnknownRule, + format!( + "Unknown lint rule `{rule_name}`. Did you mean `{suggestion}`?" + ), + Severity::Warning, + ) + } + GetLintError::Removed(_) => OptionDiagnostic::new( DiagnosticId::UnknownRule, format!("Unknown lint rule `{rule_name}`"), diff --git a/crates/red_knot_python_semantic/resources/mdtest/suppressions/knot_ignore.md b/crates/red_knot_python_semantic/resources/mdtest/suppressions/knot_ignore.md index f4bc7612f9c099..7ef72e63b21255 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/suppressions/knot_ignore.md +++ b/crates/red_knot_python_semantic/resources/mdtest/suppressions/knot_ignore.md @@ -180,3 +180,12 @@ a = 4 / 0 # error: [division-by-zero] # error: [unknown-rule] "Unknown rule `is-equal-14`" a = 10 + 4 # knot: ignore[is-equal-14] ``` + + +## Code with `lint:` prefix + +```py +# error:[unknown-rule] "Unknown rule `lint:division-by-zero`. Did you mean `division-by-zero`?" +# error: [division-by-zero] +a = 10 / 0 # knot: ignore[lint:division-by-zero] +``` diff --git a/crates/red_knot_python_semantic/src/lint.rs b/crates/red_knot_python_semantic/src/lint.rs index 56280363c4be7f..8b18558abbb005 100644 --- a/crates/red_knot_python_semantic/src/lint.rs +++ b/crates/red_knot_python_semantic/src/lint.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use ruff_db::diagnostic::{LintName, Severity}; +use ruff_db::diagnostic::{DiagnosticId, LintName, Severity}; use rustc_hash::FxHashMap; use std::hash::Hasher; use thiserror::Error; @@ -345,7 +345,18 @@ impl LintRegistry { } } Some(LintEntry::Removed(lint)) => Err(GetLintError::Removed(lint.name())), - None => Err(GetLintError::Unknown(code.to_string())), + None => { + if let Some(without_prefix) = DiagnosticId::strip_category(code) { + if let Some(entry) = self.by_name.get(without_prefix) { + return Err(GetLintError::PrefixedWithCategory { + prefixed: code.to_string(), + suggestion: entry.id().name.to_string(), + }); + } + } + + Err(GetLintError::Unknown(code.to_string())) + } } } @@ -382,12 +393,20 @@ impl LintRegistry { #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum GetLintError { /// The name maps to this removed lint. - #[error("lint {0} has been removed")] + #[error("lint `{0}` has been removed")] Removed(LintName), /// No lint with the given name is known. - #[error("unknown lint {0}")] + #[error("unknown lint `{0}`")] Unknown(String), + + /// The name uses the full qualified diagnostic id `lint:` instead of just `rule`. + /// The String is the name without the `lint:` category prefix. + #[error("unknown lint `{prefixed}`. Did you mean `{suggestion}`?")] + PrefixedWithCategory { + prefixed: String, + suggestion: String, + }, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -399,6 +418,16 @@ pub enum LintEntry { Alias(LintId), } +impl LintEntry { + fn id(&self) -> LintId { + match self { + LintEntry::Lint(id) => *id, + LintEntry::Removed(id) => *id, + LintEntry::Alias(id) => *id, + } + } +} + impl From<&'static LintMetadata> for LintEntry { fn from(metadata: &'static LintMetadata) -> Self { if metadata.status.is_removed() { diff --git a/crates/red_knot_python_semantic/src/suppression.rs b/crates/red_knot_python_semantic/src/suppression.rs index 6816e07f0965e7..757f9cd248ed9a 100644 --- a/crates/red_knot_python_semantic/src/suppression.rs +++ b/crates/red_knot_python_semantic/src/suppression.rs @@ -163,6 +163,17 @@ fn check_unknown_rule(context: &mut CheckSuppressionsContext) { format_args!("Unknown rule `{rule}`"), ); } + + GetLintError::PrefixedWithCategory { + prefixed, + suggestion, + } => { + context.report_lint( + &UNKNOWN_RULE, + unknown.range, + format_args!("Unknown rule `{prefixed}`. Did you mean `{suggestion}`?"), + ); + } }; } } @@ -765,8 +776,9 @@ impl<'src> SuppressionParser<'src> { fn eat_word(&mut self) -> bool { if self.cursor.eat_if(char::is_alphabetic) { + // Allow `:` for better error recovery when someone uses `lint:code` instead of just `code`. self.cursor - .eat_while(|c| c.is_alphanumeric() || matches!(c, '_' | '-')); + .eat_while(|c| c.is_alphanumeric() || matches!(c, '_' | '-' | ':')); true } else { false diff --git a/crates/ruff_db/src/diagnostic.rs b/crates/ruff_db/src/diagnostic.rs index d71245964c264a..223879cdd08ca5 100644 --- a/crates/ruff_db/src/diagnostic.rs +++ b/crates/ruff_db/src/diagnostic.rs @@ -94,6 +94,10 @@ impl DiagnosticId { matches!(self, DiagnosticId::Lint(self_name) if self_name == name) } + pub fn strip_category(code: &str) -> Option<&str> { + code.split_once(':').map(|(_, rest)| rest) + } + /// Returns `true` if this `DiagnosticId` matches the given name. /// /// ## Examples