mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[refurb
] Add coverage of set
and frozenset
calls (FURB171
) (#18035)
## Summary Adds coverage of using set(...) in addition to `{...} in SingleItemMembershipTest. Fixes #15792 (and replaces the old PR #15793) <!-- What's the purpose of the change? What does it do, and why? --> ## Test Plan Updated unit test and snapshot. Steps to reproduce are in the issue linked above. <!-- How was it tested? -->
This commit is contained in:
parent
7df79cfb70
commit
9d3cad95bc
6 changed files with 232 additions and 17 deletions
53
crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py
vendored
Normal file
53
crates/ruff_linter/resources/test/fixtures/refurb/FURB171_1.py
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Errors.
|
||||||
|
|
||||||
|
if 1 in set([1]):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in set((1,)):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in set({1}):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in frozenset([1]):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in frozenset((1,)):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in frozenset({1}):
|
||||||
|
print("Single-element set")
|
||||||
|
|
||||||
|
if 1 in set(set([1])):
|
||||||
|
print('Recursive solution')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Non-errors.
|
||||||
|
|
||||||
|
if 1 in set((1, 2)):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in set([1, 2]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in set({1, 2}):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in frozenset((1, 2)):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in frozenset([1, 2]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in frozenset({1, 2}):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in set(1,):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in set(1,2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 1 in set((x for x in range(2))):
|
||||||
|
pass
|
|
@ -35,7 +35,8 @@ mod tests {
|
||||||
#[test_case(Rule::UnnecessaryFromFloat, Path::new("FURB164.py"))]
|
#[test_case(Rule::UnnecessaryFromFloat, Path::new("FURB164.py"))]
|
||||||
#[test_case(Rule::PrintEmptyString, Path::new("FURB105.py"))]
|
#[test_case(Rule::PrintEmptyString, Path::new("FURB105.py"))]
|
||||||
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
|
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
|
||||||
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))]
|
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171_0.py"))]
|
||||||
|
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171_1.py"))]
|
||||||
#[test_case(Rule::BitCount, Path::new("FURB161.py"))]
|
#[test_case(Rule::BitCount, Path::new("FURB161.py"))]
|
||||||
#[test_case(Rule::IntOnSlicedStr, Path::new("FURB166.py"))]
|
#[test_case(Rule::IntOnSlicedStr, Path::new("FURB166.py"))]
|
||||||
#[test_case(Rule::RegexFlagAlias, Path::new("FURB167.py"))]
|
#[test_case(Rule::RegexFlagAlias, Path::new("FURB167.py"))]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast::helpers::generate_comparison;
|
use ruff_python_ast::helpers::generate_comparison;
|
||||||
use ruff_python_ast::{self as ast, CmpOp, Expr, ExprStringLiteral};
|
use ruff_python_ast::{self as ast, CmpOp, Expr, ExprStringLiteral};
|
||||||
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -78,8 +79,8 @@ pub(crate) fn single_item_membership_test(
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the right-hand side is a single-item object.
|
// Check if the right-hand side is a single-item object
|
||||||
let Some(item) = single_item(right) else {
|
let Some(item) = single_item(right, checker.semantic()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,7 +116,7 @@ pub(crate) fn single_item_membership_test(
|
||||||
|
|
||||||
/// Return the single item wrapped in `Some` if the expression contains a single
|
/// Return the single item wrapped in `Some` if the expression contains a single
|
||||||
/// item, otherwise return `None`.
|
/// item, otherwise return `None`.
|
||||||
fn single_item(expr: &Expr) -> Option<&Expr> {
|
fn single_item<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a Expr> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::List(ast::ExprList { elts, .. })
|
Expr::List(ast::ExprList { elts, .. })
|
||||||
| Expr::Tuple(ast::ExprTuple { elts, .. })
|
| Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||||
|
@ -124,6 +125,19 @@ fn single_item(expr: &Expr) -> Option<&Expr> {
|
||||||
[item] => Some(item),
|
[item] => Some(item),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
Expr::Call(ast::ExprCall {
|
||||||
|
func,
|
||||||
|
arguments,
|
||||||
|
range: _,
|
||||||
|
}) => {
|
||||||
|
if arguments.len() != 1 || !is_set_method(func, semantic) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments
|
||||||
|
.find_positional(0)
|
||||||
|
.and_then(|arg| single_item(arg, semantic))
|
||||||
|
}
|
||||||
string_expr @ Expr::StringLiteral(ExprStringLiteral { value: string, .. })
|
string_expr @ Expr::StringLiteral(ExprStringLiteral { value: string, .. })
|
||||||
if string.chars().count() == 1 =>
|
if string.chars().count() == 1 =>
|
||||||
{
|
{
|
||||||
|
@ -133,6 +147,12 @@ fn single_item(expr: &Expr) -> Option<&Expr> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_set_method(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
|
["set", "frozenset"]
|
||||||
|
.iter()
|
||||||
|
.any(|s| semantic.match_builtin_expr(func, s))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum MembershipTest {
|
enum MembershipTest {
|
||||||
/// Ex) `1 in [1]`
|
/// Ex) `1 in [1]`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||||
---
|
---
|
||||||
FURB171.py:3:4: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:3:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
1 | # Errors.
|
1 | # Errors.
|
||||||
2 |
|
2 |
|
||||||
|
@ -20,7 +20,7 @@ FURB171.py:3:4: FURB171 [*] Membership test against single-item container
|
||||||
5 5 |
|
5 5 |
|
||||||
6 6 | if 1 in [1]:
|
6 6 | if 1 in [1]:
|
||||||
|
|
||||||
FURB171.py:6:4: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:6:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
4 | print("Single-element tuple")
|
4 | print("Single-element tuple")
|
||||||
5 |
|
5 |
|
||||||
|
@ -40,7 +40,7 @@ FURB171.py:6:4: FURB171 [*] Membership test against single-item container
|
||||||
8 8 |
|
8 8 |
|
||||||
9 9 | if 1 in {1}:
|
9 9 | if 1 in {1}:
|
||||||
|
|
||||||
FURB171.py:9:4: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:9:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
7 | print("Single-element list")
|
7 | print("Single-element list")
|
||||||
8 |
|
8 |
|
||||||
|
@ -60,7 +60,7 @@ FURB171.py:9:4: FURB171 [*] Membership test against single-item container
|
||||||
11 11 |
|
11 11 |
|
||||||
12 12 | if "a" in "a":
|
12 12 | if "a" in "a":
|
||||||
|
|
||||||
FURB171.py:12:4: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:12:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
10 | print("Single-element set")
|
10 | print("Single-element set")
|
||||||
11 |
|
11 |
|
||||||
|
@ -80,7 +80,7 @@ FURB171.py:12:4: FURB171 [*] Membership test against single-item container
|
||||||
14 14 |
|
14 14 |
|
||||||
15 15 | if 1 not in (1,):
|
15 15 | if 1 not in (1,):
|
||||||
|
|
||||||
FURB171.py:15:4: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:15:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
13 | print("Single-element string")
|
13 | print("Single-element string")
|
||||||
14 |
|
14 |
|
||||||
|
@ -100,7 +100,7 @@ FURB171.py:15:4: FURB171 [*] Membership test against single-item container
|
||||||
17 17 |
|
17 17 |
|
||||||
18 18 | if not 1 in (1,):
|
18 18 | if not 1 in (1,):
|
||||||
|
|
||||||
FURB171.py:18:8: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:18:8: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
16 | print("Check `not in` membership test")
|
16 | print("Check `not in` membership test")
|
||||||
17 |
|
17 |
|
||||||
|
@ -120,7 +120,7 @@ FURB171.py:18:8: FURB171 [*] Membership test against single-item container
|
||||||
20 20 |
|
20 20 |
|
||||||
21 21 | # Non-errors.
|
21 21 | # Non-errors.
|
||||||
|
|
||||||
FURB171.py:52:5: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:52:5: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
51 | # https://github.com/astral-sh/ruff/issues/10063
|
51 | # https://github.com/astral-sh/ruff/issues/10063
|
||||||
52 | _ = a in (
|
52 | _ = a in (
|
||||||
|
@ -147,7 +147,7 @@ FURB171.py:52:5: FURB171 [*] Membership test against single-item container
|
||||||
57 54 | _ = a in ( # Foo1
|
57 54 | _ = a in ( # Foo1
|
||||||
58 55 | ( # Foo2
|
58 55 | ( # Foo2
|
||||||
|
|
||||||
FURB171.py:57:5: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:57:5: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
55 | )
|
55 | )
|
||||||
56 |
|
56 |
|
||||||
|
@ -199,7 +199,7 @@ FURB171.py:57:5: FURB171 [*] Membership test against single-item container
|
||||||
74 63 | foo = (
|
74 63 | foo = (
|
||||||
75 64 | lorem()
|
75 64 | lorem()
|
||||||
|
|
||||||
FURB171.py:77:28: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:77:28: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
75 | lorem()
|
75 | lorem()
|
||||||
76 | .ipsum()
|
76 | .ipsum()
|
||||||
|
@ -228,7 +228,7 @@ FURB171.py:77:28: FURB171 [*] Membership test against single-item container
|
||||||
83 79 |
|
83 79 |
|
||||||
84 80 | foo = (
|
84 80 | foo = (
|
||||||
|
|
||||||
FURB171.py:87:28: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:87:28: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
85 | lorem()
|
85 | lorem()
|
||||||
86 | .ipsum()
|
86 | .ipsum()
|
||||||
|
@ -262,7 +262,7 @@ FURB171.py:87:28: FURB171 [*] Membership test against single-item container
|
||||||
95 93 |
|
95 93 |
|
||||||
96 94 | foo = lorem() \
|
96 94 | foo = lorem() \
|
||||||
|
|
||||||
FURB171.py:98:24: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:98:24: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
96 | foo = lorem() \
|
96 | foo = lorem() \
|
||||||
97 | .ipsum() \
|
97 | .ipsum() \
|
||||||
|
@ -292,7 +292,7 @@ FURB171.py:98:24: FURB171 [*] Membership test against single-item container
|
||||||
104 100 | def _():
|
104 100 | def _():
|
||||||
105 101 | if foo not \
|
105 101 | if foo not \
|
||||||
|
|
||||||
FURB171.py:105:8: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:105:8: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
104 | def _():
|
104 | def _():
|
||||||
105 | if foo not \
|
105 | if foo not \
|
||||||
|
@ -323,7 +323,7 @@ FURB171.py:105:8: FURB171 [*] Membership test against single-item container
|
||||||
112 107 | def _():
|
112 107 | def _():
|
||||||
113 108 | if foo not \
|
113 108 | if foo not \
|
||||||
|
|
||||||
FURB171.py:113:8: FURB171 [*] Membership test against single-item container
|
FURB171_0.py:113:8: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
|
||||||
112 | def _():
|
112 | def _():
|
||||||
113 | if foo not \
|
113 | if foo not \
|
|
@ -0,0 +1,141 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||||
|
---
|
||||||
|
FURB171_1.py:3:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
1 | # Errors.
|
||||||
|
2 |
|
||||||
|
3 | if 1 in set([1]):
|
||||||
|
| ^^^^^^^^^^^^^ FURB171
|
||||||
|
4 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
1 1 | # Errors.
|
||||||
|
2 2 |
|
||||||
|
3 |-if 1 in set([1]):
|
||||||
|
3 |+if 1 == 1:
|
||||||
|
4 4 | print("Single-element set")
|
||||||
|
5 5 |
|
||||||
|
6 6 | if 1 in set((1,)):
|
||||||
|
|
||||||
|
FURB171_1.py:6:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
4 | print("Single-element set")
|
||||||
|
5 |
|
||||||
|
6 | if 1 in set((1,)):
|
||||||
|
| ^^^^^^^^^^^^^^ FURB171
|
||||||
|
7 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
3 3 | if 1 in set([1]):
|
||||||
|
4 4 | print("Single-element set")
|
||||||
|
5 5 |
|
||||||
|
6 |-if 1 in set((1,)):
|
||||||
|
6 |+if 1 == 1:
|
||||||
|
7 7 | print("Single-element set")
|
||||||
|
8 8 |
|
||||||
|
9 9 | if 1 in set({1}):
|
||||||
|
|
||||||
|
FURB171_1.py:9:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
7 | print("Single-element set")
|
||||||
|
8 |
|
||||||
|
9 | if 1 in set({1}):
|
||||||
|
| ^^^^^^^^^^^^^ FURB171
|
||||||
|
10 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
6 6 | if 1 in set((1,)):
|
||||||
|
7 7 | print("Single-element set")
|
||||||
|
8 8 |
|
||||||
|
9 |-if 1 in set({1}):
|
||||||
|
9 |+if 1 == 1:
|
||||||
|
10 10 | print("Single-element set")
|
||||||
|
11 11 |
|
||||||
|
12 12 | if 1 in frozenset([1]):
|
||||||
|
|
||||||
|
FURB171_1.py:12:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
10 | print("Single-element set")
|
||||||
|
11 |
|
||||||
|
12 | if 1 in frozenset([1]):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ FURB171
|
||||||
|
13 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
9 9 | if 1 in set({1}):
|
||||||
|
10 10 | print("Single-element set")
|
||||||
|
11 11 |
|
||||||
|
12 |-if 1 in frozenset([1]):
|
||||||
|
12 |+if 1 == 1:
|
||||||
|
13 13 | print("Single-element set")
|
||||||
|
14 14 |
|
||||||
|
15 15 | if 1 in frozenset((1,)):
|
||||||
|
|
||||||
|
FURB171_1.py:15:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
13 | print("Single-element set")
|
||||||
|
14 |
|
||||||
|
15 | if 1 in frozenset((1,)):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^ FURB171
|
||||||
|
16 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
12 12 | if 1 in frozenset([1]):
|
||||||
|
13 13 | print("Single-element set")
|
||||||
|
14 14 |
|
||||||
|
15 |-if 1 in frozenset((1,)):
|
||||||
|
15 |+if 1 == 1:
|
||||||
|
16 16 | print("Single-element set")
|
||||||
|
17 17 |
|
||||||
|
18 18 | if 1 in frozenset({1}):
|
||||||
|
|
||||||
|
FURB171_1.py:18:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
16 | print("Single-element set")
|
||||||
|
17 |
|
||||||
|
18 | if 1 in frozenset({1}):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ FURB171
|
||||||
|
19 | print("Single-element set")
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
15 15 | if 1 in frozenset((1,)):
|
||||||
|
16 16 | print("Single-element set")
|
||||||
|
17 17 |
|
||||||
|
18 |-if 1 in frozenset({1}):
|
||||||
|
18 |+if 1 == 1:
|
||||||
|
19 19 | print("Single-element set")
|
||||||
|
20 20 |
|
||||||
|
21 21 | if 1 in set(set([1])):
|
||||||
|
|
||||||
|
FURB171_1.py:21:4: FURB171 [*] Membership test against single-item container
|
||||||
|
|
|
||||||
|
19 | print("Single-element set")
|
||||||
|
20 |
|
||||||
|
21 | if 1 in set(set([1])):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ FURB171
|
||||||
|
22 | print('Recursive solution')
|
||||||
|
|
|
||||||
|
= help: Convert to equality test
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
18 18 | if 1 in frozenset({1}):
|
||||||
|
19 19 | print("Single-element set")
|
||||||
|
20 20 |
|
||||||
|
21 |-if 1 in set(set([1])):
|
||||||
|
21 |+if 1 == 1:
|
||||||
|
22 22 | print('Recursive solution')
|
||||||
|
23 23 |
|
||||||
|
24 24 |
|
Loading…
Add table
Add a link
Reference in a new issue