Skip to content

Commit

Permalink
Preserve triple quotes and prefixes for strings (#15818)
Browse files Browse the repository at this point in the history
## Summary

This is a follow-up to #15726, #15778, and #15794 to preserve the triple
quote and prefix flags in plain strings, bytestrings, and f-strings.

I also added a `StringLiteralFlags::without_triple_quotes` method to
avoid passing along triple quotes in rules like SIM905 where it might
not make sense, as discussed
[here](#15726 (comment)).

## Test Plan

Existing tests, plus many new cases in the `generator::tests::quote`
test that should cover all combinations of quotes and prefixes, at least
for simple string bodies.

Closes #7799 when combined with #15694, #15726, #15778, and #15794.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
ntBre and AlexWaygood authored Feb 4, 2025
1 parent 9a33924 commit b5e5271
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 141 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/red_knot_python_semantic/src/types/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::fmt::{self, Display, Formatter, Write};

use ruff_db::display::FormatterJoinExtension;
use ruff_python_ast::str::Quote;
use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;

use crate::types::class_base::ClassBase;
Expand Down Expand Up @@ -98,7 +98,7 @@ impl Display for DisplayRepresentation<'_> {
let escape =
AsciiEscape::with_preferred_quote(bytes.value(self.db).as_ref(), Quote::Double);

escape.bytes_repr().write(f)
escape.bytes_repr(TripleQuotes::No).write(f)
}
Type::SliceLiteral(slice) => {
f.write_str("slice[")?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,12 @@
"hello\nworld".splitlines()
"hello\nworld".splitlines(keepends=True)
"hello\nworld".splitlines(keepends=False)


# another positive demonstrating quote preservation
"""
"itemA"
'itemB'
'''itemC'''
"'itemD'"
""".split()
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ fn check_string_or_bytes(

let mut diagnostic = Diagnostic::new(UnnecessaryEscapedQuote, range);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
flags.format_string_contents(&unescape_string(contents, opposite_quote_char)),
flags
.display_contents(&unescape_string(contents, opposite_quote_char))
.to_string(),
range,
)));
Some(diagnostic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::cmp::Ordering;
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{
Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral, StringLiteralFlags,
StringLiteralValue, UnaryOp,
str::TripleQuotes, Expr, ExprCall, ExprContext, ExprList, ExprUnaryOp, StringLiteral,
StringLiteralFlags, StringLiteralValue, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange};

Expand Down Expand Up @@ -123,7 +123,17 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::from(StringLiteral {
value: Box::from(*elt),
range: TextRange::default(),
flags,
// intentionally omit the triple quote flag, if set, to avoid strange
// replacements like
//
// ```python
// """
// itemA
// itemB
// itemC
// """.split() # -> ["""itemA""", """itemB""", """itemC"""]
// ```
flags: flags.with_triple_quotes(TripleQuotes::No),
})
})
.collect(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,4 +842,29 @@ SIM905.py:72:1: SIM905 [*] Consider using a list literal instead of `str.split`
72 |+["a,b,c"] # ["a,b,c"]
73 73 |
74 74 | # negatives
75 75 |
75 75 |

SIM905.py:103:1: SIM905 [*] Consider using a list literal instead of `str.split`
|
102 | # another positive demonstrating quote preservation
103 | / """
104 | | "itemA"
105 | | 'itemB'
106 | | '''itemC'''
107 | | "'itemD'"
108 | | """.split()
| |___________^ SIM905
|
= help: Replace with list literal

Safe fix
100 100 |
101 101 |
102 102 | # another positive demonstrating quote preservation
103 |-"""
104 |-"itemA"
105 |-'itemB'
106 |-'''itemC'''
107 |-"'itemD'"
108 |-""".split()
103 |+['"itemA"', "'itemB'", "'''itemC'''", "\"'itemD'\""]
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ pub(crate) fn printf_string_formatting(
// Convert the `%`-format string to a `.format` string.
format_strings.push((
string_literal.range(),
flags.format_string_contents(&percent_to_format(&format_string)),
flags
.display_contents(&percent_to_format(&format_string))
.to_string(),
));
}

Expand Down
Loading

0 comments on commit b5e5271

Please sign in to comment.