diff --git a/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py b/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py new file mode 100644 index 0000000000..1ab940de7b --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/compare_to_empty_string.py @@ -0,0 +1,8 @@ +x = "" +y = "hello" + +if x == "": # [compare-to-empty-string] + print("x is an empty string") + +if y != "": # [compare-to-empty-string] + print("y is not an empty string") diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 359d66df1a..6c0f2ffe0e 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3299,6 +3299,10 @@ where pylint::rules::comparison_of_constant(self, left, ops, comparators); } + if self.settings.rules.enabled(&Rule::CompareToEmptyString) { + pylint::rules::compare_to_empty_string(self, left, ops, comparators); + } + if self.settings.rules.enabled(&Rule::MagicValueComparison) { pylint::rules::magic_value_comparison(self, left, comparators); } diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 454372db3e..c26610bbcb 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -177,6 +177,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "E1142") => Rule::AwaitOutsideAsync, (Pylint, "R0206") => Rule::PropertyWithParameters, (Pylint, "R0402") => Rule::ConsiderUsingFromImport, + (Pylint, "C1901") => Rule::CompareToEmptyString, (Pylint, "R0133") => Rule::ComparisonOfConstant, (Pylint, "R1701") => Rule::ConsiderMergingIsinstance, (Pylint, "R1722") => Rule::ConsiderUsingSysExit, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index eec5f4c4d6..cf7686de81 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -158,6 +158,7 @@ ruff_macros::register_rules!( rules::pylint::rules::PropertyWithParameters, rules::pylint::rules::ReturnInInit, rules::pylint::rules::ConsiderUsingFromImport, + rules::pylint::rules::CompareToEmptyString, rules::pylint::rules::ComparisonOfConstant, rules::pylint::rules::ConsiderMergingIsinstance, rules::pylint::rules::ConsiderUsingSysExit, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 6a65fe3b63..71a923cba4 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -25,6 +25,7 @@ mod tests { #[test_case(Rule::NonlocalWithoutBinding, Path::new("nonlocal_without_binding.py"); "PLE0117")] #[test_case(Rule::UsedPriorGlobalDeclaration, Path::new("used_prior_global_declaration.py"); "PLE0118")] #[test_case(Rule::AwaitOutsideAsync, Path::new("await_outside_async.py"); "PLE1142")] + #[test_case(Rule::CompareToEmptyString, Path::new("compare_to_empty_string.py"); "PLC1901")] #[test_case(Rule::ComparisonOfConstant, Path::new("comparison_of_constant.py"); "PLR0133")] #[test_case(Rule::PropertyWithParameters, Path::new("property_with_parameters.py"); "PLR0206")] #[test_case(Rule::ConsiderUsingFromImport, Path::new("import_aliasing.py"); "PLR0402")] diff --git a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs new file mode 100644 index 0000000000..05dcea5d70 --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs @@ -0,0 +1,42 @@ +use itertools::Itertools; +use ruff_macros::{derive_message_formats, violation}; +use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Located}; + +use crate::ast::helpers::unparse_constant; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +#[violation] +pub struct CompareToEmptyString; + +impl Violation for CompareToEmptyString { + #[derive_message_formats] + fn message(&self) -> String { + format!("todo") + } +} + +pub fn compare_to_empty_string( + checker: &mut Checker, + left: &Expr, + ops: &[Cmpop], + comparators: &[Expr], +) { + for ((left, rhs), op) in std::iter::once(left) + .chain(comparators.iter()) + .tuple_windows::<(&Located<_>, &Located<_>)>() + .zip(ops) + { + if matches!(op, Cmpop::Eq | Cmpop::NotEq) { + if let ExprKind::Constant { value: v, .. } = &rhs.node { + let k = unparse_constant(v, checker.stylist); + if k == "\"\"" || k == "''" { + let diag = Diagnostic::new(CompareToEmptyString {}, Range::from_located(left)); + checker.diagnostics.push(diag); + } + } + } + } +} diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index 9eb81a4395..7efae626c1 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -3,6 +3,7 @@ pub use bad_str_strip_call::{bad_str_strip_call, BadStrStripCall}; pub use bad_string_format_type::{bad_string_format_type, BadStringFormatType}; pub use bidirectional_unicode::{bidirectional_unicode, BidirectionalUnicode}; pub use collapsible_else_if::{collapsible_else_if, CollapsibleElseIf}; +pub use compare_to_empty_string::{compare_to_empty_string, CompareToEmptyString}; pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant}; pub use consider_using_sys_exit::{consider_using_sys_exit, ConsiderUsingSysExit}; pub use global_statement::{global_statement, GlobalStatement}; @@ -36,6 +37,7 @@ mod bad_str_strip_call; mod bad_string_format_type; mod bidirectional_unicode; mod collapsible_else_if; +mod compare_to_empty_string; mod comparison_of_constant; mod consider_using_sys_exit; mod global_statement; diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap new file mode 100644 index 0000000000..a846c5d79d --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC1901_compare_to_empty_string.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + CompareToEmptyString: ~ + location: + row: 4 + column: 3 + end_location: + row: 4 + column: 4 + fix: ~ + parent: ~ +- kind: + CompareToEmptyString: ~ + location: + row: 7 + column: 3 + end_location: + row: 7 + column: 4 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index 531b9c2f8d..04400a4292 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1817,6 +1817,10 @@ "PLC04", "PLC041", "PLC0414", + "PLC1", + "PLC19", + "PLC190", + "PLC1901", "PLC3", "PLC30", "PLC300",