mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:28 +00:00
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:
parent
534fc34f11
commit
16060670b8
10 changed files with 1048 additions and 19 deletions
45
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped_unnecessary.py
vendored
Normal file
45
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped_unnecessary.py
vendored
Normal 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'
|
43
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped_unnecessary.py
vendored
Normal file
43
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped_unnecessary.py
vendored
Normal 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"
|
|
@ -115,6 +115,10 @@ pub(crate) fn check_tokens(
|
||||||
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
|
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(&[
|
if settings.rules.any_enabled(&[
|
||||||
Rule::BadQuotesInlineString,
|
Rule::BadQuotesInlineString,
|
||||||
Rule::BadQuotesMultilineString,
|
Rule::BadQuotesMultilineString,
|
||||||
|
|
|
@ -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, "001") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesMultilineString),
|
||||||
(Flake8Quotes, "002") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesDocstring),
|
(Flake8Quotes, "002") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesDocstring),
|
||||||
(Flake8Quotes, "003") => (RuleGroup::Stable, rules::flake8_quotes::rules::AvoidableEscapedQuote),
|
(Flake8Quotes, "003") => (RuleGroup::Stable, rules::flake8_quotes::rules::AvoidableEscapedQuote),
|
||||||
|
(Flake8Quotes, "004") => (RuleGroup::Preview, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),
|
||||||
|
|
||||||
// flake8-annotations
|
// flake8-annotations
|
||||||
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
|
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
|
||||||
|
|
|
@ -19,6 +19,7 @@ mod tests {
|
||||||
|
|
||||||
#[test_case(Path::new("doubles.py"))]
|
#[test_case(Path::new("doubles.py"))]
|
||||||
#[test_case(Path::new("doubles_escaped.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_implicit.py"))]
|
||||||
#[test_case(Path::new("doubles_multiline_string.py"))]
|
#[test_case(Path::new("doubles_multiline_string.py"))]
|
||||||
#[test_case(Path::new("doubles_noqa.py"))]
|
#[test_case(Path::new("doubles_noqa.py"))]
|
||||||
|
@ -39,6 +40,7 @@ mod tests {
|
||||||
Rule::BadQuotesMultilineString,
|
Rule::BadQuotesMultilineString,
|
||||||
Rule::BadQuotesDocstring,
|
Rule::BadQuotesDocstring,
|
||||||
Rule::AvoidableEscapedQuote,
|
Rule::AvoidableEscapedQuote,
|
||||||
|
Rule::UnnecessaryEscapedQuote,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -86,6 +88,7 @@ mod tests {
|
||||||
|
|
||||||
#[test_case(Path::new("singles.py"))]
|
#[test_case(Path::new("singles.py"))]
|
||||||
#[test_case(Path::new("singles_escaped.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_implicit.py"))]
|
||||||
#[test_case(Path::new("singles_multiline_string.py"))]
|
#[test_case(Path::new("singles_multiline_string.py"))]
|
||||||
#[test_case(Path::new("singles_noqa.py"))]
|
#[test_case(Path::new("singles_noqa.py"))]
|
||||||
|
@ -106,6 +109,7 @@ mod tests {
|
||||||
Rule::BadQuotesMultilineString,
|
Rule::BadQuotesMultilineString,
|
||||||
Rule::BadQuotesDocstring,
|
Rule::BadQuotesDocstring,
|
||||||
Rule::AvoidableEscapedQuote,
|
Rule::AvoidableEscapedQuote,
|
||||||
|
Rule::UnnecessaryEscapedQuote,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -139,6 +143,7 @@ mod tests {
|
||||||
Rule::BadQuotesMultilineString,
|
Rule::BadQuotesMultilineString,
|
||||||
Rule::BadQuotesDocstring,
|
Rule::BadQuotesDocstring,
|
||||||
Rule::AvoidableEscapedQuote,
|
Rule::AvoidableEscapedQuote,
|
||||||
|
Rule::UnnecessaryEscapedQuote,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -172,6 +177,7 @@ mod tests {
|
||||||
Rule::BadQuotesMultilineString,
|
Rule::BadQuotesMultilineString,
|
||||||
Rule::BadQuotesDocstring,
|
Rule::BadQuotesDocstring,
|
||||||
Rule::AvoidableEscapedQuote,
|
Rule::AvoidableEscapedQuote,
|
||||||
|
Rule::UnnecessaryEscapedQuote,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -7,9 +7,10 @@ use ruff_source_file::Locator;
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::lex::docstring_detection::StateMachine;
|
use crate::lex::docstring_detection::StateMachine;
|
||||||
|
|
||||||
use crate::settings::LinterSettings;
|
use crate::settings::LinterSettings;
|
||||||
|
|
||||||
|
use super::super::settings::Quote;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for strings that include escaped quotes, and suggests changing
|
/// Checks for strings that include escaped quotes, and suggests changing
|
||||||
/// the quote style to avoid the need to escape them.
|
/// 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 {
|
struct FStringContext {
|
||||||
/// Whether to check for escaped quotes in the f-string.
|
/// Whether to check for escaped quotes in the f-string.
|
||||||
check_for_escaped_quote: bool,
|
check_for_escaped_quote: bool,
|
||||||
|
@ -55,14 +93,21 @@ struct FStringContext {
|
||||||
start_range: TextRange,
|
start_range: TextRange,
|
||||||
/// The ranges of the f-string middle tokens containing escaped quotes.
|
/// The ranges of the f-string middle tokens containing escaped quotes.
|
||||||
middle_ranges_with_escapes: Vec<TextRange>,
|
middle_ranges_with_escapes: Vec<TextRange>,
|
||||||
|
/// The quote style used for the f-string
|
||||||
|
quote_style: Quote,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FStringContext {
|
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 {
|
Self {
|
||||||
check_for_escaped_quote,
|
check_for_escaped_quote,
|
||||||
start_range: fstring_start_range,
|
start_range: fstring_start_range,
|
||||||
middle_ranges_with_escapes: vec![],
|
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.
|
// Check if we're using the preferred quotation style.
|
||||||
if !leading_quote(locator.slice(tok_range))
|
if !leading_quote(locator.slice(tok_range)).is_some_and(|text| {
|
||||||
.is_some_and(|text| text.contains(quotes_settings.inline_quotes.as_char()))
|
contains_quote(text, quotes_settings.inline_quotes.as_char())
|
||||||
{
|
}) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if string_contents.contains(quotes_settings.inline_quotes.as_char())
|
if contains_escaped_quote(string_contents, quotes_settings.inline_quotes.as_char())
|
||||||
&& !string_contents.contains(quotes_settings.inline_quotes.opposite().as_char())
|
&& !contains_quote(
|
||||||
|
string_contents,
|
||||||
|
quotes_settings.inline_quotes.opposite().as_char(),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, tok_range);
|
let mut diagnostic = Diagnostic::new(AvoidableEscapedQuote, tok_range);
|
||||||
let fixed_contents = format!(
|
let fixed_contents = format!(
|
||||||
"{prefix}{quote}{value}{quote}",
|
"{prefix}{quote}{value}{quote}",
|
||||||
prefix = kind.as_str(),
|
prefix = kind.as_str(),
|
||||||
quote = quotes_settings.inline_quotes.opposite().as_char(),
|
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(
|
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||||
fixed_contents,
|
fixed_contents,
|
||||||
|
@ -159,10 +210,13 @@ pub(crate) fn avoidable_escaped_quote(
|
||||||
let text = locator.slice(tok_range);
|
let text = locator.slice(tok_range);
|
||||||
// Check for escaped quote only if we're using the preferred quotation
|
// Check for escaped quote only if we're using the preferred quotation
|
||||||
// style and it isn't a triple-quoted f-string.
|
// style and it isn't a triple-quoted f-string.
|
||||||
let check_for_escaped_quote = text
|
let check_for_escaped_quote = !is_triple_quote(text)
|
||||||
.contains(quotes_settings.inline_quotes.as_char())
|
&& contains_quote(text, quotes_settings.inline_quotes.as_char());
|
||||||
&& !is_triple_quote(text);
|
fstrings.push(FStringContext::new(
|
||||||
fstrings.push(FStringContext::new(check_for_escaped_quote, tok_range));
|
check_for_escaped_quote,
|
||||||
|
tok_range,
|
||||||
|
quotes_settings.inline_quotes,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Tok::FStringMiddle {
|
Tok::FStringMiddle {
|
||||||
value: string_contents,
|
value: string_contents,
|
||||||
|
@ -176,11 +230,15 @@ pub(crate) fn avoidable_escaped_quote(
|
||||||
}
|
}
|
||||||
// If any part of the f-string contains the opposite quote,
|
// If any part of the f-string contains the opposite quote,
|
||||||
// we can't change the quote style in the entire f-string.
|
// 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();
|
context.ignore_escaped_quotes();
|
||||||
continue;
|
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);
|
context.push_fstring_middle_range(tok_range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +265,13 @@ pub(crate) fn avoidable_escaped_quote(
|
||||||
.middle_ranges_with_escapes
|
.middle_ranges_with_escapes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&range| {
|
.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(
|
.chain(std::iter::once(
|
||||||
// `FStringEnd` edit
|
// `FStringEnd` edit
|
||||||
|
@ -231,13 +295,152 @@ pub(crate) fn avoidable_escaped_quote(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unescape_string(value: &str) -> String {
|
/// Q004
|
||||||
let mut fixed_contents = String::with_capacity(value.len());
|
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() {
|
while let Some(char) = chars.next() {
|
||||||
if char != '\\' {
|
if char != '\\' {
|
||||||
fixed_contents.push(char);
|
fixed_contents.push(char);
|
||||||
|
backslashes = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If we're at the end of the line
|
// If we're at the end of the line
|
||||||
|
@ -246,9 +449,11 @@ fn unescape_string(value: &str) -> String {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// Remove quote escape
|
// Remove quote escape
|
||||||
if matches!(*next_char, '\'' | '"') {
|
if *next_char == quote && backslashes % 2 == 0 {
|
||||||
|
backslashes = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
backslashes += 1;
|
||||||
fixed_contents.push(char);
|
fixed_contents.push(char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
1
ruff.schema.json
generated
|
@ -3277,6 +3277,7 @@
|
||||||
"Q001",
|
"Q001",
|
||||||
"Q002",
|
"Q002",
|
||||||
"Q003",
|
"Q003",
|
||||||
|
"Q004",
|
||||||
"RET",
|
"RET",
|
||||||
"RET5",
|
"RET5",
|
||||||
"RET50",
|
"RET50",
|
||||||
|
|
|
@ -76,6 +76,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
||||||
"unexpected-spaces-around-keyword-parameter-equals",
|
"unexpected-spaces-around-keyword-parameter-equals",
|
||||||
"unicode-kind-prefix",
|
"unicode-kind-prefix",
|
||||||
"unnecessary-class-parentheses",
|
"unnecessary-class-parentheses",
|
||||||
|
"unnecessary-escaped-quote",
|
||||||
"useless-semicolon",
|
"useless-semicolon",
|
||||||
"whitespace-after-open-bracket",
|
"whitespace-after-open-bracket",
|
||||||
"whitespace-before-close-bracket",
|
"whitespace-before-close-bracket",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue