-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[refurb] - implement
FURB161
/use-bit-count
- Loading branch information
1 parent
20def33
commit 35d578f
Showing
8 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
crates/ruff_linter/resources/test/fixtures/refurb/FURB161.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
x = 10 | ||
|
||
def ten() -> int: | ||
return 10 | ||
|
||
count = bin(x).count("1") # FURB161 | ||
count = bin(10).count("1") # FURB161 | ||
count = bin(0b1010).count("1") # FURB161 | ||
count = bin(0xA).count("1") # FURB161 | ||
count = bin(0o12).count("1") # FURB161 | ||
count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
count = bin(ten()).count("1") # FURB161 | ||
|
||
count = x.bit_count() # OK | ||
count = (10).bit_count() # OK | ||
count = 0b1010.bit_count() # OK | ||
count = 0xA.bit_count() # OK | ||
count = 0o12.bit_count() # OK | ||
count = (0x10 + 0x1000).bit_count() # OK | ||
count = ten().bit_count() # OK |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall}; | ||
use ruff_text_size::{Ranged, TextRange, TextSize}; | ||
|
||
use crate::{checkers::ast::Checker, settings::types::PythonVersion}; | ||
|
||
/// ## What it does | ||
/// Checks for the use of `bin(x).count("1")` as a population count. | ||
/// | ||
/// ## Why is this bad? | ||
/// Python 3.10 added the `int.bit_count()` method, which is more efficient. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// x = bin(123).count("1") | ||
/// y = bin(0b1111011).count("1") | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// x = (123).bit_count() | ||
/// y = 0b1111011.bit_count() | ||
/// ``` | ||
#[violation] | ||
pub struct BitCount { | ||
original_argument: String, | ||
replacement: String, | ||
} | ||
|
||
impl AlwaysFixableViolation for BitCount { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
let BitCount { | ||
original_argument, .. | ||
} = self; | ||
format!("Use of bin({original_argument}).count('1')") | ||
} | ||
|
||
fn fix_title(&self) -> String { | ||
let BitCount { replacement, .. } = self; | ||
format!("Replace with `{replacement}`") | ||
} | ||
} | ||
|
||
/// FURB161 | ||
pub(crate) fn bit_count(checker: &mut Checker, call: &ExprCall) { | ||
if checker.settings.target_version < PythonVersion::Py310 { | ||
// `int.bit_count()` was added in Python 3.10 | ||
return; | ||
} | ||
|
||
let Expr::Attribute(ExprAttribute { attr, value, .. }) = call.func.as_ref() else { | ||
return; | ||
}; | ||
|
||
// make sure we're doing count | ||
if attr.as_str() != "count" { | ||
return; | ||
} | ||
|
||
let Some(arg) = call.arguments.args.first() else { | ||
return; | ||
}; | ||
|
||
let Expr::StringLiteral(ast::ExprStringLiteral { | ||
value: count_value, .. | ||
}) = arg | ||
else { | ||
return; | ||
}; | ||
|
||
// make sure we're doing count("1") | ||
if count_value != "1" { | ||
return; | ||
} | ||
|
||
let Expr::Call(ExprCall { | ||
func, arguments, .. | ||
}) = value.as_ref() | ||
else { | ||
return; | ||
}; | ||
|
||
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else { | ||
return; | ||
}; | ||
|
||
// make sure we're doing bin() | ||
if id.as_str() != "bin" { | ||
return; | ||
} | ||
|
||
if arguments.len() != 1 { | ||
return; | ||
} | ||
|
||
let Some(arg) = arguments.args.first() else { | ||
return; | ||
}; | ||
|
||
let literal_text = checker.locator().slice(arg.range()); | ||
|
||
let replacement = match arg { | ||
Expr::Name(ast::ExprName { id, .. }) => { | ||
format!("{id}.bit_count()") | ||
} | ||
Expr::NumberLiteral(ast::ExprNumberLiteral { .. }) => { | ||
let first_two_chars = checker | ||
.locator() | ||
.slice(TextRange::new(arg.start(), arg.start() + TextSize::from(2))); | ||
|
||
match first_two_chars { | ||
"0b" | "0B" | "0x" | "0X" | "0o" | "0O" => format!("{literal_text}.bit_count()"), | ||
_ => format!("({literal_text}).bit_count()"), | ||
} | ||
} | ||
Expr::Call(ast::ExprCall { .. }) => { | ||
format!("{literal_text}.bit_count()") | ||
} | ||
_ => { | ||
format!("({literal_text}).bit_count()") | ||
} | ||
}; | ||
|
||
let mut diagnostic = Diagnostic::new( | ||
BitCount { | ||
original_argument: literal_text.to_string(), | ||
replacement: replacement.clone(), | ||
}, | ||
call.range(), | ||
); | ||
|
||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( | ||
replacement, | ||
call.range(), | ||
))); | ||
|
||
checker.diagnostics.push(diagnostic); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
...ter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB161_FURB161.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/refurb/mod.rs | ||
--- | ||
FURB161.py:6:9: FURB161 [*] Use of bin(x).count('1') | ||
| | ||
4 | return 10 | ||
5 | | ||
6 | count = bin(x).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^ FURB161 | ||
7 | count = bin(10).count("1") # FURB161 | ||
8 | count = bin(0b1010).count("1") # FURB161 | ||
| | ||
= help: Replace with `x.bit_count()` | ||
|
||
ℹ Safe fix | ||
3 3 | def ten() -> int: | ||
4 4 | return 10 | ||
5 5 | | ||
6 |-count = bin(x).count("1") # FURB161 | ||
6 |+count = x.bit_count() # FURB161 | ||
7 7 | count = bin(10).count("1") # FURB161 | ||
8 8 | count = bin(0b1010).count("1") # FURB161 | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
|
||
FURB161.py:7:9: FURB161 [*] Use of bin(10).count('1') | ||
| | ||
6 | count = bin(x).count("1") # FURB161 | ||
7 | count = bin(10).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^ FURB161 | ||
8 | count = bin(0b1010).count("1") # FURB161 | ||
9 | count = bin(0xA).count("1") # FURB161 | ||
| | ||
= help: Replace with `(10).bit_count()` | ||
|
||
ℹ Safe fix | ||
4 4 | return 10 | ||
5 5 | | ||
6 6 | count = bin(x).count("1") # FURB161 | ||
7 |-count = bin(10).count("1") # FURB161 | ||
7 |+count = (10).bit_count() # FURB161 | ||
8 8 | count = bin(0b1010).count("1") # FURB161 | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
10 10 | count = bin(0o12).count("1") # FURB161 | ||
|
||
FURB161.py:8:9: FURB161 [*] Use of bin(0b1010).count('1') | ||
| | ||
6 | count = bin(x).count("1") # FURB161 | ||
7 | count = bin(10).count("1") # FURB161 | ||
8 | count = bin(0b1010).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
9 | count = bin(0xA).count("1") # FURB161 | ||
10 | count = bin(0o12).count("1") # FURB161 | ||
| | ||
= help: Replace with `0b1010.bit_count()` | ||
|
||
ℹ Safe fix | ||
5 5 | | ||
6 6 | count = bin(x).count("1") # FURB161 | ||
7 7 | count = bin(10).count("1") # FURB161 | ||
8 |-count = bin(0b1010).count("1") # FURB161 | ||
8 |+count = 0b1010.bit_count() # FURB161 | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
10 10 | count = bin(0o12).count("1") # FURB161 | ||
11 11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
|
||
FURB161.py:9:9: FURB161 [*] Use of bin(0xA).count('1') | ||
| | ||
7 | count = bin(10).count("1") # FURB161 | ||
8 | count = bin(0b1010).count("1") # FURB161 | ||
9 | count = bin(0xA).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^ FURB161 | ||
10 | count = bin(0o12).count("1") # FURB161 | ||
11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
| | ||
= help: Replace with `0xA.bit_count()` | ||
|
||
ℹ Safe fix | ||
6 6 | count = bin(x).count("1") # FURB161 | ||
7 7 | count = bin(10).count("1") # FURB161 | ||
8 8 | count = bin(0b1010).count("1") # FURB161 | ||
9 |-count = bin(0xA).count("1") # FURB161 | ||
9 |+count = 0xA.bit_count() # FURB161 | ||
10 10 | count = bin(0o12).count("1") # FURB161 | ||
11 11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
12 12 | count = bin(ten()).count("1") # FURB161 | ||
|
||
FURB161.py:10:9: FURB161 [*] Use of bin(0o12).count('1') | ||
| | ||
8 | count = bin(0b1010).count("1") # FURB161 | ||
9 | count = bin(0xA).count("1") # FURB161 | ||
10 | count = bin(0o12).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
12 | count = bin(ten()).count("1") # FURB161 | ||
| | ||
= help: Replace with `0o12.bit_count()` | ||
|
||
ℹ Safe fix | ||
7 7 | count = bin(10).count("1") # FURB161 | ||
8 8 | count = bin(0b1010).count("1") # FURB161 | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
10 |-count = bin(0o12).count("1") # FURB161 | ||
10 |+count = 0o12.bit_count() # FURB161 | ||
11 11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
12 12 | count = bin(ten()).count("1") # FURB161 | ||
13 13 | | ||
|
||
FURB161.py:11:9: FURB161 [*] Use of bin(0x10 + 0x1000).count('1') | ||
| | ||
9 | count = bin(0xA).count("1") # FURB161 | ||
10 | count = bin(0o12).count("1") # FURB161 | ||
11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
12 | count = bin(ten()).count("1") # FURB161 | ||
| | ||
= help: Replace with `(0x10 + 0x1000).bit_count()` | ||
|
||
ℹ Safe fix | ||
8 8 | count = bin(0b1010).count("1") # FURB161 | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
10 10 | count = bin(0o12).count("1") # FURB161 | ||
11 |-count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
11 |+count = (0x10 + 0x1000).bit_count() # FURB161 | ||
12 12 | count = bin(ten()).count("1") # FURB161 | ||
13 13 | | ||
14 14 | count = x.bit_count() # OK | ||
|
||
FURB161.py:12:9: FURB161 [*] Use of bin(ten()).count('1') | ||
| | ||
10 | count = bin(0o12).count("1") # FURB161 | ||
11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
12 | count = bin(ten()).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
13 | | ||
14 | count = x.bit_count() # OK | ||
| | ||
= help: Replace with `ten().bit_count()` | ||
|
||
ℹ Safe fix | ||
9 9 | count = bin(0xA).count("1") # FURB161 | ||
10 10 | count = bin(0o12).count("1") # FURB161 | ||
11 11 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
12 |-count = bin(ten()).count("1") # FURB161 | ||
12 |+count = ten().bit_count() # FURB161 | ||
13 13 | | ||
14 14 | count = x.bit_count() # OK | ||
15 15 | count = (10).bit_count() # OK | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.