Skip to content

Commit b95fc6d

Browse files
authored
Format bytes string (#6166)
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary Format bytes string Closes #6064 ## Test Plan Added a fixture based on string's one
1 parent de898c5 commit b95fc6d

9 files changed

+577
-89
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"quote_style": "double"
4+
},
5+
{
6+
"quote_style": "single"
7+
}
8+
]
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
b"' test"
2+
b'" test'
3+
4+
b"\" test"
5+
b'\' test'
6+
7+
# Prefer single quotes for string with more double quotes
8+
b"' \" \" '' \" \" '"
9+
10+
# Prefer double quotes for string with more single quotes
11+
b'\' " " \'\' " " \''
12+
13+
# Prefer double quotes for string with equal amount of single and double quotes
14+
b'" \' " " \'\''
15+
b"' \" '' \" \""
16+
17+
b"\\' \"\""
18+
b'\\\' ""'
19+
20+
21+
b"Test"
22+
B"Test"
23+
24+
rb"Test"
25+
Rb"Test"
26+
27+
b'This string will not include \
28+
backslashes or newline characters.'
29+
30+
if True:
31+
b'This string will not include \
32+
backslashes or newline characters.'
33+
34+
b"""Multiline
35+
String \"
36+
"""
37+
38+
b'''Multiline
39+
String \'
40+
'''
41+
42+
b'''Multiline
43+
String ""
44+
'''
45+
46+
b'''Multiline
47+
String """
48+
'''
49+
50+
b'''Multiline
51+
String "'''
52+
53+
b"""Multiline
54+
String '''
55+
"""
56+
57+
b"""Multiline
58+
String '"""
59+
60+
b'''Multiline
61+
String \"\"\"
62+
'''
63+
64+
# String continuation
65+
66+
b"Let's" b"start" b"with" b"a" b"simple" b"example"
67+
68+
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident"
69+
70+
(
71+
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident"
72+
)
73+
74+
if (
75+
a + b"Let's"
76+
b"start"
77+
b"with"
78+
b"a"
79+
b"simple"
80+
b"example"
81+
b"now repeat after me:"
82+
b"I am confident"
83+
b"I am confident"
84+
b"I am confident"
85+
b"I am confident"
86+
b"I am confident"
87+
):
88+
pass
89+
90+
if b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident":
91+
pass
92+
93+
(
94+
# leading
95+
b"a" # trailing part comment
96+
97+
# leading part comment
98+
99+
b"b" # trailing second part comment
100+
# trailing
101+
)
102+
103+
test_particular = [
104+
# squares
105+
b'1.00000000100000000025',
106+
b'1.0000000000000000000000000100000000000000000000000' #...
107+
b'00025',
108+
b'1.0000000000000000000000000000000000000000000010000' #...
109+
b'0000000000000000000000000000000000000000025',
110+
]
111+
112+
# Parenthesized string continuation with messed up indentation
113+
{
114+
"key": (
115+
[],
116+
b'a'
117+
b'b'
118+
b'c'
119+
)
120+
}

crates/ruff_python_formatter/src/expression/expr_constant.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::expression::number::{FormatComplex, FormatFloat, FormatInt};
99
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
1010
use crate::expression::string::{FormatString, StringLayout, StringPrefix, StringQuotes};
1111
use crate::prelude::*;
12-
use crate::{not_yet_implemented_custom_text, FormatNodeRule};
12+
use crate::FormatNodeRule;
1313

1414
#[derive(Default)]
1515
pub struct FormatExprConstant {
@@ -51,16 +51,13 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
5151
Constant::Int(_) => FormatInt::new(item).fmt(f),
5252
Constant::Float(_) => FormatFloat::new(item).fmt(f),
5353
Constant::Complex { .. } => FormatComplex::new(item).fmt(f),
54-
Constant::Str(_) => {
54+
Constant::Str(_) | Constant::Bytes(_) => {
5555
let string_layout = match self.layout {
5656
ExprConstantLayout::Default => StringLayout::Default,
5757
ExprConstantLayout::String(layout) => layout,
5858
};
5959
FormatString::new(item).with_layout(string_layout).fmt(f)
6060
}
61-
Constant::Bytes(_) => {
62-
not_yet_implemented_custom_text(r#"b"NOT_YET_IMPLEMENTED_BYTE_STRING""#).fmt(f)
63-
}
6461
}
6562
}
6663

@@ -79,7 +76,7 @@ impl NeedsParentheses for ExprConstant {
7976
_parent: AnyNodeRef,
8077
context: &PyFormatContext,
8178
) -> OptionalParentheses {
82-
if self.value.is_str() {
79+
if self.value.is_str() || self.value.is_bytes() {
8380
let contents = context.locator().slice(self.range());
8481
// Don't wrap triple quoted strings
8582
if is_multiline_string(self, context.source()) || !is_implicit_concatenation(contents) {
@@ -94,7 +91,7 @@ impl NeedsParentheses for ExprConstant {
9491
}
9592

9693
pub(super) fn is_multiline_string(constant: &ExprConstant, source: &str) -> bool {
97-
if constant.value.is_str() {
94+
if constant.value.is_str() || constant.value.is_bytes() {
9895
let contents = &source[constant.range()];
9996
let prefix = StringPrefix::parse(contents);
10097
let quotes =

crates/ruff_python_formatter/src/expression/string.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub enum StringLayout {
3131

3232
impl<'a> FormatString<'a> {
3333
pub(super) fn new(constant: &'a ExprConstant) -> Self {
34-
debug_assert!(constant.value.is_str());
34+
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
3535
Self {
3636
constant,
3737
layout: StringLayout::Default,
@@ -70,7 +70,7 @@ struct FormatStringContinuation<'a> {
7070

7171
impl<'a> FormatStringContinuation<'a> {
7272
fn new(constant: &'a ExprConstant) -> Self {
73-
debug_assert!(constant.value.is_str());
73+
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
7474
Self { constant }
7575
}
7676
}

crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments2.py.snap

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,6 @@ instruction()#comment with bad spacing
191191
)
192192
193193
# Please keep __all__ alphabetized within each category.
194-
@@ -45,7 +45,7 @@
195-
# user-defined types and objects
196-
Cheese,
197-
Cheese("Wensleydale"),
198-
- SubBytes(b"spam"),
199-
+ SubBytes(b"NOT_YET_IMPLEMENTED_BYTE_STRING"),
200-
]
201-
202-
if "PYTHON" in os.environ:
203194
@@ -60,8 +60,12 @@
204195
# Comment before function.
205196
def inline_comments_in_brackets_ruin_everything():
@@ -314,7 +305,7 @@ not_shareables = [
314305
# user-defined types and objects
315306
Cheese,
316307
Cheese("Wensleydale"),
317-
SubBytes(b"NOT_YET_IMPLEMENTED_BYTE_STRING"),
308+
SubBytes(b"spam"),
318309
]
319310
320311
if "PYTHON" in os.environ:

crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,6 @@ last_call()
266266
```diff
267267
--- Black
268268
+++ Ruff
269-
@@ -1,6 +1,6 @@
270-
...
271-
"some_string"
272-
-b"\\xa3"
273-
+b"NOT_YET_IMPLEMENTED_BYTE_STRING"
274-
Name
275-
None
276-
True
277269
@@ -31,7 +31,7 @@
278270
-1
279271
~int and not v1 ^ 123 + v2 | True
@@ -299,22 +291,7 @@ last_call()
299291
1 if True else 2
300292
str or None if True else str or bytes or None
301293
(str or None) if True else (str or bytes or None)
302-
@@ -57,7 +58,13 @@
303-
{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
304-
{**a, **b, **c}
305-
{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")}
306-
-({"a": "b"}, (True or False), (+value), "string", b"bytes") or None
307-
+(
308-
+ {"a": "b"},
309-
+ (True or False),
310-
+ (+value),
311-
+ "string",
312-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
313-
+) or None
314-
()
315-
(1,)
316-
(1, 2)
317-
@@ -115,7 +122,7 @@
294+
@@ -115,7 +116,7 @@
318295
arg,
319296
another,
320297
kwarg="hey",
@@ -323,19 +300,18 @@ last_call()
323300
) # note: no trailing comma pre-3.6
324301
call(*gidgets[:2])
325302
call(a, *gidgets[:2])
326-
@@ -207,25 +214,15 @@
327-
)
303+
@@ -208,24 +209,14 @@
328304
what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
329305
vars_to_remove
330-
-)
306+
)
331307
-result = (
332308
- session.query(models.Customer.id)
333309
- .filter(
334310
- models.Customer.account_id == account_id, models.Customer.email == email_address
335311
- )
336312
- .order_by(models.Customer.id.asc())
337313
- .all()
338-
)
314+
-)
339315
-result = (
340316
- session.query(models.Customer.id)
341317
- .filter(
@@ -357,7 +333,7 @@ last_call()
357333
Ø = set()
358334
authors.łukasz.say_thanks()
359335
mapping = {
360-
@@ -328,13 +325,18 @@
336+
@@ -328,13 +319,18 @@
361337
):
362338
return True
363339
if (
@@ -379,7 +355,7 @@ last_call()
379355
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
380356
):
381357
return True
382-
@@ -342,7 +344,8 @@
358+
@@ -342,7 +338,8 @@
383359
~aaaaaaaaaaaaaaaa.a
384360
+ aaaaaaaaaaaaaaaa.b
385361
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
@@ -396,7 +372,7 @@ last_call()
396372
```py
397373
...
398374
"some_string"
399-
b"NOT_YET_IMPLEMENTED_BYTE_STRING"
375+
b"\\xa3"
400376
Name
401377
None
402378
True
@@ -454,13 +430,7 @@ str or None if (1 if True else 2) else str or bytes or None
454430
{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
455431
{**a, **b, **c}
456432
{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")}
457-
(
458-
{"a": "b"},
459-
(True or False),
460-
(+value),
461-
"string",
462-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
463-
) or None
433+
({"a": "b"}, (True or False), (+value), "string", b"bytes") or None
464434
()
465435
(1,)
466436
(1, 2)

crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__string_prefixes.py.snap

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,17 @@ def docstring_multiline():
3232
```diff
3333
--- Black
3434
+++ Ruff
35-
@@ -1,13 +1,31 @@
35+
@@ -1,12 +1,21 @@
3636
#!/usr/bin/env python3
3737
3838
name = "Łukasz"
3939
-(f"hello {name}", f"hello {name}")
40-
-(b"", b"")
4140
+(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", f"NOT_YET_IMPLEMENTED_ExprJoinedStr")
42-
+(b"NOT_YET_IMPLEMENTED_BYTE_STRING", b"NOT_YET_IMPLEMENTED_BYTE_STRING")
41+
(b"", b"")
4342
("", "")
4443
(r"", R"")
4544
4645
-(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"")
47-
-(rb"", rb"", Rb"", Rb"", rb"", rb"", Rb"", Rb"")
4846
+(
4947
+ f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
5048
+ f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
@@ -55,19 +53,9 @@ def docstring_multiline():
5553
+ f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
5654
+ f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
5755
+)
58-
+(
59-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
60-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
61-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
62-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
63-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
64-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
65-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
66-
+ b"NOT_YET_IMPLEMENTED_BYTE_STRING",
67-
+)
56+
(rb"", rb"", Rb"", Rb"", rb"", rb"", Rb"", Rb"")
6857
6958
70-
def docstring_singleline():
7159
```
7260

7361
## Ruff Output
@@ -77,7 +65,7 @@ def docstring_multiline():
7765
7866
name = "Łukasz"
7967
(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", f"NOT_YET_IMPLEMENTED_ExprJoinedStr")
80-
(b"NOT_YET_IMPLEMENTED_BYTE_STRING", b"NOT_YET_IMPLEMENTED_BYTE_STRING")
68+
(b"", b"")
8169
("", "")
8270
(r"", R"")
8371
@@ -91,16 +79,7 @@ name = "Łukasz"
9179
f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
9280
f"NOT_YET_IMPLEMENTED_ExprJoinedStr",
9381
)
94-
(
95-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
96-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
97-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
98-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
99-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
100-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
101-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
102-
b"NOT_YET_IMPLEMENTED_BYTE_STRING",
103-
)
82+
(rb"", rb"", Rb"", Rb"", rb"", rb"", Rb"", Rb"")
10483
10584
10685
def docstring_singleline():

0 commit comments

Comments
 (0)