Allow implicit multiline strings with internal quotes to use non-preferred quote (#2416)

As an example, if you have `single` as your preferred style, we'll now allow this:

```py
assert s.to_python(123) == (
    "123 info=SerializationInfo(include=None, exclude=None, mode='python', by_alias=True, exclude_unset=False, "
    "exclude_defaults=False, exclude_none=False, round_trip=False)"
)
```

Previously, the second line of the implicit string concatenation would be flagged as invalid, despite the _first_ line requiring double quotes. (Note that we'll accept either single or double quotes for that second line.)

Mechanically, this required that we process sequences of `Tok::String` rather than a single `Tok::String` at a time. Prior to iterating over the strings in the sequence, we check if any of them require the non-preferred quote style; if so, we let _any_ of them use it.

Closes #2400.
This commit is contained in:
Charlie Marsh 2023-01-31 16:27:15 -05:00 committed by GitHub
parent 1dd9ccf7f6
commit fbf231e1b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 766 additions and 223 deletions

View File

@ -1,2 +1,4 @@
this_should_be_linted = "double quote string" this_should_be_linted = "double quote string"
this_should_be_linted = u"double quote string" this_should_be_linted = u"double quote string"
this_should_be_linted = f"double quote string"
this_should_be_linted = f"double {'quote'} string"

View File

@ -4,3 +4,8 @@ this_is_fine = '"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 = "\"This\" is a 'string'"
this_is_fine = r'This is a \'string\'' this_is_fine = r'This is a \'string\''
this_is_fine = R'This is a \'string\''
this_should_raise = (
'This is a'
'\'string\''
)

View File

@ -0,0 +1,27 @@
x = (
"This"
"is"
"not"
)
x = (
"This" \
"is" \
"not"
)
x = (
"This"
"is 'actually'"
"fine"
)
x = (
"This" \
"is 'actually'" \
"fine"
)
if True:
"This can use 'double' quotes"
"But this needs to be changed"

View File

@ -1,2 +1,4 @@
this_should_be_linted = 'single quote string' this_should_be_linted = 'single quote string'
this_should_be_linted = u'double quote string' this_should_be_linted = u'double quote string'
this_should_be_linted = f'double quote string'
this_should_be_linted = f'double {"quote"} string'

View File

@ -3,3 +3,8 @@ this_is_fine = "'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 = '\'This\' is a "string"'
this_is_fine = r"This is a \"string\"" this_is_fine = r"This is a \"string\""
this_is_fine = R"This is a \"string\""
this_should_raise = (
"This is a"
"\"string\""
)

View File

@ -0,0 +1,27 @@
x = (
'This'
'is'
'not'
)
x = (
'This' \
'is' \
'not'
)
x = (
'This'
'is "actually"'
'fine'
)
x = (
'This' \
'is "actually"' \
'fine'
)
if True:
'This can use "single" quotes'
'But this needs to be changed'

View File

@ -48,77 +48,73 @@ pub fn check_tokens(
|| settings.rules.enabled(&Rule::TrailingCommaProhibited); || settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses); let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let mut state_machine = StateMachine::default(); if enforce_ambiguous_unicode_character
for &(start, ref tok, end) in tokens.iter().flatten() { || enforce_commented_out_code
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes { || enforce_invalid_escape_sequence
state_machine.consume(tok) {
} else { let mut state_machine = StateMachine::default();
false for &(start, ref tok, end) in tokens.iter().flatten() {
}; let is_docstring = if enforce_ambiguous_unicode_character {
state_machine.consume(tok)
} else {
false
};
// RUF001, RUF002, RUF003 // RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character { if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. } | Tok::Comment(_)) { if matches!(tok, Tok::String { .. } | Tok::Comment(_)) {
diagnostics.extend(ruff::rules::ambiguous_unicode_character( diagnostics.extend(ruff::rules::ambiguous_unicode_character(
locator, locator,
start, start,
end, end,
if matches!(tok, Tok::String { .. }) { if matches!(tok, Tok::String { .. }) {
if is_docstring { if is_docstring {
Context::Docstring Context::Docstring
} else {
Context::String
}
} else { } else {
Context::String Context::Comment
} },
} else { settings,
Context::Comment autofix,
}, ));
settings, }
autofix,
));
} }
}
// flake8-quotes // eradicate
if enforce_quotes { if enforce_commented_out_code {
if matches!(tok, Tok::String { .. }) { if matches!(tok, Tok::Comment(_)) {
if let Some(diagnostic) = flake8_quotes::rules::quotes( if let Some(diagnostic) =
locator, eradicate::rules::commented_out_code(locator, start, end, settings, autofix)
start, {
end,
is_docstring,
settings,
autofix,
) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }
} }
}
// eradicate // W605
if enforce_commented_out_code { if enforce_invalid_escape_sequence {
if matches!(tok, Tok::Comment(_)) { if matches!(tok, Tok::String { .. }) {
if let Some(diagnostic) = diagnostics.extend(pycodestyle::rules::invalid_escape_sequence(
eradicate::rules::commented_out_code(locator, start, end, settings, autofix) locator,
{ start,
diagnostics.push(diagnostic); end,
matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
));
} }
} }
} }
}
// W605 // Q001, Q002, Q003
if enforce_invalid_escape_sequence { if enforce_quotes {
if matches!(tok, Tok::String { .. }) { diagnostics.extend(
diagnostics.extend(pycodestyle::rules::invalid_escape_sequence( flake8_quotes::rules::from_tokens(tokens, locator, settings, autofix)
locator, .into_iter()
start, .filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
end, );
matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::InvalidEscapeSequence),
));
}
}
} }
// ISC001, ISC002 // ISC001, ISC002

View File

@ -17,6 +17,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_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"))]
#[test_case(Path::new("doubles_wrapped.py"))] #[test_case(Path::new("doubles_wrapped.py"))]
@ -47,6 +48,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_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"))]
#[test_case(Path::new("singles_wrapped.py"))] #[test_case(Path::new("singles_wrapped.py"))]

View File

@ -1,13 +1,16 @@
use rustpython_ast::Location; use rustpython_ast::Location;
use rustpython_parser::lexer::{LexResult, Tok};
use super::settings::Quote;
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::fix::Fix; use crate::fix::Fix;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{Diagnostic, Rule}; use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings}; use crate::settings::{flags, Settings};
use crate::source_code::Locator; use crate::source_code::Locator;
use crate::violations; use crate::violations;
use super::settings::Quote;
fn good_single(quote: &Quote) -> char { fn good_single(quote: &Quote) -> char {
match quote { match quote {
Quote::Single => '\'', Quote::Single => '\'',
@ -43,178 +46,302 @@ fn good_docstring(quote: &Quote) -> &str {
} }
} }
pub fn quotes( struct Trivia<'a> {
last_quote_char: char,
prefix: &'a str,
raw_text: &'a str,
is_multiline: bool,
}
impl<'a> From<&'a str> for Trivia<'a> {
fn from(value: &'a str) -> Self {
// Remove any prefixes (e.g., remove `u` from `u"foo"`).
let last_quote_char = value.chars().last().unwrap();
let first_quote_char = value.find(last_quote_char).unwrap();
let prefix = &value[..first_quote_char];
let raw_text = &value[first_quote_char..];
// Determine if the string is multiline-based.
let is_multiline = if raw_text.len() >= 3 {
let mut chars = raw_text.chars();
let first = chars.next().unwrap();
let second = chars.next().unwrap();
let third = chars.next().unwrap();
first == second && second == third
} else {
false
};
Self {
last_quote_char,
prefix,
raw_text,
is_multiline,
}
}
}
/// Q003
fn docstring(
locator: &Locator, locator: &Locator,
start: Location, start: Location,
end: Location, end: Location,
is_docstring: bool,
settings: &Settings, settings: &Settings,
autofix: flags::Autofix, autofix: flags::Autofix,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
let quotes_settings = &settings.flake8_quotes; let quotes_settings = &settings.flake8_quotes;
let text = locator.slice_source_code_range(&Range::new(start, end)); let text = locator.slice_source_code_range(&Range::new(start, end));
let trivia: Trivia = text.into();
// Remove any prefixes (e.g., remove `u` from `u"foo"`). if trivia
let last_quote_char = text.chars().last().unwrap(); .raw_text
let first_quote_char = text.find(last_quote_char).unwrap(); .contains(good_docstring(&quotes_settings.docstring_quotes))
let prefix = &text[..first_quote_char].to_lowercase(); {
let raw_text = &text[first_quote_char..]; return None;
// Determine if the string is multiline-based.
let is_multiline = if raw_text.len() >= 3 {
let mut chars = raw_text.chars();
let first = chars.next().unwrap();
let second = chars.next().unwrap();
let third = chars.next().unwrap();
first == second && second == third
} else {
false
};
if is_docstring {
if raw_text.contains(good_docstring(&quotes_settings.docstring_quotes)) {
return None;
}
let mut diagnostic = Diagnostic::new(
violations::BadQuotesDocstring {
quote: quotes_settings.docstring_quotes.clone(),
},
Range::new(start, end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesDocstring)
{
let quote_count = if is_multiline { 3 } else { 1 };
let string_contents = &raw_text[quote_count..raw_text.len() - quote_count];
let quote = good_docstring(&quotes_settings.docstring_quotes).repeat(quote_count);
let mut fixed_contents =
String::with_capacity(prefix.len() + string_contents.len() + quote.len() * 2);
fixed_contents.push_str(prefix);
fixed_contents.push_str(&quote);
fixed_contents.push_str(string_contents);
fixed_contents.push_str(&quote);
diagnostic.amend(Fix::replacement(fixed_contents, start, end));
}
Some(diagnostic)
} else if is_multiline {
// If our string is or contains a known good string, ignore it.
if raw_text.contains(good_multiline(&quotes_settings.multiline_quotes)) {
return None;
}
// If our string ends with a known good ending, then ignore it.
if raw_text.ends_with(good_multiline_ending(&quotes_settings.multiline_quotes)) {
return None;
}
let mut diagnostic = Diagnostic::new(
violations::BadQuotesMultilineString {
quote: quotes_settings.multiline_quotes.clone(),
},
Range::new(start, end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesMultilineString)
{
let string_contents = &raw_text[3..raw_text.len() - 3];
let quote = good_multiline(&quotes_settings.multiline_quotes);
let mut fixed_contents =
String::with_capacity(prefix.len() + string_contents.len() + quote.len() * 2);
fixed_contents.push_str(prefix);
fixed_contents.push_str(quote);
fixed_contents.push_str(string_contents);
fixed_contents.push_str(quote);
diagnostic.amend(Fix::replacement(fixed_contents, start, end));
}
Some(diagnostic)
} else {
let string_contents = &raw_text[1..raw_text.len() - 1];
// If we're using the preferred quotation type, check for escapes.
if last_quote_char == good_single(&quotes_settings.inline_quotes) {
if !quotes_settings.avoid_escape || prefix.contains('r') {
return None;
}
if string_contents.contains(good_single(&quotes_settings.inline_quotes))
&& !string_contents.contains(bad_single(&quotes_settings.inline_quotes))
{
let mut diagnostic =
Diagnostic::new(violations::AvoidQuoteEscape, Range::new(start, end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::AvoidQuoteEscape)
{
let quote = bad_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(prefix.len() + string_contents.len() + 2);
fixed_contents.push_str(prefix);
fixed_contents.push(quote);
let chars: Vec<char> = string_contents.chars().collect();
let mut backslash_count = 0;
for col_offset in 0..chars.len() {
let char = chars[col_offset];
if char != '\\' {
fixed_contents.push(char);
continue;
}
backslash_count += 1;
// If the previous character was also a backslash
if col_offset > 0 && chars[col_offset - 1] == '\\' && backslash_count == 2 {
fixed_contents.push(char);
// reset to 0
backslash_count = 0;
continue;
}
// If we're at the end of the line
if col_offset == chars.len() - 1 {
fixed_contents.push(char);
continue;
}
let next_char = chars[col_offset + 1];
// Remove quote escape
if next_char == '\'' || next_char == '"' {
// reset to 0
backslash_count = 0;
continue;
}
fixed_contents.push(char);
}
fixed_contents.push(quote);
diagnostic.amend(Fix::replacement(fixed_contents, start, end));
}
return Some(diagnostic);
}
return None;
}
// If we're not using the preferred type, only allow use to avoid escapes.
if !string_contents.contains(good_single(&quotes_settings.inline_quotes)) {
let mut diagnostic = Diagnostic::new(
violations::BadQuotesInlineString {
quote: quotes_settings.inline_quotes.clone(),
},
Range::new(start, end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesInlineString)
{
let quote = good_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(prefix.len() + string_contents.len() + 2);
fixed_contents.push_str(prefix);
fixed_contents.push(quote);
fixed_contents.push_str(string_contents);
fixed_contents.push(quote);
diagnostic.amend(Fix::replacement(fixed_contents, start, end));
}
return Some(diagnostic);
}
None
} }
let mut diagnostic = Diagnostic::new(
violations::BadQuotesDocstring {
quote: quotes_settings.docstring_quotes.clone(),
},
Range::new(start, end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesDocstring)
{
let quote_count = if trivia.is_multiline { 3 } else { 1 };
let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count];
let quote = good_docstring(&quotes_settings.docstring_quotes).repeat(quote_count);
let mut fixed_contents =
String::with_capacity(trivia.prefix.len() + string_contents.len() + quote.len() * 2);
fixed_contents.push_str(trivia.prefix);
fixed_contents.push_str(&quote);
fixed_contents.push_str(string_contents);
fixed_contents.push_str(&quote);
diagnostic.amend(Fix::replacement(fixed_contents, start, end));
}
Some(diagnostic)
}
/// Q001, Q002
fn strings(
locator: &Locator,
sequence: &[(Location, Location)],
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
let quotes_settings = &settings.flake8_quotes;
let trivia = sequence
.iter()
.map(|(start, end)| {
let text = locator.slice_source_code_range(&Range::new(*start, *end));
let trivia: Trivia = text.into();
trivia
})
.collect::<Vec<_>>();
// Return `true` if any of the strings are inline strings that contain the quote character in
// the body.
let relax_quote = trivia.iter().any(|trivia| {
if trivia.is_multiline {
return false;
}
if trivia.last_quote_char == good_single(&quotes_settings.inline_quotes) {
return false;
}
let string_contents = &trivia.raw_text[1..trivia.raw_text.len() - 1];
string_contents.contains(good_single(&quotes_settings.inline_quotes))
});
for ((start, end), trivia) in sequence.iter().zip(trivia.into_iter()) {
if trivia.is_multiline {
// If our string is or contains a known good string, ignore it.
if trivia
.raw_text
.contains(good_multiline(&quotes_settings.multiline_quotes))
{
continue;
}
// If our string ends with a known good ending, then ignore it.
if trivia
.raw_text
.ends_with(good_multiline_ending(&quotes_settings.multiline_quotes))
{
continue;
}
let mut diagnostic = Diagnostic::new(
violations::BadQuotesMultilineString {
quote: quotes_settings.multiline_quotes.clone(),
},
Range::new(*start, *end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesMultilineString)
{
let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3];
let quote = good_multiline(&quotes_settings.multiline_quotes);
let mut fixed_contents = String::with_capacity(
trivia.prefix.len() + string_contents.len() + quote.len() * 2,
);
fixed_contents.push_str(trivia.prefix);
fixed_contents.push_str(quote);
fixed_contents.push_str(string_contents);
fixed_contents.push_str(quote);
diagnostic.amend(Fix::replacement(fixed_contents, *start, *end));
}
diagnostics.push(diagnostic);
} else {
let string_contents = &trivia.raw_text[1..trivia.raw_text.len() - 1];
// If we're using the preferred quotation type, check for escapes.
if trivia.last_quote_char == good_single(&quotes_settings.inline_quotes) {
if !quotes_settings.avoid_escape
|| trivia.prefix.contains('r')
|| trivia.prefix.contains('R')
{
continue;
}
if string_contents.contains(good_single(&quotes_settings.inline_quotes))
&& !string_contents.contains(bad_single(&quotes_settings.inline_quotes))
{
let mut diagnostic =
Diagnostic::new(violations::AvoidQuoteEscape, Range::new(*start, *end));
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::AvoidQuoteEscape)
{
let quote = bad_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);
fixed_contents.push_str(trivia.prefix);
fixed_contents.push(quote);
let chars: Vec<char> = string_contents.chars().collect();
let mut backslash_count = 0;
for col_offset in 0..chars.len() {
let char = chars[col_offset];
if char != '\\' {
fixed_contents.push(char);
continue;
}
backslash_count += 1;
// If the previous character was also a backslash
if col_offset > 0
&& chars[col_offset - 1] == '\\'
&& backslash_count == 2
{
fixed_contents.push(char);
// reset to 0
backslash_count = 0;
continue;
}
// If we're at the end of the line
if col_offset == chars.len() - 1 {
fixed_contents.push(char);
continue;
}
let next_char = chars[col_offset + 1];
// Remove quote escape
if next_char == '\'' || next_char == '"' {
// reset to 0
backslash_count = 0;
continue;
}
fixed_contents.push(char);
}
fixed_contents.push(quote);
diagnostic.amend(Fix::replacement(fixed_contents, *start, *end));
}
diagnostics.push(diagnostic);
}
continue;
}
// If we're not using the preferred type, only allow use to avoid escapes.
if !relax_quote {
let mut diagnostic = Diagnostic::new(
violations::BadQuotesInlineString {
quote: quotes_settings.inline_quotes.clone(),
},
Range::new(*start, *end),
);
if matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::BadQuotesInlineString)
{
let quote = good_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);
fixed_contents.push_str(trivia.prefix);
fixed_contents.push(quote);
fixed_contents.push_str(string_contents);
fixed_contents.push(quote);
diagnostic.amend(Fix::replacement(fixed_contents, *start, *end));
}
diagnostics.push(diagnostic);
}
}
}
diagnostics
}
/// Generate `flake8-quote` diagnostics from a token stream.
pub fn from_tokens(
lxr: &[LexResult],
locator: &Locator,
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
// Keep track of sequences of strings, which represent implicit string concatenation, and
// should thus be handled as a single unit.
let mut sequence = vec![];
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in lxr.iter().flatten() {
let is_docstring = state_machine.consume(tok);
// If this is a docstring, consume the existing sequence, then consume the docstring, then
// move on.
if is_docstring {
if !sequence.is_empty() {
diagnostics.extend(strings(locator, &sequence, settings, autofix));
sequence.clear();
}
if let Some(diagnostic) = docstring(locator, start, end, settings, autofix) {
diagnostics.push(diagnostic);
}
} else {
if matches!(tok, Tok::String { .. }) {
// If this is a string, add it to the sequence.
sequence.push((start, end));
} else if !matches!(tok, Tok::Comment(..) | Tok::NonLogicalNewline) {
// Otherwise, consume the sequence.
if !sequence.is_empty() {
diagnostics.extend(strings(locator, &sequence, settings, autofix));
sequence.clear();
}
}
}
}
// If we have an unterminated sequence, consume it.
if !sequence.is_empty() {
diagnostics.extend(strings(locator, &sequence, settings, autofix));
sequence.clear();
}
diagnostics
} }

View File

@ -40,4 +40,23 @@ expression: diagnostics
row: 2 row: 2
column: 46 column: 46
parent: ~ parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 3
column: 24
end_location:
row: 3
column: 46
fix:
content:
- "f\"double quote string\""
location:
row: 3
column: 24
end_location:
row: 3
column: 46
parent: ~

View File

@ -20,4 +20,22 @@ expression: diagnostics
row: 1 row: 1
column: 47 column: 47
parent: ~ parent: ~
- kind:
AvoidQuoteEscape: ~
location:
row: 9
column: 4
end_location:
row: 9
column: 16
fix:
content:
- "'\"string\"'"
location:
row: 9
column: 4
end_location:
row: 9
column: 16
parent: ~

View File

@ -0,0 +1,138 @@
---
source: src/rules/flake8_quotes/mod.rs
expression: diagnostics
---
- kind:
BadQuotesInlineString:
quote: double
location:
row: 2
column: 4
end_location:
row: 2
column: 10
fix:
content:
- "\"This\""
location:
row: 2
column: 4
end_location:
row: 2
column: 10
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 3
column: 4
end_location:
row: 3
column: 8
fix:
content:
- "\"is\""
location:
row: 3
column: 4
end_location:
row: 3
column: 8
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 4
column: 4
end_location:
row: 4
column: 9
fix:
content:
- "\"not\""
location:
row: 4
column: 4
end_location:
row: 4
column: 9
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 8
column: 4
end_location:
row: 8
column: 10
fix:
content:
- "\"This\""
location:
row: 8
column: 4
end_location:
row: 8
column: 10
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix:
content:
- "\"is\""
location:
row: 9
column: 4
end_location:
row: 9
column: 8
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 10
column: 4
end_location:
row: 10
column: 9
fix:
content:
- "\"not\""
location:
row: 10
column: 4
end_location:
row: 10
column: 9
parent: ~
- kind:
BadQuotesInlineString:
quote: double
location:
row: 27
column: 0
end_location:
row: 27
column: 30
fix:
content:
- "\"But this needs to be changed\""
location:
row: 27
column: 0
end_location:
row: 27
column: 30
parent: ~

View File

@ -40,4 +40,23 @@ expression: diagnostics
row: 2 row: 2
column: 46 column: 46
parent: ~ parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 3
column: 24
end_location:
row: 3
column: 46
fix:
content:
- "f'double quote string'"
location:
row: 3
column: 24
end_location:
row: 3
column: 46
parent: ~

View File

@ -38,4 +38,22 @@ expression: diagnostics
row: 2 row: 2
column: 52 column: 52
parent: ~ parent: ~
- kind:
AvoidQuoteEscape: ~
location:
row: 10
column: 4
end_location:
row: 10
column: 16
fix:
content:
- "\"'string'\""
location:
row: 10
column: 4
end_location:
row: 10
column: 16
parent: ~

View File

@ -0,0 +1,138 @@
---
source: src/rules/flake8_quotes/mod.rs
expression: diagnostics
---
- kind:
BadQuotesInlineString:
quote: single
location:
row: 2
column: 4
end_location:
row: 2
column: 10
fix:
content:
- "'This'"
location:
row: 2
column: 4
end_location:
row: 2
column: 10
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 3
column: 4
end_location:
row: 3
column: 8
fix:
content:
- "'is'"
location:
row: 3
column: 4
end_location:
row: 3
column: 8
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 4
column: 4
end_location:
row: 4
column: 9
fix:
content:
- "'not'"
location:
row: 4
column: 4
end_location:
row: 4
column: 9
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 8
column: 4
end_location:
row: 8
column: 10
fix:
content:
- "'This'"
location:
row: 8
column: 4
end_location:
row: 8
column: 10
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix:
content:
- "'is'"
location:
row: 9
column: 4
end_location:
row: 9
column: 8
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 10
column: 4
end_location:
row: 10
column: 9
fix:
content:
- "'not'"
location:
row: 10
column: 4
end_location:
row: 10
column: 9
parent: ~
- kind:
BadQuotesInlineString:
quote: single
location:
row: 27
column: 0
end_location:
row: 27
column: 30
fix:
content:
- "'But this needs to be changed'"
location:
row: 27
column: 0
end_location:
row: 27
column: 30
parent: ~