[ruff] re and regex calls with unraw string as first argument (RUF039) (#14446)

This commit is contained in:
InSync 2024-11-19 19:44:55 +07:00 committed by GitHub
parent f8c20258ae
commit 5f09d4a90a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 832 additions and 0 deletions

View file

@ -0,0 +1,55 @@
import re
import regex
# Errors
re.compile('single free-spacing', flags=re.X)
re.findall('si\ngle')
re.finditer("dou\ble")
re.fullmatch('''t\riple single''')
re.match("""\triple double""")
re.search('two', 'args')
re.split("raw", r'second')
re.sub(u'''nicode''', u"f(?i)rst")
re.subn(b"""ytes are""", f"\u006e")
regex.compile('single free-spacing', flags=regex.X)
regex.findall('si\ngle')
regex.finditer("dou\ble")
regex.fullmatch('''t\riple single''')
regex.match("""\triple double""")
regex.search('two', 'args')
regex.split("raw", r'second')
regex.sub(u'''nicode''', u"f(?i)rst")
regex.subn(b"""ytes are""", f"\u006e")
regex.template("""(?m)
(?:ulti)?
(?=(?<!(?<=(?!l)))
l(?i:ne)
""", flags = regex.X)
# No errors
re.compile(R'uppercase')
re.findall(not_literal)
re.finditer(0, literal_but_not_string)
re.fullmatch() # no first argument
re.match('string' f'''concatenation''')
re.search(R"raw" r'concatenation')
re.split(rf"multiple", f"""lags""")
re.sub(FR'ee', '''as in free speech''')
re.subn(br"""eak your machine with rm -""", rf"""/""")
regex.compile(R'uppercase')
regex.findall(not_literal)
regex.finditer(0, literal_but_not_string)
regex.fullmatch() # no first argument
regex.match('string' f'''concatenation''')
regex.search(R"raw" r'concatenation')
regex.split(rf"multiple", f"""lags""")
regex.sub(FR'ee', '''as in free speech''')
regex.subn(br"""eak your machine with rm -""", rf"""/""")
regex.splititer(both, non_literal)
regex.subf(f, lambda _: r'means', '"format"')
regex.subfn(fn, f'''a$1n't''', lambda: "'function'")

View file

@ -0,0 +1,93 @@
import re
re.compile(
'implicit'
'concatenation'
)
re.findall(
r'''
multiline
'''
"""
concatenation
"""
)
re.finditer(
f'(?P<{group}>Dynamic'
r'\s+group'
'name)'
)
re.fullmatch(
u'n'r'''eadable'''
f'much?'
)
re.match(
b'reak'
br'eak'
)
re.search(
r''u''
'''okay?'''
)
re.split(''U"""w"""U'')
re.sub(
"I''m o"
'utta ideas'
)
re.subn("()"r' am I'"??")
import regex
regex.compile(
'implicit'
'concatenation'
)
regex.findall(
r'''
multiline
'''
"""
concatenation
"""
)
regex.finditer(
f'(?P<{group}>Dynamic'
r'\s+group'
'name)'
)
regex.fullmatch(
u'n'r'''eadable'''
f'much?'
)
regex.match(
b'reak'
br'eak'
)
regex.search(
r''u''
'''okay?'''
)
regex.split(''U"""w"""U'')
regex.sub(
"I''m o"
'utta ideas'
)
regex.subn("()"r' am I'"??")
regex.template(
r'''kitty says'''
r""r''r""r'aw'r""
)
regex.splititer(
r'r+r*r?'
)
regex.subf(
rb"haha"
br"ust go"
br''br""br''
)
regex.subfn(br'I\s\nee*d\s[O0o]me\x20\Qoffe\E, ' br'b')

View file

@ -1058,6 +1058,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::MapIntVersionParsing) { if checker.enabled(Rule::MapIntVersionParsing) {
ruff::rules::map_int_version_parsing(checker, call); ruff::rules::map_int_version_parsing(checker, call);
} }
if checker.enabled(Rule::UnrawRePattern) {
ruff::rules::unraw_re_pattern(checker, call);
}
} }
Expr::Dict(dict) => { Expr::Dict(dict) => {
if checker.any_enabled(&[ if checker.any_enabled(&[

View file

@ -972,6 +972,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "036") => (RuleGroup::Preview, rules::ruff::rules::NoneNotAtEndOfUnion), (Ruff, "036") => (RuleGroup::Preview, rules::ruff::rules::NoneNotAtEndOfUnion),
(Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral), (Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral),
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing), (Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),

View file

@ -399,6 +399,8 @@ mod tests {
#[test_case(Rule::MutableDataclassDefault, Path::new("RUF008_attrs.py"))] #[test_case(Rule::MutableDataclassDefault, Path::new("RUF008_attrs.py"))]
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))] #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))]
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))] #[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))]
#[test_case(Rule::UnrawRePattern, Path::new("RUF039.py"))]
#[test_case(Rule::UnrawRePattern, Path::new("RUF039_concat.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!( let snapshot = format!(
"preview__{}_{}", "preview__{}_{}",

View file

@ -31,6 +31,7 @@ pub(crate) use static_key_dict_comprehension::*;
pub(crate) use test_rules::*; pub(crate) use test_rules::*;
pub(crate) use unnecessary_iterable_allocation_for_first_element::*; pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
pub(crate) use unnecessary_key_check::*; pub(crate) use unnecessary_key_check::*;
pub(crate) use unraw_re_pattern::*;
pub(crate) use unsafe_markup_use::*; pub(crate) use unsafe_markup_use::*;
pub(crate) use unused_async::*; pub(crate) use unused_async::*;
pub(crate) use unused_noqa::*; pub(crate) use unused_noqa::*;
@ -74,6 +75,7 @@ mod suppression_comment_visitor;
pub(crate) mod test_rules; pub(crate) mod test_rules;
mod unnecessary_iterable_allocation_for_first_element; mod unnecessary_iterable_allocation_for_first_element;
mod unnecessary_key_check; mod unnecessary_key_check;
mod unraw_re_pattern;
mod unsafe_markup_use; mod unsafe_markup_use;
mod unused_async; mod unused_async;
mod unused_noqa; mod unused_noqa;

View file

@ -0,0 +1,177 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{
BytesLiteral, Expr, ExprBytesLiteral, ExprCall, ExprStringLiteral, StringLiteral,
};
use ruff_python_semantic::{Modules, SemanticModel};
use crate::checkers::ast::Checker;
/// ## What it does
/// Reports the following `re` and `regex` calls when
/// their first arguments are not raw strings:
///
/// - For `regex` and `re`: `compile`, `findall`, `finditer`,
/// `fullmatch`, `match`, `search`, `split`, `sub`, `subn`.
/// - `regex`-specific: `splititer`, `subf`, `subfn`, `template`.
///
/// ## Why is this bad?
/// Regular expressions should be written
/// using raw strings to avoid double escaping.
///
/// ## Example
///
/// ```python
/// re.compile("foo\\bar")
/// ```
///
/// Use instead:
///
/// ```python
/// re.compile(r"foo\bar")
/// ```
#[violation]
pub struct UnrawRePattern {
module: RegexModule,
func: String,
kind: PatternKind,
}
impl Violation for UnrawRePattern {
#[derive_message_formats]
fn message(&self) -> String {
let Self { module, func, kind } = &self;
let call = format!("`{module}.{func}()`");
match kind {
PatternKind::String => format!("First argument to {call} is not raw string"),
PatternKind::Bytes => format!("First argument to {call} is not raw bytes literal"),
}
}
fn fix_title(&self) -> Option<String> {
match self.kind {
PatternKind::String => Some("Replace with raw string".to_string()),
PatternKind::Bytes => Some("Replace with raw bytes literal".to_string()),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum RegexModule {
Re,
Regex,
}
impl RegexModule {
fn is_function_taking_pattern(self, name: &str) -> bool {
match name {
"compile" | "findall" | "finditer" | "fullmatch" | "match" | "search" | "split"
| "sub" | "subn" => true,
"splititer" | "subf" | "subfn" | "template" => self == Self::Regex,
_ => false,
}
}
}
impl Display for RegexModule {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
RegexModule::Re => "re",
RegexModule::Regex => "regex",
})
}
}
impl FromStr for RegexModule {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"re" => Ok(Self::Re),
"regex" => Ok(Self::Regex),
_ => Err(()),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum PatternKind {
String,
Bytes,
}
/// RUF039
pub(crate) fn unraw_re_pattern(checker: &mut Checker, call: &ExprCall) {
let semantic = checker.semantic();
if !semantic.seen_module(Modules::RE) && !semantic.seen_module(Modules::REGEX) {
return;
}
let Some((module, func)) = regex_module_and_func(semantic, call.func.as_ref()) else {
return;
};
match call.arguments.args.as_ref().first() {
Some(Expr::StringLiteral(ExprStringLiteral { value, .. })) => {
value
.iter()
.for_each(|part| check_string(checker, part, module, func));
}
Some(Expr::BytesLiteral(ExprBytesLiteral { value, .. })) => {
value
.iter()
.for_each(|part| check_bytes(checker, part, module, func));
}
_ => {}
}
}
fn regex_module_and_func<'model>(
semantic: &SemanticModel<'model>,
expr: &'model Expr,
) -> Option<(RegexModule, &'model str)> {
let qualified_name = semantic.resolve_qualified_name(expr)?;
if let [module, func] = qualified_name.segments() {
let module = RegexModule::from_str(module).ok()?;
if !module.is_function_taking_pattern(func) {
return None;
}
return Some((module, func));
}
None
}
fn check_string(checker: &mut Checker, literal: &StringLiteral, module: RegexModule, func: &str) {
if literal.flags.prefix().is_raw() {
return;
}
let kind = PatternKind::String;
let func = func.to_string();
let range = literal.range;
let diagnostic = Diagnostic::new(UnrawRePattern { module, func, kind }, range);
checker.diagnostics.push(diagnostic);
}
fn check_bytes(checker: &mut Checker, literal: &BytesLiteral, module: RegexModule, func: &str) {
if literal.flags.prefix().is_raw() {
return;
}
let kind = PatternKind::Bytes;
let func = func.to_string();
let range = literal.range;
let diagnostic = Diagnostic::new(UnrawRePattern { module, func, kind }, range);
checker.diagnostics.push(diagnostic);
}

View file

@ -0,0 +1,211 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF039.py:5:12: RUF039 First argument to `re.compile()` is not raw string
|
4 | # Errors
5 | re.compile('single free-spacing', flags=re.X)
| ^^^^^^^^^^^^^^^^^^^^^ RUF039
6 | re.findall('si\ngle')
7 | re.finditer("dou\ble")
|
= help: Replace with raw string
RUF039.py:6:12: RUF039 First argument to `re.findall()` is not raw string
|
4 | # Errors
5 | re.compile('single free-spacing', flags=re.X)
6 | re.findall('si\ngle')
| ^^^^^^^^^ RUF039
7 | re.finditer("dou\ble")
8 | re.fullmatch('''t\riple single''')
|
= help: Replace with raw string
RUF039.py:7:13: RUF039 First argument to `re.finditer()` is not raw string
|
5 | re.compile('single free-spacing', flags=re.X)
6 | re.findall('si\ngle')
7 | re.finditer("dou\ble")
| ^^^^^^^^^ RUF039
8 | re.fullmatch('''t\riple single''')
9 | re.match("""\triple double""")
|
= help: Replace with raw string
RUF039.py:8:14: RUF039 First argument to `re.fullmatch()` is not raw string
|
6 | re.findall('si\ngle')
7 | re.finditer("dou\ble")
8 | re.fullmatch('''t\riple single''')
| ^^^^^^^^^^^^^^^^^^^^ RUF039
9 | re.match("""\triple double""")
10 | re.search('two', 'args')
|
= help: Replace with raw string
RUF039.py:9:10: RUF039 First argument to `re.match()` is not raw string
|
7 | re.finditer("dou\ble")
8 | re.fullmatch('''t\riple single''')
9 | re.match("""\triple double""")
| ^^^^^^^^^^^^^^^^^^^^ RUF039
10 | re.search('two', 'args')
11 | re.split("raw", r'second')
|
= help: Replace with raw string
RUF039.py:10:11: RUF039 First argument to `re.search()` is not raw string
|
8 | re.fullmatch('''t\riple single''')
9 | re.match("""\triple double""")
10 | re.search('two', 'args')
| ^^^^^ RUF039
11 | re.split("raw", r'second')
12 | re.sub(u'''nicode''', u"f(?i)rst")
|
= help: Replace with raw string
RUF039.py:11:10: RUF039 First argument to `re.split()` is not raw string
|
9 | re.match("""\triple double""")
10 | re.search('two', 'args')
11 | re.split("raw", r'second')
| ^^^^^ RUF039
12 | re.sub(u'''nicode''', u"f(?i)rst")
13 | re.subn(b"""ytes are""", f"\u006e")
|
= help: Replace with raw string
RUF039.py:12:8: RUF039 First argument to `re.sub()` is not raw string
|
10 | re.search('two', 'args')
11 | re.split("raw", r'second')
12 | re.sub(u'''nicode''', u"f(?i)rst")
| ^^^^^^^^^^^^^ RUF039
13 | re.subn(b"""ytes are""", f"\u006e")
|
= help: Replace with raw string
RUF039.py:13:9: RUF039 First argument to `re.subn()` is not raw bytes literal
|
11 | re.split("raw", r'second')
12 | re.sub(u'''nicode''', u"f(?i)rst")
13 | re.subn(b"""ytes are""", f"\u006e")
| ^^^^^^^^^^^^^^^ RUF039
14 |
15 | regex.compile('single free-spacing', flags=regex.X)
|
= help: Replace with raw bytes literal
RUF039.py:15:15: RUF039 First argument to `regex.compile()` is not raw string
|
13 | re.subn(b"""ytes are""", f"\u006e")
14 |
15 | regex.compile('single free-spacing', flags=regex.X)
| ^^^^^^^^^^^^^^^^^^^^^ RUF039
16 | regex.findall('si\ngle')
17 | regex.finditer("dou\ble")
|
= help: Replace with raw string
RUF039.py:16:15: RUF039 First argument to `regex.findall()` is not raw string
|
15 | regex.compile('single free-spacing', flags=regex.X)
16 | regex.findall('si\ngle')
| ^^^^^^^^^ RUF039
17 | regex.finditer("dou\ble")
18 | regex.fullmatch('''t\riple single''')
|
= help: Replace with raw string
RUF039.py:17:16: RUF039 First argument to `regex.finditer()` is not raw string
|
15 | regex.compile('single free-spacing', flags=regex.X)
16 | regex.findall('si\ngle')
17 | regex.finditer("dou\ble")
| ^^^^^^^^^ RUF039
18 | regex.fullmatch('''t\riple single''')
19 | regex.match("""\triple double""")
|
= help: Replace with raw string
RUF039.py:18:17: RUF039 First argument to `regex.fullmatch()` is not raw string
|
16 | regex.findall('si\ngle')
17 | regex.finditer("dou\ble")
18 | regex.fullmatch('''t\riple single''')
| ^^^^^^^^^^^^^^^^^^^^ RUF039
19 | regex.match("""\triple double""")
20 | regex.search('two', 'args')
|
= help: Replace with raw string
RUF039.py:19:13: RUF039 First argument to `regex.match()` is not raw string
|
17 | regex.finditer("dou\ble")
18 | regex.fullmatch('''t\riple single''')
19 | regex.match("""\triple double""")
| ^^^^^^^^^^^^^^^^^^^^ RUF039
20 | regex.search('two', 'args')
21 | regex.split("raw", r'second')
|
= help: Replace with raw string
RUF039.py:20:14: RUF039 First argument to `regex.search()` is not raw string
|
18 | regex.fullmatch('''t\riple single''')
19 | regex.match("""\triple double""")
20 | regex.search('two', 'args')
| ^^^^^ RUF039
21 | regex.split("raw", r'second')
22 | regex.sub(u'''nicode''', u"f(?i)rst")
|
= help: Replace with raw string
RUF039.py:21:13: RUF039 First argument to `regex.split()` is not raw string
|
19 | regex.match("""\triple double""")
20 | regex.search('two', 'args')
21 | regex.split("raw", r'second')
| ^^^^^ RUF039
22 | regex.sub(u'''nicode''', u"f(?i)rst")
23 | regex.subn(b"""ytes are""", f"\u006e")
|
= help: Replace with raw string
RUF039.py:22:11: RUF039 First argument to `regex.sub()` is not raw string
|
20 | regex.search('two', 'args')
21 | regex.split("raw", r'second')
22 | regex.sub(u'''nicode''', u"f(?i)rst")
| ^^^^^^^^^^^^^ RUF039
23 | regex.subn(b"""ytes are""", f"\u006e")
|
= help: Replace with raw string
RUF039.py:23:12: RUF039 First argument to `regex.subn()` is not raw bytes literal
|
21 | regex.split("raw", r'second')
22 | regex.sub(u'''nicode''', u"f(?i)rst")
23 | regex.subn(b"""ytes are""", f"\u006e")
| ^^^^^^^^^^^^^^^ RUF039
24 |
25 | regex.template("""(?m)
|
= help: Replace with raw bytes literal
RUF039.py:25:16: RUF039 First argument to `regex.template()` is not raw string
|
23 | regex.subn(b"""ytes are""", f"\u006e")
24 |
25 | regex.template("""(?m)
| ________________^
26 | | (?:ulti)?
27 | | (?=(?<!(?<=(?!l)))
28 | | l(?i:ne)
29 | | """, flags = regex.X)
| |___^ RUF039
|
= help: Replace with raw string

View file

@ -0,0 +1,285 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF039_concat.py:5:5: RUF039 First argument to `re.compile()` is not raw string
|
4 | re.compile(
5 | 'implicit'
| ^^^^^^^^^^ RUF039
6 | 'concatenation'
7 | )
|
= help: Replace with raw string
RUF039_concat.py:6:5: RUF039 First argument to `re.compile()` is not raw string
|
4 | re.compile(
5 | 'implicit'
6 | 'concatenation'
| ^^^^^^^^^^^^^^^ RUF039
7 | )
8 | re.findall(
|
= help: Replace with raw string
RUF039_concat.py:12:5: RUF039 First argument to `re.findall()` is not raw string
|
10 | multiline
11 | '''
12 | """
| _____^
13 | | concatenation
14 | | """
| |_______^ RUF039
15 | )
16 | re.finditer(
|
= help: Replace with raw string
RUF039_concat.py:26:5: RUF039 First argument to `re.match()` is not raw bytes literal
|
24 | )
25 | re.match(
26 | b'reak'
| ^^^^^^^ RUF039
27 | br'eak'
28 | )
|
= help: Replace with raw bytes literal
RUF039_concat.py:30:8: RUF039 First argument to `re.search()` is not raw string
|
28 | )
29 | re.search(
30 | r''u''
| ^^^ RUF039
31 | '''okay?'''
32 | )
|
= help: Replace with raw string
RUF039_concat.py:31:5: RUF039 First argument to `re.search()` is not raw string
|
29 | re.search(
30 | r''u''
31 | '''okay?'''
| ^^^^^^^^^^^ RUF039
32 | )
33 | re.split(''U"""w"""U'')
|
= help: Replace with raw string
RUF039_concat.py:33:10: RUF039 First argument to `re.split()` is not raw string
|
31 | '''okay?'''
32 | )
33 | re.split(''U"""w"""U'')
| ^^ RUF039
34 | re.sub(
35 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:33:12: RUF039 First argument to `re.split()` is not raw string
|
31 | '''okay?'''
32 | )
33 | re.split(''U"""w"""U'')
| ^^^^^^^^ RUF039
34 | re.sub(
35 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:33:20: RUF039 First argument to `re.split()` is not raw string
|
31 | '''okay?'''
32 | )
33 | re.split(''U"""w"""U'')
| ^^^ RUF039
34 | re.sub(
35 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:35:5: RUF039 First argument to `re.sub()` is not raw string
|
33 | re.split(''U"""w"""U'')
34 | re.sub(
35 | "I''m o"
| ^^^^^^^^ RUF039
36 | 'utta ideas'
37 | )
|
= help: Replace with raw string
RUF039_concat.py:36:5: RUF039 First argument to `re.sub()` is not raw string
|
34 | re.sub(
35 | "I''m o"
36 | 'utta ideas'
| ^^^^^^^^^^^^ RUF039
37 | )
38 | re.subn("()"r' am I'"??")
|
= help: Replace with raw string
RUF039_concat.py:38:9: RUF039 First argument to `re.subn()` is not raw string
|
36 | 'utta ideas'
37 | )
38 | re.subn("()"r' am I'"??")
| ^^^^ RUF039
|
= help: Replace with raw string
RUF039_concat.py:38:21: RUF039 First argument to `re.subn()` is not raw string
|
36 | 'utta ideas'
37 | )
38 | re.subn("()"r' am I'"??")
| ^^^^ RUF039
|
= help: Replace with raw string
RUF039_concat.py:45:5: RUF039 First argument to `regex.compile()` is not raw string
|
44 | regex.compile(
45 | 'implicit'
| ^^^^^^^^^^ RUF039
46 | 'concatenation'
47 | )
|
= help: Replace with raw string
RUF039_concat.py:46:5: RUF039 First argument to `regex.compile()` is not raw string
|
44 | regex.compile(
45 | 'implicit'
46 | 'concatenation'
| ^^^^^^^^^^^^^^^ RUF039
47 | )
48 | regex.findall(
|
= help: Replace with raw string
RUF039_concat.py:52:5: RUF039 First argument to `regex.findall()` is not raw string
|
50 | multiline
51 | '''
52 | """
| _____^
53 | | concatenation
54 | | """
| |_______^ RUF039
55 | )
56 | regex.finditer(
|
= help: Replace with raw string
RUF039_concat.py:66:5: RUF039 First argument to `regex.match()` is not raw bytes literal
|
64 | )
65 | regex.match(
66 | b'reak'
| ^^^^^^^ RUF039
67 | br'eak'
68 | )
|
= help: Replace with raw bytes literal
RUF039_concat.py:70:8: RUF039 First argument to `regex.search()` is not raw string
|
68 | )
69 | regex.search(
70 | r''u''
| ^^^ RUF039
71 | '''okay?'''
72 | )
|
= help: Replace with raw string
RUF039_concat.py:71:5: RUF039 First argument to `regex.search()` is not raw string
|
69 | regex.search(
70 | r''u''
71 | '''okay?'''
| ^^^^^^^^^^^ RUF039
72 | )
73 | regex.split(''U"""w"""U'')
|
= help: Replace with raw string
RUF039_concat.py:73:13: RUF039 First argument to `regex.split()` is not raw string
|
71 | '''okay?'''
72 | )
73 | regex.split(''U"""w"""U'')
| ^^ RUF039
74 | regex.sub(
75 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:73:15: RUF039 First argument to `regex.split()` is not raw string
|
71 | '''okay?'''
72 | )
73 | regex.split(''U"""w"""U'')
| ^^^^^^^^ RUF039
74 | regex.sub(
75 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:73:23: RUF039 First argument to `regex.split()` is not raw string
|
71 | '''okay?'''
72 | )
73 | regex.split(''U"""w"""U'')
| ^^^ RUF039
74 | regex.sub(
75 | "I''m o"
|
= help: Replace with raw string
RUF039_concat.py:75:5: RUF039 First argument to `regex.sub()` is not raw string
|
73 | regex.split(''U"""w"""U'')
74 | regex.sub(
75 | "I''m o"
| ^^^^^^^^ RUF039
76 | 'utta ideas'
77 | )
|
= help: Replace with raw string
RUF039_concat.py:76:5: RUF039 First argument to `regex.sub()` is not raw string
|
74 | regex.sub(
75 | "I''m o"
76 | 'utta ideas'
| ^^^^^^^^^^^^ RUF039
77 | )
78 | regex.subn("()"r' am I'"??")
|
= help: Replace with raw string
RUF039_concat.py:78:12: RUF039 First argument to `regex.subn()` is not raw string
|
76 | 'utta ideas'
77 | )
78 | regex.subn("()"r' am I'"??")
| ^^^^ RUF039
|
= help: Replace with raw string
RUF039_concat.py:78:24: RUF039 First argument to `regex.subn()` is not raw string
|
76 | 'utta ideas'
77 | )
78 | regex.subn("()"r' am I'"??")
| ^^^^ RUF039
|
= help: Replace with raw string

View file

@ -1281,6 +1281,7 @@ impl<'a> SemanticModel<'a> {
"pandas" => self.seen.insert(Modules::PANDAS), "pandas" => self.seen.insert(Modules::PANDAS),
"pytest" => self.seen.insert(Modules::PYTEST), "pytest" => self.seen.insert(Modules::PYTEST),
"re" => self.seen.insert(Modules::RE), "re" => self.seen.insert(Modules::RE),
"regex" => self.seen.insert(Modules::REGEX),
"six" => self.seen.insert(Modules::SIX), "six" => self.seen.insert(Modules::SIX),
"subprocess" => self.seen.insert(Modules::SUBPROCESS), "subprocess" => self.seen.insert(Modules::SUBPROCESS),
"tarfile" => self.seen.insert(Modules::TARFILE), "tarfile" => self.seen.insert(Modules::TARFILE),
@ -1858,6 +1859,7 @@ bitflags! {
const MARKUPSAFE = 1 << 23; const MARKUPSAFE = 1 << 23;
const FLASK = 1 << 24; const FLASK = 1 << 24;
const ATTRS = 1 << 25; const ATTRS = 1 << 25;
const REGEX = 1 << 26;
} }
} }

1
ruff.schema.json generated
View file

@ -3836,6 +3836,7 @@
"RUF035", "RUF035",
"RUF036", "RUF036",
"RUF038", "RUF038",
"RUF039",
"RUF04", "RUF04",
"RUF048", "RUF048",
"RUF1", "RUF1",