Add new rule to check for useless quote escapes (#8630)

When using the autofixer for `Q000` it does not remove the backslashes
from quotes that no longer need escaping.

This new rule checks for such backslashes (regardless whether they come
from the autofixer or not) and can remove them.

fixes #8617
This commit is contained in:
Adrian 2023-11-13 22:59:37 +01:00 committed by GitHub
parent 534fc34f11
commit 16060670b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1048 additions and 19 deletions

View file

@ -0,0 +1,45 @@
this_should_raise_Q004 = 'This is a \"string\"'
this_should_raise_Q004 = 'This is \\ a \\\"string\"'
this_is_fine = '"This" is a \"string\"'
this_is_fine = "This is a 'string'"
this_is_fine = "\"This\" is a 'string'"
this_is_fine = r'This is a \"string\"'
this_is_fine = R'This is a \"string\"'
this_should_raise_Q004 = (
'This is a'
'\"string\"'
)
# Same as above, but with f-strings
f'This is a \"string\"' # Q004
f'This is \\ a \\\"string\"' # Q004
f'"This" is a \"string\"'
f"This is a 'string'"
f"\"This\" is a 'string'"
fr'This is a \"string\"'
fR'This is a \"string\"'
this_should_raise_Q004 = (
f'This is a'
f'\"string\"' # Q004
)
# Nested f-strings (Python 3.12+)
#
# The first one is interesting because the fix for it is valid pre 3.12:
#
# f"'foo' {'nested'}"
#
# but as the actual string itself is invalid pre 3.12, we don't catch it.
f'\"foo\" {'nested'}' # Q004
f'\"foo\" {f'nested'}' # Q004
f'\"foo\" {f'\"nested\"'} \"\"' # Q004
f'normal {f'nested'} normal'
f'\"normal\" {f'nested'} normal' # Q004
f'\"normal\" {f'nested'} "double quotes"'
f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
# Make sure we do not unescape quotes
this_is_fine = 'This is an \\"escaped\\" quote'
this_should_raise_Q004 = 'This is an \\\"escaped\\\" quote with an extra backslash'

View file

@ -0,0 +1,43 @@
this_should_raise_Q004 = "This is a \'string\'"
this_should_raise_Q004 = "'This' is a \'string\'"
this_is_fine = 'This is a "string"'
this_is_fine = '\'This\' is a "string"'
this_is_fine = r"This is a \'string\'"
this_is_fine = R"This is a \'string\'"
this_should_raise_Q004 = (
"This is a"
"\'string\'"
)
# Same as above, but with f-strings
f"This is a \'string\'" # Q004
f"'This' is a \'string\'" # Q004
f'This is a "string"'
f'\'This\' is a "string"'
fr"This is a \'string\'"
fR"This is a \'string\'"
this_should_raise_Q004 = (
f"This is a"
f"\'string\'" # Q004
)
# Nested f-strings (Python 3.12+)
#
# The first one is interesting because the fix for it is valid pre 3.12:
#
# f'"foo" {"nested"}'
#
# but as the actual string itself is invalid pre 3.12, we don't catch it.
f"\'foo\' {"foo"}" # Q004
f"\'foo\' {f"foo"}" # Q004
f"\'foo\' {f"\'foo\'"} \'\'" # Q004
f"normal {f"nested"} normal"
f"\'normal\' {f"nested"} normal" # Q004
f"\'normal\' {f"nested"} 'single quotes'"
f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
# Make sure we do not unescape quotes
this_is_fine = "This is an \\'escaped\\' quote"
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"

View file

@ -115,6 +115,10 @@ pub(crate) fn check_tokens(
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
}
if settings.rules.enabled(Rule::UnnecessaryEscapedQuote) {
flake8_quotes::rules::unnecessary_escaped_quote(&mut diagnostics, tokens, locator);
}
if settings.rules.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,

View file

@ -403,6 +403,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Quotes, "001") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesMultilineString),
(Flake8Quotes, "002") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesDocstring),
(Flake8Quotes, "003") => (RuleGroup::Stable, rules::flake8_quotes::rules::AvoidableEscapedQuote),
(Flake8Quotes, "004") => (RuleGroup::Preview, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),
// flake8-annotations
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),

View file

@ -19,6 +19,7 @@ mod tests {
#[test_case(Path::new("doubles.py"))]
#[test_case(Path::new("doubles_escaped.py"))]
#[test_case(Path::new("doubles_escaped_unnecessary.py"))]
#[test_case(Path::new("doubles_implicit.py"))]
#[test_case(Path::new("doubles_multiline_string.py"))]
#[test_case(Path::new("doubles_noqa.py"))]
@ -39,6 +40,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
@ -86,6 +88,7 @@ mod tests {
#[test_case(Path::new("singles.py"))]
#[test_case(Path::new("singles_escaped.py"))]
#[test_case(Path::new("singles_escaped_unnecessary.py"))]
#[test_case(Path::new("singles_implicit.py"))]
#[test_case(Path::new("singles_multiline_string.py"))]
#[test_case(Path::new("singles_noqa.py"))]
@ -106,6 +109,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
@ -139,6 +143,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
@ -172,6 +177,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;

View file

@ -7,9 +7,10 @@ use ruff_source_file::Locator;
use ruff_text_size::TextRange;
use crate::lex::docstring_detection::StateMachine;
use crate::settings::LinterSettings;
use super::super::settings::Quote;
/// ## What it does
/// Checks for strings that include escaped quotes, and suggests changing
/// the quote style to avoid the need to escape them.
@ -48,6 +49,43 @@ impl AlwaysFixableViolation for AvoidableEscapedQuote {
}
}
/// ## What it does
/// Checks for strings that include unnecessarily escaped quotes.
///
/// ## Why is this bad?
/// If a string contains an escaped quote that doesn't match the quote
/// character used for the string, it's unnecessary and can be removed.
///
/// ## Example
/// ```python
/// foo = "bar\'s"
/// ```
///
/// Use instead:
/// ```python
/// foo = "bar's"
/// ```
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
/// formatter automatically removes unnecessary escapes, making the rule
/// redundant.
///
/// [formatter]: https://docs.astral.sh/ruff/formatter
#[violation]
pub struct UnnecessaryEscapedQuote;
impl AlwaysFixableViolation for UnnecessaryEscapedQuote {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unnecessary escape on inner quote character")
}
fn fix_title(&self) -> String {
"Remove backslash".to_string()
}
}
struct FStringContext {
/// Whether to check for escaped quotes in the f-string.
check_for_escaped_quote: bool,
@ -55,14 +93,21 @@ struct FStringContext {
start_range: TextRange,
/// The ranges of the f-string middle tokens containing escaped quotes.
middle_ranges_with_escapes: Vec<TextRange>,
/// The quote style used for the f-string
quote_style: Quote,
}
impl FStringContext {
fn new(check_for_escaped_quote: bool, fstring_start_range: TextRange) -> Self {
fn new(
check_for_escaped_quote: bool,
fstring_start_range: TextRange,
quote_style: Quote,
) -> Self {
Self {
check_for_escaped_quote,
start_range: fstring_start_range,
middle_ranges_with_escapes: vec![],
quote_style,
}
}
@ -132,21 +177,27 @@ pub(crate) fn avoidable_escaped_quote(
}
// Check if we're using the preferred quotation style.
if !leading_quote(locator.slice(tok_range))
.is_some_and(|text| text.contains(quotes_settings.inline_quotes.as_char()))
{
if !leading_quote(locator.slice(tok_range)).is_some_and(|text| {
contains_quote(text, quotes_settings.inline_quotes.as_char())
}) {
continue;
}
if string_contents.contains(quotes_settings.inline_quotes.as_char())
&& !string_contents.contains(quotes_settings.inline_quotes.opposite().as_char())
if contains_escaped_quote(string_contents, quotes_settings.inline_quotes.as_char())
&& !contains_quote(
string_contents,
quotes_settings.inline_quotes.opposite().as_char(),
)
{
let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, tok_range);
let fixed_contents = format!(
"{prefix}{quote}{value}{quote}",
prefix = kind.as_str(),
quote = quotes_settings.inline_quotes.opposite().as_char(),
value = unescape_string(string_contents)
value = unescape_string(
string_contents,
quotes_settings.inline_quotes.as_char()
)
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
fixed_contents,
@ -159,10 +210,13 @@ pub(crate) fn avoidable_escaped_quote(
let text = locator.slice(tok_range);
// Check for escaped quote only if we're using the preferred quotation
// style and it isn't a triple-quoted f-string.
let check_for_escaped_quote = text
.contains(quotes_settings.inline_quotes.as_char())
&& !is_triple_quote(text);
fstrings.push(FStringContext::new(check_for_escaped_quote, tok_range));
let check_for_escaped_quote = !is_triple_quote(text)
&& contains_quote(text, quotes_settings.inline_quotes.as_char());
fstrings.push(FStringContext::new(
check_for_escaped_quote,
tok_range,
quotes_settings.inline_quotes,
));
}
Tok::FStringMiddle {
value: string_contents,
@ -176,11 +230,15 @@ pub(crate) fn avoidable_escaped_quote(
}
// If any part of the f-string contains the opposite quote,
// we can't change the quote style in the entire f-string.
if string_contents.contains(quotes_settings.inline_quotes.opposite().as_char()) {
if contains_quote(
string_contents,
quotes_settings.inline_quotes.opposite().as_char(),
) {
context.ignore_escaped_quotes();
continue;
}
if string_contents.contains(quotes_settings.inline_quotes.as_char()) {
if contains_escaped_quote(string_contents, quotes_settings.inline_quotes.as_char())
{
context.push_fstring_middle_range(tok_range);
}
}
@ -207,7 +265,13 @@ pub(crate) fn avoidable_escaped_quote(
.middle_ranges_with_escapes
.iter()
.map(|&range| {
Edit::range_replacement(unescape_string(locator.slice(range)), range)
Edit::range_replacement(
unescape_string(
locator.slice(range),
quotes_settings.inline_quotes.as_char(),
),
range,
)
})
.chain(std::iter::once(
// `FStringEnd` edit
@ -231,13 +295,152 @@ pub(crate) fn avoidable_escaped_quote(
}
}
fn unescape_string(value: &str) -> String {
let mut fixed_contents = String::with_capacity(value.len());
/// Q004
pub(crate) fn unnecessary_escaped_quote(
diagnostics: &mut Vec<Diagnostic>,
lxr: &[LexResult],
locator: &Locator,
) {
let mut fstrings: Vec<FStringContext> = Vec::new();
let mut state_machine = StateMachine::default();
let mut chars = value.chars().peekable();
for &(ref tok, tok_range) in lxr.iter().flatten() {
let is_docstring = state_machine.consume(tok);
if is_docstring {
continue;
}
match tok {
Tok::String {
value: string_contents,
kind,
triple_quoted,
} => {
if kind.is_raw() || *triple_quoted {
continue;
}
let leading = match leading_quote(locator.slice(tok_range)) {
Some("\"") => Quote::Double,
Some("'") => Quote::Single,
_ => continue,
};
if !contains_escaped_quote(string_contents, leading.opposite().as_char()) {
continue;
}
let mut diagnostic = Diagnostic::new(UnnecessaryEscapedQuote, tok_range);
let fixed_contents = format!(
"{prefix}{quote}{value}{quote}",
prefix = kind.as_str(),
quote = leading.as_char(),
value = unescape_string(string_contents, leading.opposite().as_char())
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
fixed_contents,
tok_range,
)));
diagnostics.push(diagnostic);
}
Tok::FStringStart => {
let text = locator.slice(tok_range);
// Check for escaped quote only if we're using the preferred quotation
// style and it isn't a triple-quoted f-string.
let check_for_escaped_quote = !is_triple_quote(text);
let quote_style = if contains_quote(text, Quote::Single.as_char()) {
Quote::Single
} else {
Quote::Double
};
fstrings.push(FStringContext::new(
check_for_escaped_quote,
tok_range,
quote_style,
));
}
Tok::FStringMiddle {
value: string_contents,
is_raw,
} if !is_raw => {
let Some(context) = fstrings.last_mut() else {
continue;
};
if !context.check_for_escaped_quote {
continue;
}
if contains_escaped_quote(string_contents, context.quote_style.opposite().as_char())
{
context.push_fstring_middle_range(tok_range);
}
}
Tok::FStringEnd => {
let Some(context) = fstrings.pop() else {
continue;
};
let [first, rest @ ..] = context.middle_ranges_with_escapes.as_slice() else {
continue;
};
let mut diagnostic = Diagnostic::new(
UnnecessaryEscapedQuote,
TextRange::new(context.start_range.start(), tok_range.end()),
);
let first_edit = Edit::range_replacement(
unescape_string(
locator.slice(first),
context.quote_style.opposite().as_char(),
),
*first,
);
let rest_edits = rest.iter().map(|&range| {
Edit::range_replacement(
unescape_string(
locator.slice(range),
context.quote_style.opposite().as_char(),
),
range,
)
});
diagnostic.set_fix(Fix::safe_edits(first_edit, rest_edits));
diagnostics.push(diagnostic);
}
_ => {}
}
}
}
/// Return `true` if the haystack contains the quote.
fn contains_quote(haystack: &str, quote: char) -> bool {
memchr::memchr(quote as u8, haystack.as_bytes()).is_some()
}
/// Return `true` if the haystack contains an escaped quote.
fn contains_escaped_quote(haystack: &str, quote: char) -> bool {
for index in memchr::memchr_iter(quote as u8, haystack.as_bytes()) {
// If the quote is preceded by an even number of backslashes, it's not escaped.
if haystack.as_bytes()[..index]
.iter()
.rev()
.take_while(|&&c| c == b'\\')
.count()
% 2
!= 0
{
return true;
}
}
false
}
/// Return a modified version of the string with all quote escapes removed.
fn unescape_string(haystack: &str, quote: char) -> String {
let mut fixed_contents = String::with_capacity(haystack.len());
let mut chars = haystack.chars().peekable();
let mut backslashes = 0;
while let Some(char) = chars.next() {
if char != '\\' {
fixed_contents.push(char);
backslashes = 0;
continue;
}
// If we're at the end of the line
@ -246,9 +449,11 @@ fn unescape_string(value: &str) -> String {
continue;
};
// Remove quote escape
if matches!(*next_char, '\'' | '"') {
if *next_char == quote && backslashes % 2 == 0 {
backslashes = 0;
continue;
}
backslashes += 1;
fixed_contents.push(char);
}

View file

@ -0,0 +1,341 @@
---
source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs
---
singles_escaped_unnecessary.py:1:26: Q004 [*] Unnecessary escape on inner quote character
|
1 | this_should_raise_Q004 = "This is a \'string\'"
| ^^^^^^^^^^^^^^^^^^^^^^ Q004
2 | this_should_raise_Q004 = "'This' is a \'string\'"
3 | this_is_fine = 'This is a "string"'
|
= help: Remove backslash
Safe fix
1 |-this_should_raise_Q004 = "This is a \'string\'"
1 |+this_should_raise_Q004 = "This is a 'string'"
2 2 | this_should_raise_Q004 = "'This' is a \'string\'"
3 3 | this_is_fine = 'This is a "string"'
4 4 | this_is_fine = '\'This\' is a "string"'
singles_escaped_unnecessary.py:2:26: Q004 [*] Unnecessary escape on inner quote character
|
1 | this_should_raise_Q004 = "This is a \'string\'"
2 | this_should_raise_Q004 = "'This' is a \'string\'"
| ^^^^^^^^^^^^^^^^^^^^^^^^ Q004
3 | this_is_fine = 'This is a "string"'
4 | this_is_fine = '\'This\' is a "string"'
|
= help: Remove backslash
Safe fix
1 1 | this_should_raise_Q004 = "This is a \'string\'"
2 |-this_should_raise_Q004 = "'This' is a \'string\'"
2 |+this_should_raise_Q004 = "'This' is a 'string'"
3 3 | this_is_fine = 'This is a "string"'
4 4 | this_is_fine = '\'This\' is a "string"'
5 5 | this_is_fine = r"This is a \'string\'"
singles_escaped_unnecessary.py:9:5: Q004 [*] Unnecessary escape on inner quote character
|
7 | this_should_raise_Q004 = (
8 | "This is a"
9 | "\'string\'"
| ^^^^^^^^^^^^ Q004
10 | )
|
= help: Remove backslash
Safe fix
6 6 | this_is_fine = R"This is a \'string\'"
7 7 | this_should_raise_Q004 = (
8 8 | "This is a"
9 |- "\'string\'"
9 |+ "'string'"
10 10 | )
11 11 |
12 12 | # Same as above, but with f-strings
singles_escaped_unnecessary.py:13:1: Q004 [*] Unnecessary escape on inner quote character
|
12 | # Same as above, but with f-strings
13 | f"This is a \'string\'" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^ Q004
14 | f"'This' is a \'string\'" # Q004
15 | f'This is a "string"'
|
= help: Remove backslash
Safe fix
10 10 | )
11 11 |
12 12 | # Same as above, but with f-strings
13 |-f"This is a \'string\'" # Q004
13 |+f"This is a 'string'" # Q004
14 14 | f"'This' is a \'string\'" # Q004
15 15 | f'This is a "string"'
16 16 | f'\'This\' is a "string"'
singles_escaped_unnecessary.py:14:1: Q004 [*] Unnecessary escape on inner quote character
|
12 | # Same as above, but with f-strings
13 | f"This is a \'string\'" # Q004
14 | f"'This' is a \'string\'" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
15 | f'This is a "string"'
16 | f'\'This\' is a "string"'
|
= help: Remove backslash
Safe fix
11 11 |
12 12 | # Same as above, but with f-strings
13 13 | f"This is a \'string\'" # Q004
14 |-f"'This' is a \'string\'" # Q004
14 |+f"'This' is a 'string'" # Q004
15 15 | f'This is a "string"'
16 16 | f'\'This\' is a "string"'
17 17 | fr"This is a \'string\'"
singles_escaped_unnecessary.py:21:5: Q004 [*] Unnecessary escape on inner quote character
|
19 | this_should_raise_Q004 = (
20 | f"This is a"
21 | f"\'string\'" # Q004
| ^^^^^^^^^^^^^ Q004
22 | )
|
= help: Remove backslash
Safe fix
18 18 | fR"This is a \'string\'"
19 19 | this_should_raise_Q004 = (
20 20 | f"This is a"
21 |- f"\'string\'" # Q004
21 |+ f"'string'" # Q004
22 22 | )
23 23 |
24 24 | # Nested f-strings (Python 3.12+)
singles_escaped_unnecessary.py:31:1: Q004 [*] Unnecessary escape on inner quote character
|
29 | #
30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 | f"\'foo\' {"foo"}" # Q004
| ^^^^^^^^^^^^^^^^^^ Q004
32 | f"\'foo\' {f"foo"}" # Q004
33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
|
= help: Remove backslash
Safe fix
28 28 | # f'"foo" {"nested"}'
29 29 | #
30 30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 |-f"\'foo\' {"foo"}" # Q004
31 |+f"'foo' {"foo"}" # Q004
32 32 | f"\'foo\' {f"foo"}" # Q004
33 33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
34 34 |
singles_escaped_unnecessary.py:32:1: Q004 [*] Unnecessary escape on inner quote character
|
30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 | f"\'foo\' {"foo"}" # Q004
32 | f"\'foo\' {f"foo"}" # Q004
| ^^^^^^^^^^^^^^^^^^^ Q004
33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
|
= help: Remove backslash
Safe fix
29 29 | #
30 30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 31 | f"\'foo\' {"foo"}" # Q004
32 |-f"\'foo\' {f"foo"}" # Q004
32 |+f"'foo' {f"foo"}" # Q004
33 33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
34 34 |
35 35 | f"normal {f"nested"} normal"
singles_escaped_unnecessary.py:33:1: Q004 [*] Unnecessary escape on inner quote character
|
31 | f"\'foo\' {"foo"}" # Q004
32 | f"\'foo\' {f"foo"}" # Q004
33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
34 |
35 | f"normal {f"nested"} normal"
|
= help: Remove backslash
Safe fix
30 30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 31 | f"\'foo\' {"foo"}" # Q004
32 32 | f"\'foo\' {f"foo"}" # Q004
33 |-f"\'foo\' {f"\'foo\'"} \'\'" # Q004
33 |+f"'foo' {f"\'foo\'"} ''" # Q004
34 34 |
35 35 | f"normal {f"nested"} normal"
36 36 | f"\'normal\' {f"nested"} normal" # Q004
singles_escaped_unnecessary.py:33:12: Q004 [*] Unnecessary escape on inner quote character
|
31 | f"\'foo\' {"foo"}" # Q004
32 | f"\'foo\' {f"foo"}" # Q004
33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
| ^^^^^^^^^^ Q004
34 |
35 | f"normal {f"nested"} normal"
|
= help: Remove backslash
Safe fix
30 30 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
31 31 | f"\'foo\' {"foo"}" # Q004
32 32 | f"\'foo\' {f"foo"}" # Q004
33 |-f"\'foo\' {f"\'foo\'"} \'\'" # Q004
33 |+f"\'foo\' {f"'foo'"} \'\'" # Q004
34 34 |
35 35 | f"normal {f"nested"} normal"
36 36 | f"\'normal\' {f"nested"} normal" # Q004
singles_escaped_unnecessary.py:36:1: Q004 [*] Unnecessary escape on inner quote character
|
35 | f"normal {f"nested"} normal"
36 | f"\'normal\' {f"nested"} normal" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
37 | f"\'normal\' {f"nested"} 'single quotes'"
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
|
= help: Remove backslash
Safe fix
33 33 | f"\'foo\' {f"\'foo\'"} \'\'" # Q004
34 34 |
35 35 | f"normal {f"nested"} normal"
36 |-f"\'normal\' {f"nested"} normal" # Q004
36 |+f"'normal' {f"nested"} normal" # Q004
37 37 | f"\'normal\' {f"nested"} 'single quotes'"
38 38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
singles_escaped_unnecessary.py:37:1: Q004 [*] Unnecessary escape on inner quote character
|
35 | f"normal {f"nested"} normal"
36 | f"\'normal\' {f"nested"} normal" # Q004
37 | f"\'normal\' {f"nested"} 'single quotes'"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
= help: Remove backslash
Safe fix
34 34 |
35 35 | f"normal {f"nested"} normal"
36 36 | f"\'normal\' {f"nested"} normal" # Q004
37 |-f"\'normal\' {f"nested"} 'single quotes'"
37 |+f"'normal' {f"nested"} 'single quotes'"
38 38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
40 40 |
singles_escaped_unnecessary.py:38:1: Q004 [*] Unnecessary escape on inner quote character
|
36 | f"\'normal\' {f"nested"} normal" # Q004
37 | f"\'normal\' {f"nested"} 'single quotes'"
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
= help: Remove backslash
Safe fix
35 35 | f"normal {f"nested"} normal"
36 36 | f"\'normal\' {f"nested"} normal" # Q004
37 37 | f"\'normal\' {f"nested"} 'single quotes'"
38 |-f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
38 |+f"'normal' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
40 40 |
41 41 | # Make sure we do not unescape quotes
singles_escaped_unnecessary.py:38:15: Q004 [*] Unnecessary escape on inner quote character
|
36 | f"\'normal\' {f"nested"} normal" # Q004
37 | f"\'normal\' {f"nested"} 'single quotes'"
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
= help: Remove backslash
Safe fix
35 35 | f"normal {f"nested"} normal"
36 36 | f"\'normal\' {f"nested"} normal" # Q004
37 37 | f"\'normal\' {f"nested"} 'single quotes'"
38 |-f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
38 |+f"\'normal\' {f"'nested' {"other"} normal"} 'single quotes'" # Q004
39 39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
40 40 |
41 41 | # Make sure we do not unescape quotes
singles_escaped_unnecessary.py:39:1: Q004 [*] Unnecessary escape on inner quote character
|
37 | f"\'normal\' {f"nested"} 'single quotes'"
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
40 |
41 | # Make sure we do not unescape quotes
|
= help: Remove backslash
Safe fix
36 36 | f"\'normal\' {f"nested"} normal" # Q004
37 37 | f"\'normal\' {f"nested"} 'single quotes'"
38 38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 |-f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
39 |+f"'normal' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
40 40 |
41 41 | # Make sure we do not unescape quotes
42 42 | this_is_fine = "This is an \\'escaped\\' quote"
singles_escaped_unnecessary.py:39:15: Q004 [*] Unnecessary escape on inner quote character
|
37 | f"\'normal\' {f"nested"} 'single quotes'"
38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 | f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
40 |
41 | # Make sure we do not unescape quotes
|
= help: Remove backslash
Safe fix
36 36 | f"\'normal\' {f"nested"} normal" # Q004
37 37 | f"\'normal\' {f"nested"} 'single quotes'"
38 38 | f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
39 |-f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
39 |+f"\'normal\' {f"'nested' {"other"} 'single quotes'"} normal" # Q004
40 40 |
41 41 | # Make sure we do not unescape quotes
42 42 | this_is_fine = "This is an \\'escaped\\' quote"
singles_escaped_unnecessary.py:43:26: Q004 [*] Unnecessary escape on inner quote character
|
41 | # Make sure we do not unescape quotes
42 | this_is_fine = "This is an \\'escaped\\' quote"
43 | this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
|
= help: Remove backslash
Safe fix
40 40 |
41 41 | # Make sure we do not unescape quotes
42 42 | this_is_fine = "This is an \\'escaped\\' quote"
43 |-this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
43 |+this_should_raise_Q004 = "This is an \\'escaped\\' quote with an extra backslash"

View file

@ -0,0 +1,382 @@
---
source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs
---
doubles_escaped_unnecessary.py:1:26: Q004 [*] Unnecessary escape on inner quote character
|
1 | this_should_raise_Q004 = 'This is a \"string\"'
| ^^^^^^^^^^^^^^^^^^^^^^ Q004
2 | this_should_raise_Q004 = 'This is \\ a \\\"string\"'
3 | this_is_fine = '"This" is a \"string\"'
|
= help: Remove backslash
Safe fix
1 |-this_should_raise_Q004 = 'This is a \"string\"'
1 |+this_should_raise_Q004 = 'This is a "string"'
2 2 | this_should_raise_Q004 = 'This is \\ a \\\"string\"'
3 3 | this_is_fine = '"This" is a \"string\"'
4 4 | this_is_fine = "This is a 'string'"
doubles_escaped_unnecessary.py:2:26: Q004 [*] Unnecessary escape on inner quote character
|
1 | this_should_raise_Q004 = 'This is a \"string\"'
2 | this_should_raise_Q004 = 'This is \\ a \\\"string\"'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
3 | this_is_fine = '"This" is a \"string\"'
4 | this_is_fine = "This is a 'string'"
|
= help: Remove backslash
Safe fix
1 1 | this_should_raise_Q004 = 'This is a \"string\"'
2 |-this_should_raise_Q004 = 'This is \\ a \\\"string\"'
2 |+this_should_raise_Q004 = 'This is \\ a \\"string"'
3 3 | this_is_fine = '"This" is a \"string\"'
4 4 | this_is_fine = "This is a 'string'"
5 5 | this_is_fine = "\"This\" is a 'string'"
doubles_escaped_unnecessary.py:3:16: Q004 [*] Unnecessary escape on inner quote character
|
1 | this_should_raise_Q004 = 'This is a \"string\"'
2 | this_should_raise_Q004 = 'This is \\ a \\\"string\"'
3 | this_is_fine = '"This" is a \"string\"'
| ^^^^^^^^^^^^^^^^^^^^^^^^ Q004
4 | this_is_fine = "This is a 'string'"
5 | this_is_fine = "\"This\" is a 'string'"
|
= help: Remove backslash
Safe fix
1 1 | this_should_raise_Q004 = 'This is a \"string\"'
2 2 | this_should_raise_Q004 = 'This is \\ a \\\"string\"'
3 |-this_is_fine = '"This" is a \"string\"'
3 |+this_is_fine = '"This" is a "string"'
4 4 | this_is_fine = "This is a 'string'"
5 5 | this_is_fine = "\"This\" is a 'string'"
6 6 | this_is_fine = r'This is a \"string\"'
doubles_escaped_unnecessary.py:10:5: Q004 [*] Unnecessary escape on inner quote character
|
8 | this_should_raise_Q004 = (
9 | 'This is a'
10 | '\"string\"'
| ^^^^^^^^^^^^ Q004
11 | )
|
= help: Remove backslash
Safe fix
7 7 | this_is_fine = R'This is a \"string\"'
8 8 | this_should_raise_Q004 = (
9 9 | 'This is a'
10 |- '\"string\"'
10 |+ '"string"'
11 11 | )
12 12 |
13 13 | # Same as above, but with f-strings
doubles_escaped_unnecessary.py:14:1: Q004 [*] Unnecessary escape on inner quote character
|
13 | # Same as above, but with f-strings
14 | f'This is a \"string\"' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^ Q004
15 | f'This is \\ a \\\"string\"' # Q004
16 | f'"This" is a \"string\"'
|
= help: Remove backslash
Safe fix
11 11 | )
12 12 |
13 13 | # Same as above, but with f-strings
14 |-f'This is a \"string\"' # Q004
14 |+f'This is a "string"' # Q004
15 15 | f'This is \\ a \\\"string\"' # Q004
16 16 | f'"This" is a \"string\"'
17 17 | f"This is a 'string'"
doubles_escaped_unnecessary.py:15:1: Q004 [*] Unnecessary escape on inner quote character
|
13 | # Same as above, but with f-strings
14 | f'This is a \"string\"' # Q004
15 | f'This is \\ a \\\"string\"' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
16 | f'"This" is a \"string\"'
17 | f"This is a 'string'"
|
= help: Remove backslash
Safe fix
12 12 |
13 13 | # Same as above, but with f-strings
14 14 | f'This is a \"string\"' # Q004
15 |-f'This is \\ a \\\"string\"' # Q004
15 |+f'This is \\ a \\"string"' # Q004
16 16 | f'"This" is a \"string\"'
17 17 | f"This is a 'string'"
18 18 | f"\"This\" is a 'string'"
doubles_escaped_unnecessary.py:16:1: Q004 [*] Unnecessary escape on inner quote character
|
14 | f'This is a \"string\"' # Q004
15 | f'This is \\ a \\\"string\"' # Q004
16 | f'"This" is a \"string\"'
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
17 | f"This is a 'string'"
18 | f"\"This\" is a 'string'"
|
= help: Remove backslash
Safe fix
13 13 | # Same as above, but with f-strings
14 14 | f'This is a \"string\"' # Q004
15 15 | f'This is \\ a \\\"string\"' # Q004
16 |-f'"This" is a \"string\"'
16 |+f'"This" is a "string"'
17 17 | f"This is a 'string'"
18 18 | f"\"This\" is a 'string'"
19 19 | fr'This is a \"string\"'
doubles_escaped_unnecessary.py:23:5: Q004 [*] Unnecessary escape on inner quote character
|
21 | this_should_raise_Q004 = (
22 | f'This is a'
23 | f'\"string\"' # Q004
| ^^^^^^^^^^^^^ Q004
24 | )
|
= help: Remove backslash
Safe fix
20 20 | fR'This is a \"string\"'
21 21 | this_should_raise_Q004 = (
22 22 | f'This is a'
23 |- f'\"string\"' # Q004
23 |+ f'"string"' # Q004
24 24 | )
25 25 |
26 26 | # Nested f-strings (Python 3.12+)
doubles_escaped_unnecessary.py:33:1: Q004 [*] Unnecessary escape on inner quote character
|
31 | #
32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 | f'\"foo\" {'nested'}' # Q004
| ^^^^^^^^^^^^^^^^^^^^^ Q004
34 | f'\"foo\" {f'nested'}' # Q004
35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
|
= help: Remove backslash
Safe fix
30 30 | # f"'foo' {'nested'}"
31 31 | #
32 32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 |-f'\"foo\" {'nested'}' # Q004
33 |+f'"foo" {'nested'}' # Q004
34 34 | f'\"foo\" {f'nested'}' # Q004
35 35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
36 36 |
doubles_escaped_unnecessary.py:34:1: Q004 [*] Unnecessary escape on inner quote character
|
32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 | f'\"foo\" {'nested'}' # Q004
34 | f'\"foo\" {f'nested'}' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^ Q004
35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
|
= help: Remove backslash
Safe fix
31 31 | #
32 32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 33 | f'\"foo\" {'nested'}' # Q004
34 |-f'\"foo\" {f'nested'}' # Q004
34 |+f'"foo" {f'nested'}' # Q004
35 35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
36 36 |
37 37 | f'normal {f'nested'} normal'
doubles_escaped_unnecessary.py:35:1: Q004 [*] Unnecessary escape on inner quote character
|
33 | f'\"foo\" {'nested'}' # Q004
34 | f'\"foo\" {f'nested'}' # Q004
35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
36 |
37 | f'normal {f'nested'} normal'
|
= help: Remove backslash
Safe fix
32 32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 33 | f'\"foo\" {'nested'}' # Q004
34 34 | f'\"foo\" {f'nested'}' # Q004
35 |-f'\"foo\" {f'\"nested\"'} \"\"' # Q004
35 |+f'"foo" {f'\"nested\"'} ""' # Q004
36 36 |
37 37 | f'normal {f'nested'} normal'
38 38 | f'\"normal\" {f'nested'} normal' # Q004
doubles_escaped_unnecessary.py:35:12: Q004 [*] Unnecessary escape on inner quote character
|
33 | f'\"foo\" {'nested'}' # Q004
34 | f'\"foo\" {f'nested'}' # Q004
35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
| ^^^^^^^^^^^^^ Q004
36 |
37 | f'normal {f'nested'} normal'
|
= help: Remove backslash
Safe fix
32 32 | # but as the actual string itself is invalid pre 3.12, we don't catch it.
33 33 | f'\"foo\" {'nested'}' # Q004
34 34 | f'\"foo\" {f'nested'}' # Q004
35 |-f'\"foo\" {f'\"nested\"'} \"\"' # Q004
35 |+f'\"foo\" {f'"nested"'} \"\"' # Q004
36 36 |
37 37 | f'normal {f'nested'} normal'
38 38 | f'\"normal\" {f'nested'} normal' # Q004
doubles_escaped_unnecessary.py:38:1: Q004 [*] Unnecessary escape on inner quote character
|
37 | f'normal {f'nested'} normal'
38 | f'\"normal\" {f'nested'} normal' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
39 | f'\"normal\" {f'nested'} "double quotes"'
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
|
= help: Remove backslash
Safe fix
35 35 | f'\"foo\" {f'\"nested\"'} \"\"' # Q004
36 36 |
37 37 | f'normal {f'nested'} normal'
38 |-f'\"normal\" {f'nested'} normal' # Q004
38 |+f'"normal" {f'nested'} normal' # Q004
39 39 | f'\"normal\" {f'nested'} "double quotes"'
40 40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
doubles_escaped_unnecessary.py:39:1: Q004 [*] Unnecessary escape on inner quote character
|
37 | f'normal {f'nested'} normal'
38 | f'\"normal\" {f'nested'} normal' # Q004
39 | f'\"normal\" {f'nested'} "double quotes"'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
|
= help: Remove backslash
Safe fix
36 36 |
37 37 | f'normal {f'nested'} normal'
38 38 | f'\"normal\" {f'nested'} normal' # Q004
39 |-f'\"normal\" {f'nested'} "double quotes"'
39 |+f'"normal" {f'nested'} "double quotes"'
40 40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
42 42 |
doubles_escaped_unnecessary.py:40:1: Q004 [*] Unnecessary escape on inner quote character
|
38 | f'\"normal\" {f'nested'} normal' # Q004
39 | f'\"normal\" {f'nested'} "double quotes"'
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
|
= help: Remove backslash
Safe fix
37 37 | f'normal {f'nested'} normal'
38 38 | f'\"normal\" {f'nested'} normal' # Q004
39 39 | f'\"normal\" {f'nested'} "double quotes"'
40 |-f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
40 |+f'"normal" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
42 42 |
43 43 | # Make sure we do not unescape quotes
doubles_escaped_unnecessary.py:40:15: Q004 [*] Unnecessary escape on inner quote character
|
38 | f'\"normal\" {f'nested'} normal' # Q004
39 | f'\"normal\" {f'nested'} "double quotes"'
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
|
= help: Remove backslash
Safe fix
37 37 | f'normal {f'nested'} normal'
38 38 | f'\"normal\" {f'nested'} normal' # Q004
39 39 | f'\"normal\" {f'nested'} "double quotes"'
40 |-f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
40 |+f'\"normal\" {f'"nested" {'other'} normal'} "double quotes"' # Q004
41 41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
42 42 |
43 43 | # Make sure we do not unescape quotes
doubles_escaped_unnecessary.py:41:1: Q004 [*] Unnecessary escape on inner quote character
|
39 | f'\"normal\" {f'nested'} "double quotes"'
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
42 |
43 | # Make sure we do not unescape quotes
|
= help: Remove backslash
Safe fix
38 38 | f'\"normal\" {f'nested'} normal' # Q004
39 39 | f'\"normal\" {f'nested'} "double quotes"'
40 40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 |-f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
41 |+f'"normal" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
42 42 |
43 43 | # Make sure we do not unescape quotes
44 44 | this_is_fine = 'This is an \\"escaped\\" quote'
doubles_escaped_unnecessary.py:41:15: Q004 [*] Unnecessary escape on inner quote character
|
39 | f'\"normal\" {f'nested'} "double quotes"'
40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 | f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
42 |
43 | # Make sure we do not unescape quotes
|
= help: Remove backslash
Safe fix
38 38 | f'\"normal\" {f'nested'} normal' # Q004
39 39 | f'\"normal\" {f'nested'} "double quotes"'
40 40 | f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
41 |-f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
41 |+f'\"normal\" {f'"nested" {'other'} "double quotes"'} normal' # Q004
42 42 |
43 43 | # Make sure we do not unescape quotes
44 44 | this_is_fine = 'This is an \\"escaped\\" quote'
doubles_escaped_unnecessary.py:45:26: Q004 [*] Unnecessary escape on inner quote character
|
43 | # Make sure we do not unescape quotes
44 | this_is_fine = 'This is an \\"escaped\\" quote'
45 | this_should_raise_Q004 = 'This is an \\\"escaped\\\" quote with an extra backslash'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Q004
|
= help: Remove backslash
Safe fix
42 42 |
43 43 | # Make sure we do not unescape quotes
44 44 | this_is_fine = 'This is an \\"escaped\\" quote'
45 |-this_should_raise_Q004 = 'This is an \\\"escaped\\\" quote with an extra backslash'
45 |+this_should_raise_Q004 = 'This is an \\"escaped\\" quote with an extra backslash'

1
ruff.schema.json generated
View file

@ -3277,6 +3277,7 @@
"Q001",
"Q002",
"Q003",
"Q004",
"RET",
"RET5",
"RET50",

View file

@ -76,6 +76,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
"unexpected-spaces-around-keyword-parameter-equals",
"unicode-kind-prefix",
"unnecessary-class-parentheses",
"unnecessary-escaped-quote",
"useless-semicolon",
"whitespace-after-open-bracket",
"whitespace-before-close-bracket",