Skip to content

Commit

Permalink
Add new rule to check for useless quote escapes
Browse files Browse the repository at this point in the history
  • Loading branch information
ThiefMaster committed Nov 13, 2023
1 parent 534fc34 commit 65163b5
Show file tree
Hide file tree
Showing 10 changed files with 1,028 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
this_should_raise_Q100 = 'This is a \"string\"'
this_should_raise_Q100 = '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_Q100 = (
'This is a'
'\"string\"'
)

# Same as above, but with f-strings
f'This is a \"string\"' # Q100
f'This is \\ a \\\"string\"' # Q100
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_Q100 = (
f'This is a'
f'\"string\"' # Q100
)

# 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'}' # Q100
f'\"foo\" {f'nested'}' # Q100
f'\"foo\" {f'\"nested\"'} \"\"' # Q100

f'normal {f'nested'} normal'
f'\"normal\" {f'nested'} normal' # Q100
f'\"normal\" {f'nested'} "double quotes"'
f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q100
f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q100

# Make sure we do not unescape quotes
this_is_fine = 'This is an \\"escaped\\" quote'
this_should_raise_Q100 = 'This is an \\\"escaped\\\" quote with an extra backslash'
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
this_should_raise_Q100 = "This is a \'string\'"
this_should_raise_Q100 = "'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_Q100 = (
"This is a"
"\'string\'"
)

# Same as above, but with f-strings
f"This is a \'string\'" # Q100
f"'This' is a \'string\'" # Q100
f'This is a "string"'
f'\'This\' is a "string"'
fr"This is a \'string\'"
fR"This is a \'string\'"
this_should_raise_Q100 = (
f"This is a"
f"\'string\'" # Q100
)

# 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"}" # Q100
f"\'foo\' {f"foo"}" # Q100
f"\'foo\' {f"\'foo\'"} \'\'" # Q100

f"normal {f"nested"} normal"
f"\'normal\' {f"nested"} normal" # Q100
f"\'normal\' {f"nested"} 'single quotes'"
f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q100
f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q100

# Make sure we do not unescape quotes
this_is_fine = "This is an \\'escaped\\' quote"
this_should_raise_Q100 = "This is an \\\'escaped\\\' quote with an extra backslash"
4 changes: 4 additions & 0 deletions crates/ruff_linter/src/checkers/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, "100") => (RuleGroup::Preview, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),

// flake8-annotations
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
Expand Down
6 changes: 6 additions & 0 deletions crates/ruff_linter/src/rules/flake8_quotes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand All @@ -39,6 +40,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
Expand Down Expand Up @@ -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"))]
Expand All @@ -106,6 +109,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
Expand Down Expand Up @@ -139,6 +143,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
Expand Down Expand Up @@ -172,6 +177,7 @@ mod tests {
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
Rule::UnnecessaryEscapedQuote,
])
},
)?;
Expand Down

0 comments on commit 65163b5

Please sign in to comment.