mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-02 18:02:23 +00:00
[ruff
] Ambiguous pattern passed to pytest.raises()
(RUF043
) (#14966)
This commit is contained in:
parent
c0b7c36d43
commit
ac81c72bf3
11 changed files with 575 additions and 3 deletions
91
crates/ruff_linter/resources/test/fixtures/ruff/RUF043.py
vendored
Normal file
91
crates/ruff_linter/resources/test/fixtures/ruff/RUF043.py
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_foo():
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
with pytest.raises(FooAtTheEnd, match="foo."): ...
|
||||||
|
with pytest.raises(PackageExtraSpecifier, match="Install `foo[bar]` to enjoy all features"): ...
|
||||||
|
with pytest.raises(InnocentQuestion, match="Did you mean to use `Literal` instead?"): ...
|
||||||
|
|
||||||
|
with pytest.raises(StringConcatenation, match="Huh"
|
||||||
|
"?"): ...
|
||||||
|
with pytest.raises(ManuallyEscapedWindowsPathToDotFile, match="C:\\\\Users\\\\Foo\\\\.config"): ...
|
||||||
|
|
||||||
|
with pytest.raises(MiddleDot, match="foo.bar"): ...
|
||||||
|
with pytest.raises(EndDot, match="foobar."): ...
|
||||||
|
with pytest.raises(EscapedFollowedByUnescaped, match="foo\\.*bar"): ...
|
||||||
|
with pytest.raises(UnescapedFollowedByEscaped, match="foo.\\*bar"): ...
|
||||||
|
|
||||||
|
|
||||||
|
## Metasequences
|
||||||
|
|
||||||
|
with pytest.raises(StartOfInput, match="foo\\Abar"): ...
|
||||||
|
with pytest.raises(WordBoundary, match="foo\\bbar"): ...
|
||||||
|
with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
with pytest.raises(NonWordCharacter, match="foo\\Wbar"): ...
|
||||||
|
with pytest.raises(EndOfInput, match="foo\\zbar"): ...
|
||||||
|
|
||||||
|
with pytest.raises(StartOfInput2, match="foobar\\A"): ...
|
||||||
|
with pytest.raises(WordBoundary2, match="foobar\\b"): ...
|
||||||
|
with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
with pytest.raises(NonWordCharacter2, match="foobar\\W"): ...
|
||||||
|
with pytest.raises(EndOfInput2, match="foobar\\z"): ...
|
||||||
|
|
||||||
|
|
||||||
|
### Acceptable false positives
|
||||||
|
|
||||||
|
with pytest.raises(NameEscape, match="\\N{EN DASH}"): ...
|
||||||
|
|
||||||
|
|
||||||
|
### No errors
|
||||||
|
|
||||||
|
with pytest.raises(NoMatch): ...
|
||||||
|
with pytest.raises(NonLiteral, match=pattern): ...
|
||||||
|
with pytest.raises(FunctionCall, match=frobnicate("qux")): ...
|
||||||
|
with pytest.raises(ReEscaped, match=re.escape("foobar")): ...
|
||||||
|
with pytest.raises(RawString, match=r"fo()bar"): ...
|
||||||
|
with pytest.raises(RawStringPart, match=r"foo" '\bar'): ...
|
||||||
|
with pytest.raises(NoMetacharacters, match="foobar"): ...
|
||||||
|
with pytest.raises(EndBackslash, match="foobar\\"): ...
|
||||||
|
|
||||||
|
with pytest.raises(ManuallyEscaped, match="some\\.fully\\.qualified\\.name"): ...
|
||||||
|
with pytest.raises(ManuallyEscapedWindowsPath, match="C:\\\\Users\\\\Foo\\\\file\\.py"): ...
|
||||||
|
|
||||||
|
with pytest.raises(MiddleEscapedDot, match="foo\\.bar"): ...
|
||||||
|
with pytest.raises(MiddleEscapedBackslash, match="foo\\\\bar"): ...
|
||||||
|
with pytest.raises(EndEscapedDot, match="foobar\\."): ...
|
||||||
|
with pytest.raises(EndEscapedBackslash, match="foobar\\\\"): ...
|
||||||
|
|
||||||
|
|
||||||
|
## Not-so-special metasequences
|
||||||
|
|
||||||
|
with pytest.raises(Alert, match="\\f"): ...
|
||||||
|
with pytest.raises(FormFeed, match="\\f"): ...
|
||||||
|
with pytest.raises(Newline, match="\\n"): ...
|
||||||
|
with pytest.raises(CarriageReturn, match="\\r"): ...
|
||||||
|
with pytest.raises(Tab, match="\\t"): ...
|
||||||
|
with pytest.raises(VerticalTab, match="\\v"): ...
|
||||||
|
with pytest.raises(HexEscape, match="\\xFF"): ...
|
||||||
|
with pytest.raises(_16BitUnicodeEscape, match="\\FFFF"): ...
|
||||||
|
with pytest.raises(_32BitUnicodeEscape, match="\\0010FFFF"): ...
|
||||||
|
|
||||||
|
## Escaped metasequences
|
||||||
|
|
||||||
|
with pytest.raises(Whitespace, match="foo\\\\sbar"): ...
|
||||||
|
with pytest.raises(NonWhitespace, match="foo\\\\Sbar"): ...
|
||||||
|
|
||||||
|
## Work by accident
|
||||||
|
|
||||||
|
with pytest.raises(OctalEscape, match="\\042"): ...
|
|
@ -1105,6 +1105,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::BatchedWithoutExplicitStrict) {
|
if checker.enabled(Rule::BatchedWithoutExplicitStrict) {
|
||||||
flake8_bugbear::rules::batched_without_explicit_strict(checker, call);
|
flake8_bugbear::rules::batched_without_explicit_strict(checker, call);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::PytestRaisesAmbiguousPattern) {
|
||||||
|
ruff::rules::pytest_raises_ambiguous_pattern(checker, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Dict(dict) => {
|
Expr::Dict(dict) => {
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
|
|
|
@ -985,6 +985,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
|
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
|
||||||
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
|
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
|
||||||
(Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral),
|
(Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral),
|
||||||
|
(Ruff, "043") => (RuleGroup::Preview, rules::ruff::rules::PytestRaisesAmbiguousPattern),
|
||||||
(Ruff, "046") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryCastToInt),
|
(Ruff, "046") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryCastToInt),
|
||||||
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
|
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
|
||||||
(Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel),
|
(Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel),
|
||||||
|
|
|
@ -144,7 +144,7 @@ impl Violation for PytestAssertInExcept {
|
||||||
/// ...
|
/// ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// References
|
/// ## References
|
||||||
/// - [`pytest` documentation: `pytest.fail`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fail)
|
/// - [`pytest` documentation: `pytest.fail`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-fail)
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct PytestAssertAlwaysFalse;
|
pub(crate) struct PytestAssertAlwaysFalse;
|
||||||
|
|
|
@ -104,7 +104,6 @@ impl AlwaysFixableViolation for PytestIncorrectMarkParenthesesStyle {
|
||||||
///
|
///
|
||||||
/// ## References
|
/// ## References
|
||||||
/// - [`pytest` documentation: `pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-mark-usefixtures)
|
/// - [`pytest` documentation: `pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-mark-usefixtures)
|
||||||
|
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct PytestUseFixturesWithoutParameters;
|
pub(crate) struct PytestUseFixturesWithoutParameters;
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ impl Violation for PytestRaisesWithoutException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool {
|
pub(crate) fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_qualified_name(func)
|
.resolve_qualified_name(func)
|
||||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]))
|
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]))
|
||||||
|
|
|
@ -415,6 +415,7 @@ mod tests {
|
||||||
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_0.py"))]
|
#[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_1.py"))]
|
||||||
#[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))]
|
#[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))]
|
||||||
|
#[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.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__{}_{}",
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub(crate) use never_union::*;
|
||||||
pub(crate) use none_not_at_end_of_union::*;
|
pub(crate) use none_not_at_end_of_union::*;
|
||||||
pub(crate) use parenthesize_chained_operators::*;
|
pub(crate) use parenthesize_chained_operators::*;
|
||||||
pub(crate) use post_init_default::*;
|
pub(crate) use post_init_default::*;
|
||||||
|
pub(crate) use pytest_raises_ambiguous_pattern::*;
|
||||||
pub(crate) use quadratic_list_summation::*;
|
pub(crate) use quadratic_list_summation::*;
|
||||||
pub(crate) use redirected_noqa::*;
|
pub(crate) use redirected_noqa::*;
|
||||||
pub(crate) use redundant_bool_literal::*;
|
pub(crate) use redundant_bool_literal::*;
|
||||||
|
@ -71,6 +72,7 @@ mod never_union;
|
||||||
mod none_not_at_end_of_union;
|
mod none_not_at_end_of_union;
|
||||||
mod parenthesize_chained_operators;
|
mod parenthesize_chained_operators;
|
||||||
mod post_init_default;
|
mod post_init_default;
|
||||||
|
mod pytest_raises_ambiguous_pattern;
|
||||||
mod quadratic_list_summation;
|
mod quadratic_list_summation;
|
||||||
mod redirected_noqa;
|
mod redirected_noqa;
|
||||||
mod redundant_bool_literal;
|
mod redundant_bool_literal;
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_pytest_style::rules::is_pytest_raises;
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for non-raw literal string arguments passed to the `match` parameter
|
||||||
|
/// of `pytest.raises()` where the string contains at least one unescaped
|
||||||
|
/// regex metacharacter.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// The `match` argument is implicitly converted to a regex under the hood.
|
||||||
|
/// It should be made explicit whether the string is meant to be a regex or a "plain" pattern
|
||||||
|
/// by prefixing the string with the `r` suffix, escaping the metacharacter(s)
|
||||||
|
/// in the string using backslashes, or wrapping the entire string in a call to
|
||||||
|
/// `re.escape()`.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// import pytest
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// with pytest.raises(Exception, match="A full sentence."):
|
||||||
|
/// do_thing_that_raises()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// import pytest
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// with pytest.raises(Exception, match=r"A full sentence."):
|
||||||
|
/// do_thing_that_raises()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Alternatively:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// import pytest
|
||||||
|
/// import re
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// with pytest.raises(Exception, match=re.escape("A full sentence.")):
|
||||||
|
/// do_thing_that_raises()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// or:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// import pytest
|
||||||
|
/// import re
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// with pytest.raises(Exception, "A full sentence\\."):
|
||||||
|
/// do_thing_that_raises()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `re.escape`](https://docs.python.org/3/library/re.html#re.escape)
|
||||||
|
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||||
|
#[derive(ViolationMetadata)]
|
||||||
|
pub(crate) struct PytestRaisesAmbiguousPattern;
|
||||||
|
|
||||||
|
impl Violation for PytestRaisesAmbiguousPattern {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"Pattern passed to `match=` contains metacharacters but is neither escaped nor raw"
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> Option<String> {
|
||||||
|
Some("Use a raw string or `re.escape()` to make the intention explicit".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RUF043
|
||||||
|
pub(crate) fn pytest_raises_ambiguous_pattern(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
|
if !is_pytest_raises(&call.func, checker.semantic()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It *can* be passed as a positional argument if you try very hard,
|
||||||
|
// but pytest only documents it as a keyword argument, and it's quite hard pass it positionally
|
||||||
|
let Some(ast::Keyword { value, .. }) = call.arguments.find_keyword("match") else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ast::Expr::StringLiteral(string) = value else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let any_part_is_raw = string.value.iter().any(|part| part.flags.prefix().is_raw());
|
||||||
|
|
||||||
|
if any_part_is_raw || !string_has_unescaped_metacharacters(&string.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let diagnostic = Diagnostic::new(PytestRaisesAmbiguousPattern, string.range);
|
||||||
|
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_has_unescaped_metacharacters(value: &ast::StringLiteralValue) -> bool {
|
||||||
|
let mut escaped = false;
|
||||||
|
|
||||||
|
for character in value.chars() {
|
||||||
|
if escaped {
|
||||||
|
if escaped_char_is_regex_metasequence(character) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if character == '\\' {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char_is_regex_metacharacter(character) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the sequence `\<c>` means anything special:
|
||||||
|
///
|
||||||
|
/// * `\A`: Start of input
|
||||||
|
/// * `\b`, `\B`: Word boundary and non-word-boundary
|
||||||
|
/// * `\d`, `\D`: Digit and non-digit
|
||||||
|
/// * `\s`, `\S`: Whitespace and non-whitespace
|
||||||
|
/// * `\w`, `\W`: Word and non-word character
|
||||||
|
/// * `\z`: End of input
|
||||||
|
///
|
||||||
|
/// `\u`, `\U`, `\N`, `\x`, `\a`, `\f`, `\n`, `\r`, `\t`, `\v`
|
||||||
|
/// are also valid in normal strings and thus do not count.
|
||||||
|
/// `\b` means backspace only in character sets,
|
||||||
|
/// while backreferences (e.g., `\1`) are not valid without groups,
|
||||||
|
/// both of which should be caught in [`string_has_unescaped_metacharacters`].
|
||||||
|
const fn escaped_char_is_regex_metasequence(c: char) -> bool {
|
||||||
|
matches!(c, 'A' | 'b' | 'B' | 'd' | 'D' | 's' | 'S' | 'w' | 'W' | 'z')
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn char_is_regex_metacharacter(c: char) -> bool {
|
||||||
|
matches!(
|
||||||
|
c,
|
||||||
|
'.' | '^' | '$' | '*' | '+' | '?' | '{' | '[' | '\\' | '|' | '('
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,319 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF043.py:8:43: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
6 | ### Errors
|
||||||
|
7 |
|
||||||
|
8 | with pytest.raises(FooAtTheEnd, match="foo."): ...
|
||||||
|
| ^^^^^^ RUF043
|
||||||
|
9 | with pytest.raises(PackageExtraSpecifier, match="Install `foo[bar]` to enjoy all features"): ...
|
||||||
|
10 | with pytest.raises(InnocentQuestion, match="Did you mean to use `Literal` instead?"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:9:53: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
8 | with pytest.raises(FooAtTheEnd, match="foo."): ...
|
||||||
|
9 | with pytest.raises(PackageExtraSpecifier, match="Install `foo[bar]` to enjoy all features"): ...
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF043
|
||||||
|
10 | with pytest.raises(InnocentQuestion, match="Did you mean to use `Literal` instead?"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:10:48: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
8 | with pytest.raises(FooAtTheEnd, match="foo."): ...
|
||||||
|
9 | with pytest.raises(PackageExtraSpecifier, match="Install `foo[bar]` to enjoy all features"): ...
|
||||||
|
10 | with pytest.raises(InnocentQuestion, match="Did you mean to use `Literal` instead?"): ...
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF043
|
||||||
|
11 |
|
||||||
|
12 | with pytest.raises(StringConcatenation, match="Huh"
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:12:51: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
10 | with pytest.raises(InnocentQuestion, match="Did you mean to use `Literal` instead?"): ...
|
||||||
|
11 |
|
||||||
|
12 | with pytest.raises(StringConcatenation, match="Huh"
|
||||||
|
| ___________________________________________________^
|
||||||
|
13 | | "?"): ...
|
||||||
|
| |_____________________________________________________^ RUF043
|
||||||
|
14 | with pytest.raises(ManuallyEscapedWindowsPathToDotFile, match="C:\\\\Users\\\\Foo\\\\.config"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:14:67: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
12 | with pytest.raises(StringConcatenation, match="Huh"
|
||||||
|
13 | "?"): ...
|
||||||
|
14 | with pytest.raises(ManuallyEscapedWindowsPathToDotFile, match="C:\\\\Users\\\\Foo\\\\.config"): ...
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF043
|
||||||
|
15 |
|
||||||
|
16 | with pytest.raises(MiddleDot, match="foo.bar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:16:41: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
14 | with pytest.raises(ManuallyEscapedWindowsPathToDotFile, match="C:\\\\Users\\\\Foo\\\\.config"): ...
|
||||||
|
15 |
|
||||||
|
16 | with pytest.raises(MiddleDot, match="foo.bar"): ...
|
||||||
|
| ^^^^^^^^^ RUF043
|
||||||
|
17 | with pytest.raises(EndDot, match="foobar."): ...
|
||||||
|
18 | with pytest.raises(EscapedFollowedByUnescaped, match="foo\\.*bar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:17:38: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
16 | with pytest.raises(MiddleDot, match="foo.bar"): ...
|
||||||
|
17 | with pytest.raises(EndDot, match="foobar."): ...
|
||||||
|
| ^^^^^^^^^ RUF043
|
||||||
|
18 | with pytest.raises(EscapedFollowedByUnescaped, match="foo\\.*bar"): ...
|
||||||
|
19 | with pytest.raises(UnescapedFollowedByEscaped, match="foo.\\*bar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:18:58: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
16 | with pytest.raises(MiddleDot, match="foo.bar"): ...
|
||||||
|
17 | with pytest.raises(EndDot, match="foobar."): ...
|
||||||
|
18 | with pytest.raises(EscapedFollowedByUnescaped, match="foo\\.*bar"): ...
|
||||||
|
| ^^^^^^^^^^^^ RUF043
|
||||||
|
19 | with pytest.raises(UnescapedFollowedByEscaped, match="foo.\\*bar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:19:58: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
17 | with pytest.raises(EndDot, match="foobar."): ...
|
||||||
|
18 | with pytest.raises(EscapedFollowedByUnescaped, match="foo\\.*bar"): ...
|
||||||
|
19 | with pytest.raises(UnescapedFollowedByEscaped, match="foo.\\*bar"): ...
|
||||||
|
| ^^^^^^^^^^^^ RUF043
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:24:44: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
22 | ## Metasequences
|
||||||
|
23 |
|
||||||
|
24 | with pytest.raises(StartOfInput, match="foo\\Abar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
25 | with pytest.raises(WordBoundary, match="foo\\bbar"): ...
|
||||||
|
26 | with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:25:44: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
24 | with pytest.raises(StartOfInput, match="foo\\Abar"): ...
|
||||||
|
25 | with pytest.raises(WordBoundary, match="foo\\bbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
26 | with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
27 | with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:26:47: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
24 | with pytest.raises(StartOfInput, match="foo\\Abar"): ...
|
||||||
|
25 | with pytest.raises(WordBoundary, match="foo\\bbar"): ...
|
||||||
|
26 | with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
27 | with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
28 | with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:27:37: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
25 | with pytest.raises(WordBoundary, match="foo\\bbar"): ...
|
||||||
|
26 | with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
27 | with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
28 | with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
29 | with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:28:40: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
26 | with pytest.raises(NonWordBoundary, match="foo\\Bbar"): ...
|
||||||
|
27 | with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
28 | with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
29 | with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
30 | with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:29:42: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
27 | with pytest.raises(Digit, match="foo\\dbar"): ...
|
||||||
|
28 | with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
29 | with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
30 | with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
31 | with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:30:45: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
28 | with pytest.raises(NonDigit, match="foo\\Dbar"): ...
|
||||||
|
29 | with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
30 | with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
31 | with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
32 | with pytest.raises(NonWordCharacter, match="foo\\Wbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:31:45: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
29 | with pytest.raises(Whitespace, match="foo\\sbar"): ...
|
||||||
|
30 | with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
31 | with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
32 | with pytest.raises(NonWordCharacter, match="foo\\Wbar"): ...
|
||||||
|
33 | with pytest.raises(EndOfInput, match="foo\\zbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:32:48: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
30 | with pytest.raises(NonWhitespace, match="foo\\Sbar"): ...
|
||||||
|
31 | with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
32 | with pytest.raises(NonWordCharacter, match="foo\\Wbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
33 | with pytest.raises(EndOfInput, match="foo\\zbar"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:33:42: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
31 | with pytest.raises(WordCharacter, match="foo\\wbar"): ...
|
||||||
|
32 | with pytest.raises(NonWordCharacter, match="foo\\Wbar"): ...
|
||||||
|
33 | with pytest.raises(EndOfInput, match="foo\\zbar"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
34 |
|
||||||
|
35 | with pytest.raises(StartOfInput2, match="foobar\\A"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:35:45: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
33 | with pytest.raises(EndOfInput, match="foo\\zbar"): ...
|
||||||
|
34 |
|
||||||
|
35 | with pytest.raises(StartOfInput2, match="foobar\\A"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
36 | with pytest.raises(WordBoundary2, match="foobar\\b"): ...
|
||||||
|
37 | with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:36:45: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
35 | with pytest.raises(StartOfInput2, match="foobar\\A"): ...
|
||||||
|
36 | with pytest.raises(WordBoundary2, match="foobar\\b"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
37 | with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
38 | with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:37:48: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
35 | with pytest.raises(StartOfInput2, match="foobar\\A"): ...
|
||||||
|
36 | with pytest.raises(WordBoundary2, match="foobar\\b"): ...
|
||||||
|
37 | with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
38 | with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
39 | with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:38:38: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
36 | with pytest.raises(WordBoundary2, match="foobar\\b"): ...
|
||||||
|
37 | with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
38 | with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
39 | with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
40 | with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:39:41: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
37 | with pytest.raises(NonWordBoundary2, match="foobar\\B"): ...
|
||||||
|
38 | with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
39 | with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
40 | with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
41 | with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:40:43: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
38 | with pytest.raises(Digit2, match="foobar\\d"): ...
|
||||||
|
39 | with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
40 | with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
41 | with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
42 | with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:41:46: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
39 | with pytest.raises(NonDigit2, match="foobar\\D"): ...
|
||||||
|
40 | with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
41 | with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
42 | with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
43 | with pytest.raises(NonWordCharacter2, match="foobar\\W"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:42:46: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
40 | with pytest.raises(Whitespace2, match="foobar\\s"): ...
|
||||||
|
41 | with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
42 | with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
43 | with pytest.raises(NonWordCharacter2, match="foobar\\W"): ...
|
||||||
|
44 | with pytest.raises(EndOfInput2, match="foobar\\z"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:43:49: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
41 | with pytest.raises(NonWhitespace2, match="foobar\\S"): ...
|
||||||
|
42 | with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
43 | with pytest.raises(NonWordCharacter2, match="foobar\\W"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
44 | with pytest.raises(EndOfInput2, match="foobar\\z"): ...
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:44:43: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
42 | with pytest.raises(WordCharacter2, match="foobar\\w"): ...
|
||||||
|
43 | with pytest.raises(NonWordCharacter2, match="foobar\\W"): ...
|
||||||
|
44 | with pytest.raises(EndOfInput2, match="foobar\\z"): ...
|
||||||
|
| ^^^^^^^^^^^ RUF043
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
||||||
|
|
||||||
|
RUF043.py:49:42: RUF043 Pattern passed to `match=` contains metacharacters but is neither escaped nor raw
|
||||||
|
|
|
||||||
|
47 | ### Acceptable false positives
|
||||||
|
48 |
|
||||||
|
49 | with pytest.raises(NameEscape, match="\\N{EN DASH}"): ...
|
||||||
|
| ^^^^^^^^^^^^^^ RUF043
|
||||||
|
|
|
||||||
|
= help: Use a raw string or `re.escape()` to make the intention explicit
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3848,6 +3848,7 @@
|
||||||
"RUF04",
|
"RUF04",
|
||||||
"RUF040",
|
"RUF040",
|
||||||
"RUF041",
|
"RUF041",
|
||||||
|
"RUF043",
|
||||||
"RUF046",
|
"RUF046",
|
||||||
"RUF048",
|
"RUF048",
|
||||||
"RUF05",
|
"RUF05",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue