diff --git a/src/noqa.rs b/src/noqa.rs index 3f237d33f8..eb8458ebb8 100644 --- a/src/noqa.rs +++ b/src/noqa.rs @@ -211,6 +211,7 @@ mod tests { use crate::ast::types::Range; use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX}; use crate::registry::Diagnostic; + use crate::rules::pycodestyle::rules::AmbiguousVariableName; use crate::settings::hashable::HashableHashSet; use crate::source_code::LineEnding; use crate::violations; @@ -264,7 +265,7 @@ mod tests { let diagnostics = vec![ Diagnostic::new( - violations::AmbiguousVariableName("x".to_string()), + AmbiguousVariableName("x".to_string()), Range::new(Location::new(1, 0), Location::new(1, 0)), ), Diagnostic::new( @@ -287,7 +288,7 @@ mod tests { let diagnostics = vec![ Diagnostic::new( - violations::AmbiguousVariableName("x".to_string()), + AmbiguousVariableName("x".to_string()), Range::new(Location::new(1, 0), Location::new(1, 0)), ), Diagnostic::new( diff --git a/src/registry.rs b/src/registry.rs index 0016714483..4f4a73730c 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -13,26 +13,26 @@ use crate::{rules, violations}; ruff_macros::define_rule_mapping!( // pycodestyle errors - E101 => violations::MixedSpacesAndTabs, + E101 => rules::pycodestyle::rules::MixedSpacesAndTabs, E401 => violations::MultipleImportsOnOneLine, E402 => violations::ModuleImportNotAtTopOfFile, - E501 => violations::LineTooLong, - E711 => violations::NoneComparison, - E712 => violations::TrueFalseComparison, - E713 => violations::NotInTest, - E714 => violations::NotIsTest, - E721 => violations::TypeComparison, - E722 => violations::DoNotUseBareExcept, - E731 => violations::DoNotAssignLambda, - E741 => violations::AmbiguousVariableName, - E742 => violations::AmbiguousClassName, - E743 => violations::AmbiguousFunctionName, + E501 => rules::pycodestyle::rules::LineTooLong, + E711 => rules::pycodestyle::rules::NoneComparison, + E712 => rules::pycodestyle::rules::TrueFalseComparison, + E713 => rules::pycodestyle::rules::NotInTest, + E714 => rules::pycodestyle::rules::NotIsTest, + E721 => rules::pycodestyle::rules::TypeComparison, + E722 => rules::pycodestyle::rules::DoNotUseBareExcept, + E731 => rules::pycodestyle::rules::DoNotAssignLambda, + E741 => rules::pycodestyle::rules::AmbiguousVariableName, + E742 => rules::pycodestyle::rules::AmbiguousClassName, + E743 => rules::pycodestyle::rules::AmbiguousFunctionName, E902 => violations::IOError, E999 => violations::SyntaxError, // pycodestyle warnings - W292 => violations::NoNewLineAtEndOfFile, - W505 => violations::DocLineTooLong, - W605 => violations::InvalidEscapeSequence, + W292 => rules::pycodestyle::rules::NoNewLineAtEndOfFile, + W505 => rules::pycodestyle::rules::DocLineTooLong, + W605 => rules::pycodestyle::rules::InvalidEscapeSequence, // pyflakes F401 => violations::UnusedImport, F402 => violations::ImportShadowedByLoopVar, diff --git a/src/rules/pycodestyle/rules/ambiguous_class_name.rs b/src/rules/pycodestyle/rules/ambiguous_class_name.rs index 30abfbb5f0..cb743dd8e4 100644 --- a/src/rules/pycodestyle/rules/ambiguous_class_name.rs +++ b/src/rules/pycodestyle/rules/ambiguous_class_name.rs @@ -1,7 +1,21 @@ +use ruff_macros::derive_message_formats; + use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::is_ambiguous_name; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct AmbiguousClassName(pub String); +); +impl Violation for AmbiguousClassName { + #[derive_message_formats] + fn message(&self) -> String { + let AmbiguousClassName(name) = self; + format!("Ambiguous class name: `{name}`") + } +} /// E742 pub fn ambiguous_class_name(name: &str, locate: F) -> Option @@ -10,7 +24,7 @@ where { if is_ambiguous_name(name) { Some(Diagnostic::new( - violations::AmbiguousClassName(name.to_string()), + AmbiguousClassName(name.to_string()), locate(), )) } else { diff --git a/src/rules/pycodestyle/rules/ambiguous_function_name.rs b/src/rules/pycodestyle/rules/ambiguous_function_name.rs index 40909f7d4d..6c1bb9ddc7 100644 --- a/src/rules/pycodestyle/rules/ambiguous_function_name.rs +++ b/src/rules/pycodestyle/rules/ambiguous_function_name.rs @@ -1,7 +1,21 @@ +use ruff_macros::derive_message_formats; + use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::is_ambiguous_name; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct AmbiguousFunctionName(pub String); +); +impl Violation for AmbiguousFunctionName { + #[derive_message_formats] + fn message(&self) -> String { + let AmbiguousFunctionName(name) = self; + format!("Ambiguous function name: `{name}`") + } +} /// E743 pub fn ambiguous_function_name(name: &str, locate: F) -> Option @@ -10,7 +24,7 @@ where { if is_ambiguous_name(name) { Some(Diagnostic::new( - violations::AmbiguousFunctionName(name.to_string()), + AmbiguousFunctionName(name.to_string()), locate(), )) } else { diff --git a/src/rules/pycodestyle/rules/ambiguous_variable_name.rs b/src/rules/pycodestyle/rules/ambiguous_variable_name.rs index 9aeb5c55cc..d90a4cba41 100644 --- a/src/rules/pycodestyle/rules/ambiguous_variable_name.rs +++ b/src/rules/pycodestyle/rules/ambiguous_variable_name.rs @@ -1,13 +1,27 @@ +use ruff_macros::derive_message_formats; + use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::is_ambiguous_name; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct AmbiguousVariableName(pub String); +); +impl Violation for AmbiguousVariableName { + #[derive_message_formats] + fn message(&self) -> String { + let AmbiguousVariableName(name) = self; + format!("Ambiguous variable name: `{name}`") + } +} /// E741 pub fn ambiguous_variable_name(name: &str, range: Range) -> Option { if is_ambiguous_name(name) { Some(Diagnostic::new( - violations::AmbiguousVariableName(name.to_string()), + AmbiguousVariableName(name.to_string()), range, )) } else { diff --git a/src/rules/pycodestyle/rules/do_not_assign_lambda.rs b/src/rules/pycodestyle/rules/do_not_assign_lambda.rs index 89c43e3c4b..1fa4744046 100644 --- a/src/rules/pycodestyle/rules/do_not_assign_lambda.rs +++ b/src/rules/pycodestyle/rules/do_not_assign_lambda.rs @@ -1,3 +1,4 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::{Arguments, Location, Stmt, StmtKind}; use rustpython_parser::ast::{Expr, ExprKind}; @@ -5,19 +6,33 @@ use crate::ast::helpers::{match_leading_content, match_trailing_content, unparse use crate::ast::types::Range; use crate::ast::whitespace::leading_space; use crate::checkers::ast::Checker; +use crate::define_violation; use crate::fix::Fix; use crate::registry::Diagnostic; use crate::source_code::Stylist; -use crate::violations; +use crate::violation::AlwaysAutofixableViolation; + +define_violation!( + pub struct DoNotAssignLambda(pub String); +); +impl AlwaysAutofixableViolation for DoNotAssignLambda { + #[derive_message_formats] + fn message(&self) -> String { + format!("Do not assign a `lambda` expression, use a `def`") + } + + fn autofix_title(&self) -> String { + let DoNotAssignLambda(name) = self; + format!("Rewrite `{name}` as a `def`") + } +} /// E731 pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr, stmt: &Stmt) { if let ExprKind::Name { id, .. } = &target.node { if let ExprKind::Lambda { args, body } = &value.node { - let mut diagnostic = Diagnostic::new( - violations::DoNotAssignLambda(id.to_string()), - Range::from_located(stmt), - ); + let mut diagnostic = + Diagnostic::new(DoNotAssignLambda(id.to_string()), Range::from_located(stmt)); if checker.patch(diagnostic.kind.rule()) { if !match_leading_content(stmt, checker.locator) && !match_trailing_content(stmt, checker.locator) diff --git a/src/rules/pycodestyle/rules/do_not_use_bare_except.rs b/src/rules/pycodestyle/rules/do_not_use_bare_except.rs index 019dfd9f2e..2af6423515 100644 --- a/src/rules/pycodestyle/rules/do_not_use_bare_except.rs +++ b/src/rules/pycodestyle/rules/do_not_use_bare_except.rs @@ -1,10 +1,22 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::{Excepthandler, Stmt, StmtKind}; use rustpython_parser::ast::Expr; use crate::ast::helpers::except_range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::source_code::Locator; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct DoNotUseBareExcept; +); +impl Violation for DoNotUseBareExcept { + #[derive_message_formats] + fn message(&self) -> String { + format!("Do not use bare `except`") + } +} /// E722 pub fn do_not_use_bare_except( @@ -19,7 +31,7 @@ pub fn do_not_use_bare_except( .any(|stmt| matches!(stmt.node, StmtKind::Raise { exc: None, .. })) { Some(Diagnostic::new( - violations::DoNotUseBareExcept, + DoNotUseBareExcept, except_range(handler, locator), )) } else { diff --git a/src/rules/pycodestyle/rules/doc_line_too_long.rs b/src/rules/pycodestyle/rules/doc_line_too_long.rs index 28fa277001..1ac468d42f 100644 --- a/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -1,10 +1,23 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Location; use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::is_overlong; use crate::settings::Settings; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct DocLineTooLong(pub usize, pub usize); +); +impl Violation for DocLineTooLong { + #[derive_message_formats] + fn message(&self) -> String { + let DocLineTooLong(length, limit) = self; + format!("Doc line too long ({length} > {limit} characters)") + } +} /// W505 pub fn doc_line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option { @@ -21,7 +34,7 @@ pub fn doc_line_too_long(lineno: usize, line: &str, settings: &Settings) -> Opti &settings.task_tags, ) { Some(Diagnostic::new( - violations::DocLineTooLong(line_length, limit), + DocLineTooLong(line_length, limit), Range::new( Location::new(lineno + 1, limit), Location::new(lineno + 1, line_length), diff --git a/src/rules/pycodestyle/rules/invalid_escape_sequence.rs b/src/rules/pycodestyle/rules/invalid_escape_sequence.rs index 2152a27405..04ebf43f6b 100644 --- a/src/rules/pycodestyle/rules/invalid_escape_sequence.rs +++ b/src/rules/pycodestyle/rules/invalid_escape_sequence.rs @@ -1,10 +1,27 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Location; use crate::ast::types::Range; +use crate::define_violation; use crate::fix::Fix; use crate::registry::Diagnostic; use crate::source_code::Locator; -use crate::violations; +use crate::violation::AlwaysAutofixableViolation; + +define_violation!( + pub struct InvalidEscapeSequence(pub char); +); +impl AlwaysAutofixableViolation for InvalidEscapeSequence { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidEscapeSequence(char) = self; + format!("Invalid escape sequence: '\\{char}'") + } + + fn autofix_title(&self) -> String { + "Add backslash to escape sequence".to_string() + } +} // See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals const VALID_ESCAPE_SEQUENCES: &[char; 23] = &[ @@ -75,7 +92,7 @@ pub fn invalid_escape_sequence( let location = Location::new(start.row() + row_offset, col); let end_location = Location::new(location.row(), location.column() + 2); let mut diagnostic = Diagnostic::new( - violations::InvalidEscapeSequence(next_char), + InvalidEscapeSequence(next_char), Range::new(location, end_location), ); if autofix { diff --git a/src/rules/pycodestyle/rules/line_too_long.rs b/src/rules/pycodestyle/rules/line_too_long.rs index ff5d99e699..369a217eda 100644 --- a/src/rules/pycodestyle/rules/line_too_long.rs +++ b/src/rules/pycodestyle/rules/line_too_long.rs @@ -1,10 +1,23 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Location; use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::is_overlong; use crate::settings::Settings; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct LineTooLong(pub usize, pub usize); +); +impl Violation for LineTooLong { + #[derive_message_formats] + fn message(&self) -> String { + let LineTooLong(length, limit) = self; + format!("Line too long ({length} > {limit} characters)") + } +} /// E501 pub fn line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option { @@ -18,7 +31,7 @@ pub fn line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option for EqCmpop { + fn from(cmpop: &Cmpop) -> Self { + match cmpop { + Cmpop::Eq => EqCmpop::Eq, + Cmpop::NotEq => EqCmpop::NotEq, + _ => unreachable!("Expected Cmpop::Eq | Cmpop::NotEq"), + } + } +} + +define_violation!( + pub struct NoneComparison(pub EqCmpop); +); +impl AlwaysAutofixableViolation for NoneComparison { + #[derive_message_formats] + fn message(&self) -> String { + let NoneComparison(op) = self; + match op { + EqCmpop::Eq => format!("Comparison to `None` should be `cond is None`"), + EqCmpop::NotEq => format!("Comparison to `None` should be `cond is not None`"), + } + } + + fn autofix_title(&self) -> String { + let NoneComparison(op) = self; + match op { + EqCmpop::Eq => "Replace with `cond is None`".to_string(), + EqCmpop::NotEq => "Replace with `cond is not None`".to_string(), + } + } +} + +define_violation!( + pub struct TrueFalseComparison(pub bool, pub EqCmpop); +); +impl AlwaysAutofixableViolation for TrueFalseComparison { + #[derive_message_formats] + fn message(&self) -> String { + let TrueFalseComparison(value, op) = self; + match (value, op) { + (true, EqCmpop::Eq) => format!("Comparison to `True` should be `cond is True`"), + (true, EqCmpop::NotEq) => { + format!("Comparison to `True` should be `cond is not True`") + } + (false, EqCmpop::Eq) => format!("Comparison to `False` should be `cond is False`"), + (false, EqCmpop::NotEq) => { + format!("Comparison to `False` should be `cond is not False`") + } + } + } + + fn autofix_title(&self) -> String { + let TrueFalseComparison(value, op) = self; + match (value, op) { + (true, EqCmpop::Eq) => "Replace with `cond is True`".to_string(), + (true, EqCmpop::NotEq) => "Replace with `cond is not True`".to_string(), + (false, EqCmpop::Eq) => "Replace with `cond is False`".to_string(), + (false, EqCmpop::NotEq) => "Replace with `cond is not False`".to_string(), + } + } +} /// E711, E712 pub fn literal_comparisons( @@ -43,20 +114,16 @@ pub fn literal_comparisons( ) { if matches!(op, Cmpop::Eq) { - let diagnostic = Diagnostic::new( - violations::NoneComparison(op.into()), - Range::from_located(comparator), - ); + let diagnostic = + Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator)); if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(next) { bad_ops.insert(0, Cmpop::Is); } diagnostics.push(diagnostic); } if matches!(op, Cmpop::NotEq) { - let diagnostic = Diagnostic::new( - violations::NoneComparison(op.into()), - Range::from_located(comparator), - ); + let diagnostic = + Diagnostic::new(NoneComparison(op.into()), Range::from_located(comparator)); if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(next) { bad_ops.insert(0, Cmpop::IsNot); } @@ -72,7 +139,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let diagnostic = Diagnostic::new( - violations::TrueFalseComparison(value, op.into()), + TrueFalseComparison(value, op.into()), Range::from_located(comparator), ); if checker.patch(diagnostic.kind.rule()) @@ -84,7 +151,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let diagnostic = Diagnostic::new( - violations::TrueFalseComparison(value, op.into()), + TrueFalseComparison(value, op.into()), Range::from_located(comparator), ); if checker.patch(diagnostic.kind.rule()) @@ -109,10 +176,8 @@ pub fn literal_comparisons( ) { if matches!(op, Cmpop::Eq) { - let diagnostic = Diagnostic::new( - violations::NoneComparison(op.into()), - Range::from_located(next), - ); + let diagnostic = + Diagnostic::new(NoneComparison(op.into()), Range::from_located(next)); if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(comparator) { @@ -121,10 +186,8 @@ pub fn literal_comparisons( diagnostics.push(diagnostic); } if matches!(op, Cmpop::NotEq) { - let diagnostic = Diagnostic::new( - violations::NoneComparison(op.into()), - Range::from_located(next), - ); + let diagnostic = + Diagnostic::new(NoneComparison(op.into()), Range::from_located(next)); if checker.patch(diagnostic.kind.rule()) && !helpers::is_constant_non_singleton(comparator) { @@ -142,7 +205,7 @@ pub fn literal_comparisons( { if matches!(op, Cmpop::Eq) { let diagnostic = Diagnostic::new( - violations::TrueFalseComparison(value, op.into()), + TrueFalseComparison(value, op.into()), Range::from_located(next), ); if checker.patch(diagnostic.kind.rule()) @@ -154,7 +217,7 @@ pub fn literal_comparisons( } if matches!(op, Cmpop::NotEq) { let diagnostic = Diagnostic::new( - violations::TrueFalseComparison(value, op.into()), + TrueFalseComparison(value, op.into()), Range::from_located(next), ); if checker.patch(diagnostic.kind.rule()) diff --git a/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs b/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs index 28393a3004..1ddcc06c54 100644 --- a/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs +++ b/src/rules/pycodestyle/rules/mixed_spaces_and_tabs.rs @@ -1,9 +1,21 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Location; use crate::ast::types::Range; use crate::ast::whitespace::leading_space; +use crate::define_violation; use crate::registry::Diagnostic; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct MixedSpacesAndTabs; +); +impl Violation for MixedSpacesAndTabs { + #[derive_message_formats] + fn message(&self) -> String { + format!("Indentation contains mixed spaces and tabs") + } +} /// E101 pub fn mixed_spaces_and_tabs(lineno: usize, line: &str) -> Option { @@ -11,7 +23,7 @@ pub fn mixed_spaces_and_tabs(lineno: usize, line: &str) -> Option { if indent.contains(' ') && indent.contains('\t') { Some(Diagnostic::new( - violations::MixedSpacesAndTabs, + MixedSpacesAndTabs, Range::new( Location::new(lineno + 1, 0), Location::new(lineno + 1, indent.chars().count()), diff --git a/src/rules/pycodestyle/rules/mod.rs b/src/rules/pycodestyle/rules/mod.rs index 08ea0c6d54..7cda636d1a 100644 --- a/src/rules/pycodestyle/rules/mod.rs +++ b/src/rules/pycodestyle/rules/mod.rs @@ -1,16 +1,16 @@ -pub use ambiguous_class_name::ambiguous_class_name; -pub use ambiguous_function_name::ambiguous_function_name; -pub use ambiguous_variable_name::ambiguous_variable_name; -pub use do_not_assign_lambda::do_not_assign_lambda; -pub use do_not_use_bare_except::do_not_use_bare_except; -pub use doc_line_too_long::doc_line_too_long; -pub use invalid_escape_sequence::invalid_escape_sequence; -pub use line_too_long::line_too_long; -pub use literal_comparisons::literal_comparisons; -pub use mixed_spaces_and_tabs::mixed_spaces_and_tabs; -pub use no_newline_at_end_of_file::no_newline_at_end_of_file; -pub use not_tests::not_tests; -pub use type_comparison::type_comparison; +pub use ambiguous_class_name::{ambiguous_class_name, AmbiguousClassName}; +pub use ambiguous_function_name::{ambiguous_function_name, AmbiguousFunctionName}; +pub use ambiguous_variable_name::{ambiguous_variable_name, AmbiguousVariableName}; +pub use do_not_assign_lambda::{do_not_assign_lambda, DoNotAssignLambda}; +pub use do_not_use_bare_except::{do_not_use_bare_except, DoNotUseBareExcept}; +pub use doc_line_too_long::{doc_line_too_long, DocLineTooLong}; +pub use invalid_escape_sequence::{invalid_escape_sequence, InvalidEscapeSequence}; +pub use line_too_long::{line_too_long, LineTooLong}; +pub use literal_comparisons::{literal_comparisons, NoneComparison, TrueFalseComparison}; +pub use mixed_spaces_and_tabs::{mixed_spaces_and_tabs, MixedSpacesAndTabs}; +pub use no_newline_at_end_of_file::{no_newline_at_end_of_file, NoNewLineAtEndOfFile}; +pub use not_tests::{not_tests, NotInTest, NotIsTest}; +pub use type_comparison::{type_comparison, TypeComparison}; mod ambiguous_class_name; mod ambiguous_function_name; diff --git a/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs b/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs index 8baaf9896c..74626be2e7 100644 --- a/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs +++ b/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs @@ -1,10 +1,26 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Location; use crate::ast::types::Range; +use crate::define_violation; use crate::fix::Fix; use crate::registry::Diagnostic; use crate::source_code::Stylist; -use crate::violations; +use crate::violation::AlwaysAutofixableViolation; + +define_violation!( + pub struct NoNewLineAtEndOfFile; +); +impl AlwaysAutofixableViolation for NoNewLineAtEndOfFile { + #[derive_message_formats] + fn message(&self) -> String { + format!("No newline at end of file") + } + + fn autofix_title(&self) -> String { + "Add trailing newline".to_string() + } +} /// W292 pub fn no_newline_at_end_of_file( @@ -18,10 +34,8 @@ pub fn no_newline_at_end_of_file( if let Some(line) = contents.lines().last() { // Both locations are at the end of the file (and thus the same). let location = Location::new(contents.lines().count(), line.len()); - let mut diagnostic = Diagnostic::new( - violations::NoNewLineAtEndOfFile, - Range::new(location, location), - ); + let mut diagnostic = + Diagnostic::new(NoNewLineAtEndOfFile, Range::new(location, location)); if autofix { diagnostic.amend(Fix::insertion(stylist.line_ending().to_string(), location)); } diff --git a/src/rules/pycodestyle/rules/not_tests.rs b/src/rules/pycodestyle/rules/not_tests.rs index f36a08914a..afd0991f0d 100644 --- a/src/rules/pycodestyle/rules/not_tests.rs +++ b/src/rules/pycodestyle/rules/not_tests.rs @@ -1,12 +1,42 @@ +use ruff_macros::derive_message_formats; use rustpython_ast::Unaryop; use rustpython_parser::ast::{Cmpop, Expr, ExprKind}; use crate::ast::types::Range; use crate::checkers::ast::Checker; +use crate::define_violation; use crate::fix::Fix; use crate::registry::Diagnostic; use crate::rules::pycodestyle::helpers::compare; -use crate::violations; +use crate::violation::AlwaysAutofixableViolation; + +define_violation!( + pub struct NotInTest; +); +impl AlwaysAutofixableViolation for NotInTest { + #[derive_message_formats] + fn message(&self) -> String { + format!("Test for membership should be `not in`") + } + + fn autofix_title(&self) -> String { + "Convert to `not in`".to_string() + } +} + +define_violation!( + pub struct NotIsTest; +); +impl AlwaysAutofixableViolation for NotIsTest { + #[derive_message_formats] + fn message(&self) -> String { + format!("Test for object identity should be `is not`") + } + + fn autofix_title(&self) -> String { + "Convert to `is not`".to_string() + } +} /// E713, E714 pub fn not_tests( @@ -30,10 +60,8 @@ pub fn not_tests( match op { Cmpop::In => { if check_not_in { - let mut diagnostic = Diagnostic::new( - violations::NotInTest, - Range::from_located(operand), - ); + let mut diagnostic = + Diagnostic::new(NotInTest, Range::from_located(operand)); if checker.patch(diagnostic.kind.rule()) && should_fix { diagnostic.amend(Fix::replacement( compare(left, &[Cmpop::NotIn], comparators, checker.stylist), @@ -46,10 +74,8 @@ pub fn not_tests( } Cmpop::Is => { if check_not_is { - let mut diagnostic = Diagnostic::new( - violations::NotIsTest, - Range::from_located(operand), - ); + let mut diagnostic = + Diagnostic::new(NotIsTest, Range::from_located(operand)); if checker.patch(diagnostic.kind.rule()) && should_fix { diagnostic.amend(Fix::replacement( compare(left, &[Cmpop::IsNot], comparators, checker.stylist), diff --git a/src/rules/pycodestyle/rules/type_comparison.rs b/src/rules/pycodestyle/rules/type_comparison.rs index e731dbb454..497dffa7a4 100644 --- a/src/rules/pycodestyle/rules/type_comparison.rs +++ b/src/rules/pycodestyle/rules/type_comparison.rs @@ -1,10 +1,22 @@ use itertools::izip; +use ruff_macros::derive_message_formats; use rustpython_ast::Constant; use rustpython_parser::ast::{Cmpop, Expr, ExprKind}; use crate::ast::types::Range; +use crate::define_violation; use crate::registry::Diagnostic; -use crate::violations; +use crate::violation::Violation; + +define_violation!( + pub struct TypeComparison; +); +impl Violation for TypeComparison { + #[derive_message_formats] + fn message(&self) -> String { + format!("Do not compare types, use `isinstance()`") + } +} /// E721 pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec { @@ -29,8 +41,7 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> kind: None } ) { - diagnostics - .push(Diagnostic::new(violations::TypeComparison, location)); + diagnostics.push(Diagnostic::new(TypeComparison, location)); } } } @@ -40,7 +51,7 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> if let ExprKind::Name { id, .. } = &value.node { // Ex) types.IntType if id == "types" { - diagnostics.push(Diagnostic::new(violations::TypeComparison, location)); + diagnostics.push(Diagnostic::new(TypeComparison, location)); } } } diff --git a/src/violations.rs b/src/violations.rs index 7de190760c..082598a188 100644 --- a/src/violations.rs +++ b/src/violations.rs @@ -37,191 +37,6 @@ impl Violation for ModuleImportNotAtTopOfFile { } } -define_violation!( - pub struct LineTooLong(pub usize, pub usize); -); -impl Violation for LineTooLong { - #[derive_message_formats] - fn message(&self) -> String { - let LineTooLong(length, limit) = self; - format!("Line too long ({length} > {limit} characters)") - } -} - -define_violation!( - pub struct MixedSpacesAndTabs; -); -impl Violation for MixedSpacesAndTabs { - #[derive_message_formats] - fn message(&self) -> String { - format!("Indentation contains mixed spaces and tabs") - } -} - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum EqCmpop { - Eq, - NotEq, -} - -impl From<&Cmpop> for EqCmpop { - fn from(cmpop: &Cmpop) -> Self { - match cmpop { - Cmpop::Eq => EqCmpop::Eq, - Cmpop::NotEq => EqCmpop::NotEq, - _ => unreachable!("Expected Cmpop::Eq | Cmpop::NotEq"), - } - } -} - -define_violation!( - pub struct NoneComparison(pub EqCmpop); -); -impl AlwaysAutofixableViolation for NoneComparison { - #[derive_message_formats] - fn message(&self) -> String { - let NoneComparison(op) = self; - match op { - EqCmpop::Eq => format!("Comparison to `None` should be `cond is None`"), - EqCmpop::NotEq => format!("Comparison to `None` should be `cond is not None`"), - } - } - - fn autofix_title(&self) -> String { - let NoneComparison(op) = self; - match op { - EqCmpop::Eq => "Replace with `cond is None`".to_string(), - EqCmpop::NotEq => "Replace with `cond is not None`".to_string(), - } - } -} - -define_violation!( - pub struct TrueFalseComparison(pub bool, pub EqCmpop); -); -impl AlwaysAutofixableViolation for TrueFalseComparison { - #[derive_message_formats] - fn message(&self) -> String { - let TrueFalseComparison(value, op) = self; - match (value, op) { - (true, EqCmpop::Eq) => format!("Comparison to `True` should be `cond is True`"), - (true, EqCmpop::NotEq) => { - format!("Comparison to `True` should be `cond is not True`") - } - (false, EqCmpop::Eq) => format!("Comparison to `False` should be `cond is False`"), - (false, EqCmpop::NotEq) => { - format!("Comparison to `False` should be `cond is not False`") - } - } - } - - fn autofix_title(&self) -> String { - let TrueFalseComparison(value, op) = self; - match (value, op) { - (true, EqCmpop::Eq) => "Replace with `cond is True`".to_string(), - (true, EqCmpop::NotEq) => "Replace with `cond is not True`".to_string(), - (false, EqCmpop::Eq) => "Replace with `cond is False`".to_string(), - (false, EqCmpop::NotEq) => "Replace with `cond is not False`".to_string(), - } - } -} - -define_violation!( - pub struct NotInTest; -); -impl AlwaysAutofixableViolation for NotInTest { - #[derive_message_formats] - fn message(&self) -> String { - format!("Test for membership should be `not in`") - } - - fn autofix_title(&self) -> String { - "Convert to `not in`".to_string() - } -} - -define_violation!( - pub struct NotIsTest; -); -impl AlwaysAutofixableViolation for NotIsTest { - #[derive_message_formats] - fn message(&self) -> String { - format!("Test for object identity should be `is not`") - } - - fn autofix_title(&self) -> String { - "Convert to `is not`".to_string() - } -} - -define_violation!( - pub struct TypeComparison; -); -impl Violation for TypeComparison { - #[derive_message_formats] - fn message(&self) -> String { - format!("Do not compare types, use `isinstance()`") - } -} - -define_violation!( - pub struct DoNotUseBareExcept; -); -impl Violation for DoNotUseBareExcept { - #[derive_message_formats] - fn message(&self) -> String { - format!("Do not use bare `except`") - } -} - -define_violation!( - pub struct DoNotAssignLambda(pub String); -); -impl AlwaysAutofixableViolation for DoNotAssignLambda { - #[derive_message_formats] - fn message(&self) -> String { - format!("Do not assign a `lambda` expression, use a `def`") - } - - fn autofix_title(&self) -> String { - let DoNotAssignLambda(name) = self; - format!("Rewrite `{name}` as a `def`") - } -} - -define_violation!( - pub struct AmbiguousVariableName(pub String); -); -impl Violation for AmbiguousVariableName { - #[derive_message_formats] - fn message(&self) -> String { - let AmbiguousVariableName(name) = self; - format!("Ambiguous variable name: `{name}`") - } -} - -define_violation!( - pub struct AmbiguousClassName(pub String); -); -impl Violation for AmbiguousClassName { - #[derive_message_formats] - fn message(&self) -> String { - let AmbiguousClassName(name) = self; - format!("Ambiguous class name: `{name}`") - } -} - -define_violation!( - pub struct AmbiguousFunctionName(pub String); -); -impl Violation for AmbiguousFunctionName { - #[derive_message_formats] - fn message(&self) -> String { - let AmbiguousFunctionName(name) = self; - format!("Ambiguous function name: `{name}`") - } -} - define_violation!( pub struct IOError(pub String); ); @@ -244,48 +59,6 @@ impl Violation for SyntaxError { } } -// pycodestyle warnings - -define_violation!( - pub struct NoNewLineAtEndOfFile; -); -impl AlwaysAutofixableViolation for NoNewLineAtEndOfFile { - #[derive_message_formats] - fn message(&self) -> String { - format!("No newline at end of file") - } - - fn autofix_title(&self) -> String { - "Add trailing newline".to_string() - } -} - -define_violation!( - pub struct InvalidEscapeSequence(pub char); -); -impl AlwaysAutofixableViolation for InvalidEscapeSequence { - #[derive_message_formats] - fn message(&self) -> String { - let InvalidEscapeSequence(char) = self; - format!("Invalid escape sequence: '\\{char}'") - } - - fn autofix_title(&self) -> String { - "Add backslash to escape sequence".to_string() - } -} - -define_violation!( - pub struct DocLineTooLong(pub usize, pub usize); -); -impl Violation for DocLineTooLong { - #[derive_message_formats] - fn message(&self) -> String { - let DocLineTooLong(length, limit) = self; - format!("Doc line too long ({length} > {limit} characters)") - } -} - // pyflakes define_violation!(