-
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.
[flake8-bugbear] Implement re-sub-positional-args (B034)
- Loading branch information
1 parent
e7e2f44
commit 95f7d13
Showing
10 changed files
with
269 additions
and
258 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
crates/ruff/resources/test/fixtures/flake8_bugbear/B034.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,27 @@ | ||
import re | ||
from re import sub | ||
|
||
# B034 | ||
re.sub("a", "b", "aaa", re.IGNORECASE) | ||
re.sub("a", "b", "aaa", 5) | ||
re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
re.subn("a", "b", "aaa", re.IGNORECASE) | ||
re.subn("a", "b", "aaa", 5) | ||
re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
re.split(" ", "a a a a", re.I) | ||
re.split(" ", "a a a a", 2) | ||
re.split(" ", "a a a a", 2, re.I) | ||
sub("a", "b", "aaa", re.IGNORECASE) | ||
|
||
# OK | ||
re.sub("a", "b", "aaa") | ||
re.sub("a", "b", "aaa", flags=re.IGNORECASE) | ||
re.sub("a", "b", "aaa", count=5) | ||
re.sub("a", "b", "aaa", count=5, flags=re.IGNORECASE) | ||
re.subn("a", "b", "aaa") | ||
re.subn("a", "b", "aaa", flags=re.IGNORECASE) | ||
re.subn("a", "b", "aaa", count=5) | ||
re.subn("a", "b", "aaa", count=5, flags=re.IGNORECASE) | ||
re.split(" ", "a a a a", flags=re.I) | ||
re.split(" ", "a a a a", maxsplit=2) | ||
re.split(" ", "a a a a", maxsplit=2, flags=re.I) |
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
112 changes: 112 additions & 0 deletions
112
crates/ruff/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs
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,112 @@ | ||
use std::fmt; | ||
|
||
use rustpython_parser::ast::{self, Ranged}; | ||
|
||
use ruff_diagnostics::{Diagnostic, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
|
||
use crate::checkers::ast::Checker; | ||
|
||
/// ## What it does | ||
/// Checks for calls to `re.sub`, `re.subn`, and `re.split` that pass `count`, | ||
/// `maxsplit`, or `flags` as positional arguments. | ||
/// | ||
/// ## Why is this bad? | ||
/// Passing `count`, `maxsplit`, or `flags` as positional arguments to | ||
/// `re.sub`, re.subn`, or `re.split` can lead to confusion, as most methods in | ||
/// the `re` module accepts `flags` as the third positional argument, while | ||
/// `re.sub`, `re.subn`, and `re.split` have different signatures. | ||
/// | ||
/// Instead, pass `count`, `maxsplit`, and `flags` as keyword arguments. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// import re | ||
/// | ||
/// re.split("pattern", "replacement", 1) | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// import re | ||
/// | ||
/// re.split("pattern", "replacement", maxsplit=1) | ||
/// ``` | ||
/// | ||
/// ## References | ||
/// - [Python documentation: `re.sub`](https://docs.python.org/3/library/re.html#re.sub) | ||
/// - [Python documentation: `re.subn`](https://docs.python.org/3/library/re.html#re.subn) | ||
/// - [Python documentation: `re.split`](https://docs.python.org/3/library/re.html#re.split) | ||
#[violation] | ||
pub struct ReSubPositionalArgs { | ||
method: Method, | ||
} | ||
|
||
impl Violation for ReSubPositionalArgs { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
let ReSubPositionalArgs { method } = self; | ||
let param_name = method.param_name(); | ||
format!( | ||
"`{method}` should pass `{param_name}` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions" | ||
) | ||
} | ||
} | ||
|
||
/// B034 | ||
pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall) { | ||
let Some(method) = checker | ||
.semantic() | ||
.resolve_call_path(&call.func) | ||
.and_then(|call_path| match call_path.as_slice() { | ||
["re", "sub"] => Some(Method::Sub), | ||
["re", "subn"] => Some(Method::Subn), | ||
["re", "split"] => Some(Method::Split), | ||
_ => None, | ||
}) | ||
else { | ||
return; | ||
}; | ||
|
||
if call.args.len() > method.num_args() { | ||
checker.diagnostics.push(Diagnostic::new( | ||
ReSubPositionalArgs { method }, | ||
call.range(), | ||
)); | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
enum Method { | ||
Sub, | ||
Subn, | ||
Split, | ||
} | ||
|
||
impl fmt::Display for Method { | ||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
match self { | ||
Self::Sub => fmt.write_str("re.sub"), | ||
Self::Subn => fmt.write_str("re.subn"), | ||
Self::Split => fmt.write_str("re.split"), | ||
} | ||
} | ||
} | ||
|
||
impl Method { | ||
const fn param_name(self) -> &'static str { | ||
match self { | ||
Self::Sub => "count", | ||
Self::Subn => "count", | ||
Self::Split => "maxsplit", | ||
} | ||
} | ||
|
||
const fn num_args(self) -> usize { | ||
match self { | ||
Self::Sub => 3, | ||
Self::Subn => 3, | ||
Self::Split => 2, | ||
} | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
.../src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B034_B034.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,102 @@ | ||
--- | ||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs | ||
--- | ||
B034.py:5:1: B034 `re.sub` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
4 | # B034 | ||
5 | re.sub("a", "b", "aaa", re.IGNORECASE) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
6 | re.sub("a", "b", "aaa", 5) | ||
7 | re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
| | ||
|
||
B034.py:6:1: B034 `re.sub` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
4 | # B034 | ||
5 | re.sub("a", "b", "aaa", re.IGNORECASE) | ||
6 | re.sub("a", "b", "aaa", 5) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
7 | re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
8 | re.subn("a", "b", "aaa", re.IGNORECASE) | ||
| | ||
|
||
B034.py:7:1: B034 `re.sub` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
5 | re.sub("a", "b", "aaa", re.IGNORECASE) | ||
6 | re.sub("a", "b", "aaa", 5) | ||
7 | re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
8 | re.subn("a", "b", "aaa", re.IGNORECASE) | ||
9 | re.subn("a", "b", "aaa", 5) | ||
| | ||
|
||
B034.py:8:1: B034 `re.subn` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
6 | re.sub("a", "b", "aaa", 5) | ||
7 | re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
8 | re.subn("a", "b", "aaa", re.IGNORECASE) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
9 | re.subn("a", "b", "aaa", 5) | ||
10 | re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
| | ||
|
||
B034.py:9:1: B034 `re.subn` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
7 | re.sub("a", "b", "aaa", 5, re.IGNORECASE) | ||
8 | re.subn("a", "b", "aaa", re.IGNORECASE) | ||
9 | re.subn("a", "b", "aaa", 5) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
10 | re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
11 | re.split(" ", "a a a a", re.I) | ||
| | ||
|
||
B034.py:10:1: B034 `re.subn` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
8 | re.subn("a", "b", "aaa", re.IGNORECASE) | ||
9 | re.subn("a", "b", "aaa", 5) | ||
10 | re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
11 | re.split(" ", "a a a a", re.I) | ||
12 | re.split(" ", "a a a a", 2) | ||
| | ||
|
||
B034.py:11:1: B034 `re.split` should pass `maxsplit` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
9 | re.subn("a", "b", "aaa", 5) | ||
10 | re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
11 | re.split(" ", "a a a a", re.I) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
12 | re.split(" ", "a a a a", 2) | ||
13 | re.split(" ", "a a a a", 2, re.I) | ||
| | ||
|
||
B034.py:12:1: B034 `re.split` should pass `maxsplit` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
10 | re.subn("a", "b", "aaa", 5, re.IGNORECASE) | ||
11 | re.split(" ", "a a a a", re.I) | ||
12 | re.split(" ", "a a a a", 2) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
13 | re.split(" ", "a a a a", 2, re.I) | ||
14 | sub("a", "b", "aaa", re.IGNORECASE) | ||
| | ||
|
||
B034.py:13:1: B034 `re.split` should pass `maxsplit` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
11 | re.split(" ", "a a a a", re.I) | ||
12 | re.split(" ", "a a a a", 2) | ||
13 | re.split(" ", "a a a a", 2, re.I) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
14 | sub("a", "b", "aaa", re.IGNORECASE) | ||
| | ||
|
||
B034.py:14:1: B034 `re.sub` should pass `count` and `flags` as keyword arguments to avoid confusion due to unintuitive argument positions | ||
| | ||
12 | re.split(" ", "a a a a", 2) | ||
13 | re.split(" ", "a a a a", 2, re.I) | ||
14 | sub("a", "b", "aaa", re.IGNORECASE) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B034 | ||
15 | | ||
16 | # 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
Oops, something went wrong.