mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:42:02 +00:00
[ruff
] Implement a recursive check for RUF060
(#17976)
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> The existing implementation of RUF060 (InEmptyCollection) is not recursive, meaning that although set([]) results in an empty collection, the existing code fails it because set is taking an argument. The updated implementation allows set and frozenset to take empty collection as positional argument (which results in empty set/frozenset). ## Test Plan Added test cases for recursive cases + updated snapshot (see RUF060.py). --------- Co-authored-by: Marcus Näslund <marcus.naslund@kognity.com>
This commit is contained in:
parent
d7ef01401c
commit
b2d9f59937
3 changed files with 59 additions and 9 deletions
|
@ -19,6 +19,9 @@ b'c' in b""
|
|||
b"a" in bytearray()
|
||||
b"a" in bytes()
|
||||
1 in frozenset()
|
||||
1 in set(set())
|
||||
2 in frozenset([])
|
||||
'' in set("")
|
||||
|
||||
# OK
|
||||
1 in [2]
|
||||
|
@ -35,3 +38,7 @@ b'c' in b"x"
|
|||
b"a" in bytearray([2])
|
||||
b"a" in bytes("a", "utf-8")
|
||||
1 in frozenset("c")
|
||||
1 in set(set((1,2)))
|
||||
1 in set(set([1]))
|
||||
'' in {""}
|
||||
frozenset() in {frozenset()}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
@ -48,6 +49,14 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
|||
};
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if is_empty(right, semantic) {
|
||||
checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range()));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let set_methods = ["set", "frozenset"];
|
||||
let collection_methods = [
|
||||
"list",
|
||||
"tuple",
|
||||
|
@ -59,7 +68,7 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
|||
"str",
|
||||
];
|
||||
|
||||
let is_empty_collection = match right {
|
||||
match expr {
|
||||
Expr::List(ast::ExprList { elts, .. }) => elts.is_empty(),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.is_empty(),
|
||||
Expr::Set(ast::ExprSet { elts, .. }) => elts.is_empty(),
|
||||
|
@ -75,15 +84,19 @@ pub(crate) fn in_empty_collection(checker: &Checker, compare: &ast::ExprCompare)
|
|||
arguments,
|
||||
range: _,
|
||||
}) => {
|
||||
arguments.is_empty()
|
||||
&& collection_methods
|
||||
if arguments.is_empty() {
|
||||
collection_methods
|
||||
.iter()
|
||||
.any(|s| semantic.match_builtin_expr(func, s))
|
||||
} else if let Some(arg) = arguments.find_positional(0) {
|
||||
set_methods
|
||||
.iter()
|
||||
.any(|s| semantic.match_builtin_expr(func, s))
|
||||
&& is_empty(arg, semantic)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_empty_collection {
|
||||
checker.report_diagnostic(Diagnostic::new(InEmptyCollection, compare.range()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,6 +187,7 @@ RUF060.py:20:1: RUF060 Unnecessary membership test on empty collection
|
|||
20 | b"a" in bytes()
|
||||
| ^^^^^^^^^^^^^^^ RUF060
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
|
|
||||
|
||||
RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
@ -195,6 +196,35 @@ RUF060.py:21:1: RUF060 Unnecessary membership test on empty collection
|
|||
20 | b"a" in bytes()
|
||||
21 | 1 in frozenset()
|
||||
| ^^^^^^^^^^^^^^^^ RUF060
|
||||
22 |
|
||||
23 | # OK
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
|
|
||||
|
||||
RUF060.py:22:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
20 | b"a" in bytes()
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
| ^^^^^^^^^^^^^^^ RUF060
|
||||
23 | 2 in frozenset([])
|
||||
24 | '' in set("")
|
||||
|
|
||||
|
||||
RUF060.py:23:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
21 | 1 in frozenset()
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF060
|
||||
24 | '' in set("")
|
||||
|
|
||||
|
||||
RUF060.py:24:1: RUF060 Unnecessary membership test on empty collection
|
||||
|
|
||||
22 | 1 in set(set())
|
||||
23 | 2 in frozenset([])
|
||||
24 | '' in set("")
|
||||
| ^^^^^^^^^^^^^ RUF060
|
||||
25 |
|
||||
26 | # OK
|
||||
|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue