-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 82b44df
Showing
8 changed files
with
287 additions
and
0 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
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,15 @@ | ||
x = 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 = 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 |
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,137 @@ | ||
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()"), | ||
} | ||
} | ||
_ => { | ||
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
127 changes: 127 additions & 0 deletions
127
...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,127 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/refurb/mod.rs | ||
--- | ||
FURB161.py:3:9: FURB161 [*] Use of bin(x).count('1') | ||
| | ||
1 | x = 10 | ||
2 | | ||
3 | count = bin(x).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^ FURB161 | ||
4 | count = bin(10).count("1") # FURB161 | ||
5 | count = bin(0b1010).count("1") # FURB161 | ||
| | ||
= help: Replace with `x.bit_count()` | ||
|
||
ℹ Safe fix | ||
1 1 | x = 10 | ||
2 2 | | ||
3 |-count = bin(x).count("1") # FURB161 | ||
3 |+count = x.bit_count() # FURB161 | ||
4 4 | count = bin(10).count("1") # FURB161 | ||
5 5 | count = bin(0b1010).count("1") # FURB161 | ||
6 6 | count = bin(0xA).count("1") # FURB161 | ||
|
||
FURB161.py:4:9: FURB161 [*] Use of bin(10).count('1') | ||
| | ||
3 | count = bin(x).count("1") # FURB161 | ||
4 | count = bin(10).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^ FURB161 | ||
5 | count = bin(0b1010).count("1") # FURB161 | ||
6 | count = bin(0xA).count("1") # FURB161 | ||
| | ||
= help: Replace with `(10).bit_count()` | ||
|
||
ℹ Safe fix | ||
1 1 | x = 10 | ||
2 2 | | ||
3 3 | count = bin(x).count("1") # FURB161 | ||
4 |-count = bin(10).count("1") # FURB161 | ||
4 |+count = (10).bit_count() # FURB161 | ||
5 5 | count = bin(0b1010).count("1") # FURB161 | ||
6 6 | count = bin(0xA).count("1") # FURB161 | ||
7 7 | count = bin(0o12).count("1") # FURB161 | ||
|
||
FURB161.py:5:9: FURB161 [*] Use of bin(0b1010).count('1') | ||
| | ||
3 | count = bin(x).count("1") # FURB161 | ||
4 | count = bin(10).count("1") # FURB161 | ||
5 | count = bin(0b1010).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
6 | count = bin(0xA).count("1") # FURB161 | ||
7 | count = bin(0o12).count("1") # FURB161 | ||
| | ||
= help: Replace with `0b1010.bit_count()` | ||
|
||
ℹ Safe fix | ||
2 2 | | ||
3 3 | count = bin(x).count("1") # FURB161 | ||
4 4 | count = bin(10).count("1") # FURB161 | ||
5 |-count = bin(0b1010).count("1") # FURB161 | ||
5 |+count = 0b1010.bit_count() # FURB161 | ||
6 6 | count = bin(0xA).count("1") # FURB161 | ||
7 7 | count = bin(0o12).count("1") # FURB161 | ||
8 8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
|
||
FURB161.py:6:9: FURB161 [*] Use of bin(0xA).count('1') | ||
| | ||
4 | count = bin(10).count("1") # FURB161 | ||
5 | count = bin(0b1010).count("1") # FURB161 | ||
6 | count = bin(0xA).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^ FURB161 | ||
7 | count = bin(0o12).count("1") # FURB161 | ||
8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
| | ||
= help: Replace with `0xA.bit_count()` | ||
|
||
ℹ Safe fix | ||
3 3 | count = bin(x).count("1") # FURB161 | ||
4 4 | count = bin(10).count("1") # FURB161 | ||
5 5 | count = bin(0b1010).count("1") # FURB161 | ||
6 |-count = bin(0xA).count("1") # FURB161 | ||
6 |+count = 0xA.bit_count() # FURB161 | ||
7 7 | count = bin(0o12).count("1") # FURB161 | ||
8 8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
9 9 | | ||
|
||
FURB161.py:7:9: FURB161 [*] Use of bin(0o12).count('1') | ||
| | ||
5 | count = bin(0b1010).count("1") # FURB161 | ||
6 | count = bin(0xA).count("1") # FURB161 | ||
7 | count = bin(0o12).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
| | ||
= help: Replace with `0o12.bit_count()` | ||
|
||
ℹ Safe fix | ||
4 4 | count = bin(10).count("1") # FURB161 | ||
5 5 | count = bin(0b1010).count("1") # FURB161 | ||
6 6 | count = bin(0xA).count("1") # FURB161 | ||
7 |-count = bin(0o12).count("1") # FURB161 | ||
7 |+count = 0o12.bit_count() # FURB161 | ||
8 8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
9 9 | | ||
10 10 | count = x.bit_count() # OK | ||
|
||
FURB161.py:8:9: FURB161 [*] Use of bin(0x10 + 0x1000).count('1') | ||
| | ||
6 | count = bin(0xA).count("1") # FURB161 | ||
7 | count = bin(0o12).count("1") # FURB161 | ||
8 | count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB161 | ||
9 | | ||
10 | count = x.bit_count() # OK | ||
| | ||
= help: Replace with `(0x10 + 0x1000).bit_count()` | ||
|
||
ℹ Safe fix | ||
5 5 | count = bin(0b1010).count("1") # FURB161 | ||
6 6 | count = bin(0xA).count("1") # FURB161 | ||
7 7 | count = bin(0o12).count("1") # FURB161 | ||
8 |-count = bin(0x10 + 0x1000).count("1") # FURB161 | ||
8 |+count = (0x10 + 0x1000).bit_count() # FURB161 | ||
9 9 | | ||
10 10 | count = x.bit_count() # OK | ||
11 11 | 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.