From a1d20b94e2fd948e104b8520dc89ca160ac9033f Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 24 Jun 2025 18:23:18 -0400 Subject: [PATCH 1/6] Implement RUF055 rule for unnecessary regular expressions --- .../resources/test/fixtures/ruff/RUF055_3.py | 24 ++++ crates/ruff_linter/src/rules/ruff/mod.rs | 1 + .../rules/unnecessary_regular_expression.rs | 103 +++++++++++++----- ...f__tests__preview__RUF055_RUF055_3.py.snap | 80 ++++++++++++++ 4 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF055_RUF055_3.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py new file mode 100644 index 0000000000000..13c93f53de85f --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py @@ -0,0 +1,24 @@ +import re + +b_src = b"abc" + +# Should be replaced with `b_src.replace(rb"x", b"y")` +re.sub(rb"x", b"y", b_src) + +# Should be replaced with `b_src.startswith(rb"abc")` +if re.match(rb"abc", b_src): + pass + +# Should be replaced with `rb"x" in b_src` +if re.search(rb"x", b_src): + pass + +# Should be replaced with `b_src.split(rb"abc")` +re.split(rb"abc", b_src) + +# Patterns containing metacharacters should NOT be replaced +re.sub(rb"ab[c]", b"", b_src) +re.match(rb"ab[c]", b_src) +re.search(rb"ab[c]", b_src) +re.fullmatch(rb"ab[c]", b_src) +re.split(rb"ab[c]", b_src) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index f712219503213..f95a0570e2d3b 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -534,6 +534,7 @@ mod tests { #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_0.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_2.py"))] + #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_3.py"))] #[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))] #[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))] #[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 7656a494fa738..1e652fd430b0f 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -1,8 +1,8 @@ use itertools::Itertools; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ - Arguments, CmpOp, Expr, ExprAttribute, ExprCall, ExprCompare, ExprContext, ExprStringLiteral, - ExprUnaryOp, Identifier, UnaryOp, + Arguments, CmpOp, Expr, ExprAttribute, ExprBytesLiteral, ExprCall, ExprCompare, ExprContext, + ExprStringLiteral, ExprUnaryOp, Identifier, UnaryOp, }; use ruff_python_semantic::analyze::typing::find_binding_value; use ruff_python_semantic::{Modules, SemanticModel}; @@ -95,17 +95,25 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) return; }; - // For now, restrict this rule to string literals and variables that can be resolved to literals - let Some(string_lit) = resolve_string_literal(re_func.pattern, semantic) else { + // Restrict this rule to string or bytes literals (or variables resolved to such literals). + let Some(literal) = resolve_literal(re_func.pattern, semantic) else { return; }; - // For now, reject any regex metacharacters. Compare to the complete list + // Reject any regex metacharacters. Compare to the complete list // from https://docs.python.org/3/howto/regex.html#matching-characters - let has_metacharacters = string_lit - .value - .to_str() - .contains(['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']); + let has_metacharacters = match &literal { + Literal::Str(str_lit) => str_lit + .value + .to_str() + .contains(['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']), + Literal::Bytes(bytes_lit) => bytes_lit.value.bytes().any(|b| { + matches!( + b, + b'.' | b'^' | b'$' | b'*' | b'+' | b'?' | b'{' | b'[' | b'\\' | b'|' | b'(' | b')' + ) + }), + }; if has_metacharacters { return; @@ -186,25 +194,29 @@ impl<'a> ReFunc<'a> { // version ("sub", 3) => { let repl = call.arguments.find_argument_value("repl", 1)?; - let lit = resolve_string_literal(repl, semantic)?; + let lit = resolve_literal(repl, semantic)?; let mut fixable = true; - for (c, next) in lit.value.chars().tuple_windows() { - // `\0` (or any other ASCII digit) and `\g` have special meaning in `repl` strings. - // Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes - // `re.PatternError` to be raised at runtime. - // - // If we see that the escaped character is an alphanumeric ASCII character, - // we should only emit a diagnostic suggesting to replace the `re.sub()` call with - // `str.replace`if we can detect that the escaped character is one that is both - // valid in a `repl` string *and* does not have any special meaning in a REPL string. - // - // It's out of scope for this rule to change invalid `re.sub()` calls into something - // that would not raise an exception at runtime. They should be left as-is. - if c == '\\' && next.is_ascii_alphanumeric() { - if "abfnrtv".contains(next) { - fixable = false; - } else { - return None; + + if let Literal::Str(lit_str) = lit { + // Retain existing escape analysis for string replacement literals. + for (c, next) in lit_str.value.chars().tuple_windows() { + // `\\0` (or any other ASCII digit) and `\\g` have special meaning in `repl` strings. + // Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes + // `re.PatternError` to be raised at runtime. + // + // If we see that the escaped character is an alphanumeric ASCII character, + // we should only emit a diagnostic suggesting to replace the `re.sub()` call with + // `str.replace`if we can detect that the escaped character is one that is both + // valid in a `repl` string *and* does not have any special meaning in a REPL string. + // + // It's out of scope for this rule to change invalid `re.sub()` calls into something + // that would not raise an exception at runtime. They should be left as-is. + if c == '\\' && next.is_ascii_alphanumeric() { + if "abfnrtv".contains(next) { + fixable = false; + } else { + return None; + } } } } @@ -329,6 +341,43 @@ impl<'a> ReFunc<'a> { } } +/// A literal that can be either a string or a bytes literal. +enum Literal<'a> { + Str(&'a ExprStringLiteral), + Bytes(&'a ExprBytesLiteral), +} + +/// Try to resolve `name` to an [`ExprBytesLiteral`] in `semantic`. +fn resolve_bytes_literal<'a>( + name: &'a Expr, + semantic: &'a SemanticModel, +) -> Option<&'a ExprBytesLiteral> { + if name.is_bytes_literal_expr() { + return name.as_bytes_literal_expr(); + } + + if let Some(name_expr) = name.as_name_expr() { + let binding = semantic.binding(semantic.only_binding(name_expr)?); + let value = find_binding_value(binding, semantic)?; + if value.is_bytes_literal_expr() { + return value.as_bytes_literal_expr(); + } + } + + None +} + +/// Try to resolve `name` to either a string or bytes literal in `semantic`. +fn resolve_literal<'a>(name: &'a Expr, semantic: &'a SemanticModel) -> Option> { + if let Some(str_lit) = resolve_string_literal(name, semantic) { + return Some(Literal::Str(str_lit)); + } + if let Some(bytes_lit) = resolve_bytes_literal(name, semantic) { + return Some(Literal::Bytes(bytes_lit)); + } + None +} + /// Try to resolve `name` to an [`ExprStringLiteral`] in `semantic`. fn resolve_string_literal<'a>( name: &'a Expr, diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF055_RUF055_3.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF055_RUF055_3.py.snap new file mode 100644 index 0000000000000..f14d70c98e982 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF055_RUF055_3.py.snap @@ -0,0 +1,80 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF055_3.py:6:1: RUF055 [*] Plain string pattern passed to `re` function + | +5 | # Should be replaced with `b_src.replace(rb"x", b"y")` +6 | re.sub(rb"x", b"y", b_src) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF055 +7 | +8 | # Should be replaced with `b_src.startswith(rb"abc")` + | + = help: Replace with `b_src.replace(rb"x", b"y")` + +ℹ Safe fix +3 3 | b_src = b"abc" +4 4 | +5 5 | # Should be replaced with `b_src.replace(rb"x", b"y")` +6 |-re.sub(rb"x", b"y", b_src) + 6 |+b_src.replace(rb"x", b"y") +7 7 | +8 8 | # Should be replaced with `b_src.startswith(rb"abc")` +9 9 | if re.match(rb"abc", b_src): + +RUF055_3.py:9:4: RUF055 [*] Plain string pattern passed to `re` function + | + 8 | # Should be replaced with `b_src.startswith(rb"abc")` + 9 | if re.match(rb"abc", b_src): + | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055 +10 | pass + | + = help: Replace with `b_src.startswith(rb"abc")` + +ℹ Safe fix +6 6 | re.sub(rb"x", b"y", b_src) +7 7 | +8 8 | # Should be replaced with `b_src.startswith(rb"abc")` +9 |-if re.match(rb"abc", b_src): + 9 |+if b_src.startswith(rb"abc"): +10 10 | pass +11 11 | +12 12 | # Should be replaced with `rb"x" in b_src` + +RUF055_3.py:13:4: RUF055 [*] Plain string pattern passed to `re` function + | +12 | # Should be replaced with `rb"x" in b_src` +13 | if re.search(rb"x", b_src): + | ^^^^^^^^^^^^^^^^^^^^^^^ RUF055 +14 | pass + | + = help: Replace with `rb"x" in b_src` + +ℹ Safe fix +10 10 | pass +11 11 | +12 12 | # Should be replaced with `rb"x" in b_src` +13 |-if re.search(rb"x", b_src): + 13 |+if rb"x" in b_src: +14 14 | pass +15 15 | +16 16 | # Should be replaced with `b_src.split(rb"abc")` + +RUF055_3.py:17:1: RUF055 [*] Plain string pattern passed to `re` function + | +16 | # Should be replaced with `b_src.split(rb"abc")` +17 | re.split(rb"abc", b_src) + | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF055 +18 | +19 | # Patterns containing metacharacters should NOT be replaced + | + = help: Replace with `b_src.split(rb"abc")` + +ℹ Safe fix +14 14 | pass +15 15 | +16 16 | # Should be replaced with `b_src.split(rb"abc")` +17 |-re.split(rb"abc", b_src) + 17 |+b_src.split(rb"abc") +18 18 | +19 19 | # Patterns containing metacharacters should NOT be replaced +20 20 | re.sub(rb"ab[c]", b"", b_src) From 37d950cd8be82fdcc97942ea1146101cae2610e5 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 25 Jun 2025 17:10:21 -0400 Subject: [PATCH 2/6] apply feedback --- .../rules/unnecessary_regular_expression.rs | 94 +++++++++++-------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 1e652fd430b0f..35ee3faba9eb1 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -11,6 +11,8 @@ use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; +const METACHARACTERS: [char; 12] = ['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']; + /// ## What it does /// /// Checks for uses of the `re` module that can be replaced with builtin `str` methods. @@ -95,24 +97,20 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) return; }; - // Restrict this rule to string or bytes literals (or variables resolved to such literals). + // For now, restrict this rule to string literals and variables that can be resolved to literals let Some(literal) = resolve_literal(re_func.pattern, semantic) else { return; }; // Reject any regex metacharacters. Compare to the complete list // from https://docs.python.org/3/howto/regex.html#matching-characters + let has_metacharacters = match &literal { - Literal::Str(str_lit) => str_lit + Literal::Str(str_lit) => str_lit.value.to_str().contains(METACHARACTERS), + Literal::Bytes(bytes_lit) => bytes_lit .value - .to_str() - .contains(['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']), - Literal::Bytes(bytes_lit) => bytes_lit.value.bytes().any(|b| { - matches!( - b, - b'.' | b'^' | b'$' | b'*' | b'+' | b'?' | b'{' | b'[' | b'\\' | b'|' | b'(' | b')' - ) - }), + .iter() + .any(|part| part.iter().any(|&b| METACHARACTERS.contains(&(b as char)))), }; if has_metacharacters { @@ -197,29 +195,45 @@ impl<'a> ReFunc<'a> { let lit = resolve_literal(repl, semantic)?; let mut fixable = true; - if let Literal::Str(lit_str) = lit { - // Retain existing escape analysis for string replacement literals. - for (c, next) in lit_str.value.chars().tuple_windows() { - // `\\0` (or any other ASCII digit) and `\\g` have special meaning in `repl` strings. - // Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes - // `re.PatternError` to be raised at runtime. - // - // If we see that the escaped character is an alphanumeric ASCII character, - // we should only emit a diagnostic suggesting to replace the `re.sub()` call with - // `str.replace`if we can detect that the escaped character is one that is both - // valid in a `repl` string *and* does not have any special meaning in a REPL string. - // - // It's out of scope for this rule to change invalid `re.sub()` calls into something - // that would not raise an exception at runtime. They should be left as-is. - if c == '\\' && next.is_ascii_alphanumeric() { - if "abfnrtv".contains(next) { - fixable = false; - } else { - return None; + match lit { + Literal::Str(lit_str) => { + // Retain existing escape analysis for string replacement literals. + for (c, next) in lit_str.value.to_str().chars().tuple_windows() { + // `\\0` (or any other ASCII digit) and `\\g` have special meaning in `repl` strings. + // Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes + // `re.PatternError` to be raised at runtime. + // + // If we see that the escaped character is an alphanumeric ASCII character, + // we should only emit a diagnostic suggesting to replace the `re.sub()` call with + // `str.replace`if we can detect that the escaped character is one that is both + // valid in a `repl` string *and* does not have any special meaning in a REPL string. + // + // It's out of scope for this rule to change invalid `re.sub()` calls into something + // that would not raise an exception at runtime. They should be left as-is. + if c == '\\' && next.is_ascii_alphanumeric() { + if "abfnrtv".contains(next) { + fixable = false; + } else { + return None; + } + } + } + } + Literal::Bytes(lit_bytes) => { + for part in &lit_bytes.value { + for (byte, next) in part.iter().copied().tuple_windows() { + if byte == b'\\' && (next as char).is_ascii_alphanumeric() { + if "abfnrtv".contains(next as char) { + fixable = false; + } else { + return None; + } + } } } } } + Some(ReFunc { kind: ReFuncKind::Sub { repl: fixable.then_some(repl), @@ -347,6 +361,17 @@ enum Literal<'a> { Bytes(&'a ExprBytesLiteral), } +/// Try to resolve `name` to either a string or bytes literal in `semantic`. +fn resolve_literal<'a>(name: &'a Expr, semantic: &'a SemanticModel) -> Option> { + if let Some(str_lit) = resolve_string_literal(name, semantic) { + return Some(Literal::Str(str_lit)); + } + if let Some(bytes_lit) = resolve_bytes_literal(name, semantic) { + return Some(Literal::Bytes(bytes_lit)); + } + None +} + /// Try to resolve `name` to an [`ExprBytesLiteral`] in `semantic`. fn resolve_bytes_literal<'a>( name: &'a Expr, @@ -367,17 +392,6 @@ fn resolve_bytes_literal<'a>( None } -/// Try to resolve `name` to either a string or bytes literal in `semantic`. -fn resolve_literal<'a>(name: &'a Expr, semantic: &'a SemanticModel) -> Option> { - if let Some(str_lit) = resolve_string_literal(name, semantic) { - return Some(Literal::Str(str_lit)); - } - if let Some(bytes_lit) = resolve_bytes_literal(name, semantic) { - return Some(Literal::Bytes(bytes_lit)); - } - None -} - /// Try to resolve `name` to an [`ExprStringLiteral`] in `semantic`. fn resolve_string_literal<'a>( name: &'a Expr, From e19d1f0eb9f08c0f8f82c21463b56a39f14be3d4 Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:56:03 -0400 Subject: [PATCH 3/6] Update crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> --- .../src/rules/ruff/rules/unnecessary_regular_expression.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 35ee3faba9eb1..447b880571d6d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -102,7 +102,7 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) return; }; - // Reject any regex metacharacters. Compare to the complete list + // For now, reject any regex metacharacters. Compare to the complete list // from https://docs.python.org/3/howto/regex.html#matching-characters let has_metacharacters = match &literal { From 8e03ab3151cb2bebbf01d072db49b1f315b6895f Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:56:10 -0400 Subject: [PATCH 4/6] Update crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> --- .../src/rules/ruff/rules/unnecessary_regular_expression.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 447b880571d6d..7489ed7002375 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -104,7 +104,6 @@ pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) // For now, reject any regex metacharacters. Compare to the complete list // from https://docs.python.org/3/howto/regex.html#matching-characters - let has_metacharacters = match &literal { Literal::Str(str_lit) => str_lit.value.to_str().contains(METACHARACTERS), Literal::Bytes(bytes_lit) => bytes_lit From 46c09b2a65192984d94de21b2b58cb90994d5b8a Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 18 Jul 2025 12:08:35 -0400 Subject: [PATCH 5/6] apply feedback --- .../rules/ruff/rules/unnecessary_regular_expression.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index 7489ed7002375..f544cff55198d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -12,7 +12,6 @@ use crate::checkers::ast::Checker; use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; const METACHARACTERS: [char; 12] = ['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']; - /// ## What it does /// /// Checks for uses of the `re` module that can be replaced with builtin `str` methods. @@ -74,6 +73,9 @@ impl Violation for UnnecessaryRegularExpression { } } +const METACHARACTERS: [char; 12] = ['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']; +const ESCAPABLE_SINGLE_CHARACTERS: &str = "abfnrtv"; + /// RUF055 pub(crate) fn unnecessary_regular_expression(checker: &Checker, call: &ExprCall) { // adapted from unraw_re_pattern @@ -196,7 +198,7 @@ impl<'a> ReFunc<'a> { match lit { Literal::Str(lit_str) => { - // Retain existing escape analysis for string replacement literals. + // Perform escape analysis for replacement literals. for (c, next) in lit_str.value.to_str().chars().tuple_windows() { // `\\0` (or any other ASCII digit) and `\\g` have special meaning in `repl` strings. // Meanwhile, nearly all other escapes of ASCII letters in a `repl` string causes @@ -210,7 +212,7 @@ impl<'a> ReFunc<'a> { // It's out of scope for this rule to change invalid `re.sub()` calls into something // that would not raise an exception at runtime. They should be left as-is. if c == '\\' && next.is_ascii_alphanumeric() { - if "abfnrtv".contains(next) { + if ESCAPABLE_SINGLE_CHARACTERS.contains(next) { fixable = false; } else { return None; @@ -222,7 +224,7 @@ impl<'a> ReFunc<'a> { for part in &lit_bytes.value { for (byte, next) in part.iter().copied().tuple_windows() { if byte == b'\\' && (next as char).is_ascii_alphanumeric() { - if "abfnrtv".contains(next as char) { + if ESCAPABLE_SINGLE_CHARACTERS.contains(next as char) { fixable = false; } else { return None; From fd2ad42ed2ac9b22bb341c147543c039edff5439 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 18 Jul 2025 12:09:21 -0400 Subject: [PATCH 6/6] remove additional const --- .../src/rules/ruff/rules/unnecessary_regular_expression.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs index f544cff55198d..3b9c1a4673fb6 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_regular_expression.rs @@ -11,7 +11,6 @@ use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; -const METACHARACTERS: [char; 12] = ['.', '^', '$', '*', '+', '?', '{', '[', '\\', '|', '(', ')']; /// ## What it does /// /// Checks for uses of the `re` module that can be replaced with builtin `str` methods.