Skip to content

Commit

Permalink
[refurb] - add FURB167/use-long-regex-flag with autofix
Browse files Browse the repository at this point in the history
  • Loading branch information
diceroll123 committed Jan 14, 2024
1 parent 953d48b commit 6644a9b
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 0 deletions.
10 changes: 10 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/refurb/FURB167.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import re

# BAD:
if re.match("^hello", "hello world", re.I):
pass


# GOOD:
if re.match("^hello", "hello world", re.IGNORECASE):
pass
3 changes: 3 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
}
if checker.enabled(Rule::LongRegexFlag) {
refurb::rules::long_regex_flag(checker, expr);
}
if checker.enabled(Rule::DatetimeTimezoneUTC) {
if checker.settings.target_version >= PythonVersion::Py311 {
pyupgrade::rules::datetime_utc_alias(checker, expr);
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant),
(Refurb, "161") => (RuleGroup::Preview, rules::refurb::rules::BitCount),
(Refurb, "163") => (RuleGroup::Preview, rules::refurb::rules::RedundantLogBase),
(Refurb, "167") => (RuleGroup::Preview, rules::refurb::rules::LongRegexFlag),
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/refurb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod tests {
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))]
#[test_case(Rule::BitCount, Path::new("FURB161.py"))]
#[test_case(Rule::LongRegexFlag, Path::new("FURB167.py"))]
#[test_case(Rule::IsinstanceTypeNone, Path::new("FURB168.py"))]
#[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))]
#[test_case(Rule::RedundantLogBase, Path::new("FURB163.py"))]
Expand Down
74 changes: 74 additions & 0 deletions crates/ruff_linter/src/rules/refurb/rules/long_regex_flag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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;

/// ## What it does
/// Checks for uses of shorthand regex flags such as `re.I`.
///
/// ## Why is this bad?
/// These single-character flags are not as descriptive as the full names.
///
/// ## Example
/// ```python
/// if re.match("^hello", "hello world", re.I):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// if re.match("^hello", "hello world", re.IGNORECASE):
/// ...
/// ```
///
#[violation]
pub struct LongRegexFlag {
short: &'static str,
long: &'static str,
}

impl AlwaysFixableViolation for LongRegexFlag {
#[derive_message_formats]
fn message(&self) -> String {
let LongRegexFlag { short, .. } = self;
format!("Use of shorthand `re.{short}`")
}

fn fix_title(&self) -> String {
let LongRegexFlag { long, .. } = self;
format!("Replace with `re.{long}`")
}
}

/// FURB167
pub(crate) fn long_regex_flag(checker: &mut Checker, expr: &Expr) {
let Some((short, long)) = checker
.semantic()
.resolve_call_path(expr)
.and_then(|call_path| match call_path.as_slice() {
["re", "A"] => Some(("A", "ASCII")),
["re", "I"] => Some(("I", "IGNORECASE")),
["re", "L"] => Some(("L", "LOCALE")),
["re", "M"] => Some(("M", "MULTILINE")),
["re", "S"] => Some(("S", "DOTALL")),
["re", "T"] => Some(("T", "TEMPLATE")),
["re", "U"] => Some(("U", "UNICODE")),
["re", "X"] => Some(("X", "VERBOSE")),
_ => None,
})
else {
return;
};

let mut diagnostic = Diagnostic::new(LongRegexFlag { short, long }, expr.range());

diagnostic.set_fix(Fix::safe_edit(Edit::replacement(
format!("re.{long}"),
expr.start(),
expr.end(),
)));

checker.diagnostics.push(diagnostic);
}
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/refurb/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub(crate) use hashlib_digest_hex::*;
pub(crate) use if_expr_min_max::*;
pub(crate) use implicit_cwd::*;
pub(crate) use isinstance_type_none::*;
pub(crate) use long_regex_flag::*;
pub(crate) use math_constant::*;
pub(crate) use print_empty_string::*;
pub(crate) use read_whole_file::*;
Expand All @@ -24,6 +25,7 @@ mod hashlib_digest_hex;
mod if_expr_min_max;
mod implicit_cwd;
mod isinstance_type_none;
mod long_regex_flag;
mod math_constant;
mod print_empty_string;
mod read_whole_file;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
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`

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 |


1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6644a9b

Please sign in to comment.