-
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.
- Loading branch information
1 parent
6644a9b
commit de5f663
Showing
8 changed files
with
199 additions
and
103 deletions.
There are no files selected for viewing
26 changes: 19 additions & 7 deletions
26
crates/ruff_linter/resources/test/fixtures/refurb/FURB167.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 |
---|---|---|
@@ -1,10 +1,22 @@ | ||
import re | ||
def func(): | ||
import re | ||
|
||
# BAD: | ||
if re.match("^hello", "hello world", re.I): | ||
pass | ||
# OK | ||
if re.match("^hello", "hello world", re.IGNORECASE): | ||
pass | ||
|
||
|
||
# GOOD: | ||
if re.match("^hello", "hello world", re.IGNORECASE): | ||
pass | ||
def func(): | ||
import re | ||
|
||
# FURB167 | ||
if re.match("^hello", "hello world", re.I): | ||
pass | ||
|
||
|
||
def func(): | ||
from re import match, I | ||
|
||
# FURB167 | ||
if match("^hello", "hello world", I): | ||
pass |
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
74 changes: 0 additions & 74 deletions
74
crates/ruff_linter/src/rules/refurb/rules/long_regex_flag.rs
This file was deleted.
Oops, something went wrong.
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
133 changes: 133 additions & 0 deletions
133
crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.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,133 @@ | ||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::Expr; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::checkers::ast::Checker; | ||
use crate::importer::ImportRequest; | ||
|
||
/// ## What it does | ||
/// Checks for the use of shorthand aliases for regular expression flags | ||
/// (e.g., `re.I` instead of `re.IGNORECASE`). | ||
/// | ||
/// ## Why is this bad? | ||
/// The regular expression module provides descriptive names for each flag, | ||
/// along with single-letter aliases. Prefer the descriptive names, as they | ||
/// are more readable and self-documenting. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// import re | ||
/// | ||
/// if re.match("^hello", "hello world", re.I): | ||
/// ... | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// import re | ||
/// | ||
/// if re.match("^hello", "hello world", re.IGNORECASE): | ||
/// ... | ||
/// ``` | ||
/// | ||
#[violation] | ||
pub struct RegexFlagAlias { | ||
alias: &'static str, | ||
full_name: &'static str, | ||
} | ||
|
||
impl AlwaysFixableViolation for RegexFlagAlias { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
let RegexFlagAlias { alias, .. } = self; | ||
format!("Use of regular expression alias `re.{alias}`") | ||
} | ||
|
||
fn fix_title(&self) -> String { | ||
let RegexFlagAlias { full_name, .. } = self; | ||
format!("Replace with `re.{full_name}`") | ||
} | ||
} | ||
|
||
/// FURB167 | ||
pub(crate) fn regex_flag_alias(checker: &mut Checker, expr: &Expr) { | ||
let Some(flag) = | ||
checker | ||
.semantic() | ||
.resolve_call_path(expr) | ||
.and_then(|call_path| match call_path.as_slice() { | ||
["re", "A"] => Some(RegexFlag::Ascii), | ||
["re", "I"] => Some(RegexFlag::IgnoreCase), | ||
["re", "L"] => Some(RegexFlag::Locale), | ||
["re", "M"] => Some(RegexFlag::Multiline), | ||
["re", "S"] => Some(RegexFlag::DotAll), | ||
["re", "T"] => Some(RegexFlag::Template), | ||
["re", "U"] => Some(RegexFlag::Unicode), | ||
["re", "X"] => Some(RegexFlag::Verbose), | ||
_ => None, | ||
}) | ||
else { | ||
return; | ||
}; | ||
|
||
let mut diagnostic = Diagnostic::new( | ||
RegexFlagAlias { | ||
alias: flag.alias(), | ||
full_name: flag.full_name(), | ||
}, | ||
expr.range(), | ||
); | ||
diagnostic.try_set_fix(|| { | ||
let (edit, binding) = checker.importer().get_or_import_symbol( | ||
&ImportRequest::import("re", flag.full_name()), | ||
expr.start(), | ||
checker.semantic(), | ||
)?; | ||
Ok(Fix::safe_edits( | ||
Edit::range_replacement(binding, expr.range()), | ||
[edit], | ||
)) | ||
}); | ||
checker.diagnostics.push(diagnostic); | ||
} | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
enum RegexFlag { | ||
Ascii, | ||
IgnoreCase, | ||
Locale, | ||
Multiline, | ||
DotAll, | ||
Template, | ||
Unicode, | ||
Verbose, | ||
} | ||
|
||
impl RegexFlag { | ||
fn alias(self) -> &'static str { | ||
match self { | ||
Self::Ascii => "A", | ||
Self::IgnoreCase => "I", | ||
Self::Locale => "L", | ||
Self::Multiline => "M", | ||
Self::DotAll => "S", | ||
Self::Template => "T", | ||
Self::Unicode => "U", | ||
Self::Verbose => "X", | ||
} | ||
} | ||
|
||
fn full_name(self) -> &'static str { | ||
match self { | ||
Self::Ascii => "ASCII", | ||
Self::IgnoreCase => "IGNORECASE", | ||
Self::Locale => "LOCALE", | ||
Self::Multiline => "MULTILINE", | ||
Self::DotAll => "DOTALL", | ||
Self::Template => "TEMPLATE", | ||
Self::Unicode => "UNICODE", | ||
Self::Verbose => "VERBOSE", | ||
} | ||
} | ||
} |
54 changes: 38 additions & 16 deletions
54
...ter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB167_FURB167.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 |
---|---|---|
@@ -1,23 +1,45 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/refurb/mod.rs | ||
--- | ||
FURB167.py:4:38: FURB167 [*] Use of shorthand `re.I` | ||
| | ||
3 | # BAD: | ||
4 | if re.match("^hello", "hello world", re.I): | ||
| ^^^^ FURB167 | ||
5 | pass | ||
| | ||
= help: Replace with `re.IGNORECASE` | ||
FURB167.py:13:42: FURB167 [*] Use of regular expression alias `re.I` | ||
| | ||
12 | # FURB167 | ||
13 | if re.match("^hello", "hello world", re.I): | ||
| ^^^^ FURB167 | ||
14 | pass | ||
| | ||
= help: Replace with `re.IGNORECASE` | ||
|
||
ℹ Safe fix | ||
1 1 | import re | ||
2 2 | | ||
3 3 | # BAD: | ||
4 |-if re.match("^hello", "hello world", re.I): | ||
4 |+if re.match("^hello", "hello world", re.IGNORECASE): | ||
5 5 | pass | ||
6 6 | | ||
7 7 | | ||
10 10 | import re | ||
11 11 | | ||
12 12 | # FURB167 | ||
13 |- if re.match("^hello", "hello world", re.I): | ||
13 |+ if re.match("^hello", "hello world", re.IGNORECASE): | ||
14 14 | pass | ||
15 15 | | ||
16 16 | | ||
|
||
FURB167.py:21:39: FURB167 [*] Use of regular expression alias `re.I` | ||
| | ||
20 | # FURB167 | ||
21 | if match("^hello", "hello world", I): | ||
| ^ FURB167 | ||
22 | pass | ||
| | ||
= help: Replace with `re.IGNORECASE` | ||
|
||
ℹ Safe fix | ||
1 |+import re | ||
1 2 | def func(): | ||
2 3 | import re | ||
3 4 | | ||
-------------------------------------------------------------------------------- | ||
18 19 | from re import match, I | ||
19 20 | | ||
20 21 | # FURB167 | ||
21 |- if match("^hello", "hello world", I): | ||
22 |+ if match("^hello", "hello world", re.IGNORECASE): | ||
22 23 | pass | ||
|
||
|