Skip to content

Commit f706c3f

Browse files
dhruvmanilaMichaReiser
authored andcommitted
Add f-string formatting to the docs (#15367)
Revive #15341 as it got removed from the latest rebase in #15238.
1 parent 29f6653 commit f706c3f

File tree

4 files changed

+119
-18
lines changed

4 files changed

+119
-18
lines changed

crates/ruff_python_formatter/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ filed in the issue tracker. If you've identified a new deviation, please [file a
1919

2020
When run over _non_-Black-formatted code, the formatter makes some different decisions than Black,
2121
and so more deviations should be expected, especially around the treatment of end-of-line comments.
22-
For details, see [Black compatibility](https://docs.astral.sh/ruff/formatter/#black-compatibility).
22+
For details, see [Style Guide](https://docs.astral.sh/ruff/formatter/#style-guide).
2323

2424
## Getting started
2525

docs/faq.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ to see a few differences on the margins, but the vast majority of your code shou
2828
When run over _non_-Black-formatted code, the formatter makes some different decisions than Black,
2929
and so more deviations should be expected, especially around the treatment of end-of-line comments.
3030

31-
See [_Black compatibility_](formatter.md#black-compatibility) for more.
31+
See [_Style Guide_](formatter.md#style-guide) for more.
3232

3333
## How does Ruff's linter compare to Flake8?
3434

docs/formatter.md

+113-15
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ adoption is minimally disruptive for the vast majority of projects.
3333

3434
Specifically, the formatter is intended to emit near-identical output when run over existing
3535
Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9%
36-
of lines are formatted identically. (See: [_Black compatibility_](#black-compatibility).)
36+
of lines are formatted identically. (See: [_Style Guide](#style-guide).)
3737

3838
Given this focus on Black compatibility, the formatter thus adheres to [Black's (stable) code style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html),
3939
which aims for "consistency, generality, readability and reducing git diffs". To give you a sense
@@ -373,21 +373,10 @@ Meanwhile, `ruff format --check` exits with the following status codes:
373373
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an
374374
internal error.
375375

376-
## Black compatibility
376+
## Style Guide
377377

378378
The formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black).
379-
380-
Specifically, the formatter is intended to emit near-identical output when run over Black-formatted
381-
code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines
382-
are formatted identically. When migrating an existing project from Black to Ruff, you should expect
383-
to see a few differences on the margins, but the vast majority of your code should be unchanged.
384-
385-
When run over _non_-Black-formatted code, the formatter makes some different decisions than Black,
386-
and so more deviations should be expected, especially around the treatment of end-of-line comments.
387-
388-
If you identify deviations in your project, spot-check them against the [known deviations](formatter/black.md),
389-
as well as the [unintentional deviations](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter)
390-
filed in the issue tracker. If you've identified a new deviation, please [file an issue](https://github.com/astral-sh/ruff/issues/new).
379+
This section documents the areas where the Ruff formatter goes beyond Black in terms of code style.
391380

392381
### Intentional deviations
393382

@@ -398,11 +387,120 @@ Black's code style, while others fall out of differences in the underlying imple
398387
For a complete enumeration of these intentional deviations, see [_Known deviations_](formatter/black.md).
399388

400389
Unintentional deviations from Black are tracked in the [issue tracker](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter).
390+
If you've identified a new deviation, please [file an issue](https://github.com/astral-sh/ruff/issues/new).
401391

402392
### Preview style
403-
Similar to [Black](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style), Ruff implements formatting changes
393+
394+
Similar to [Black](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style), Ruff implements formatting changes
404395
under the [`preview`](https://docs.astral.sh/ruff/settings/#format_preview) flag, promoting them to stable through minor releases, in accordance with our [versioning policy](https://github.com/astral-sh/ruff/discussions/6998#discussioncomment-7016766).
405396

397+
### F-string formatting
398+
399+
_Stabilized in Ruff 0.9.0_
400+
401+
Unlike Black, Ruff formats the expression parts of f-strings which are the parts inside the curly
402+
braces `{...}`. This is a [known deviation](formatter/black.md#f-strings) from Black.
403+
404+
Ruff employs several heuristics to determine how an f-string should be formatted which are detailed
405+
below.
406+
407+
#### Quotes
408+
409+
Ruff will use the [configured quote style] for the f-string expression unless doing so would result in
410+
invalid syntax for the target Python version or requires more backslash escapes than the original
411+
expression. Specifically, Ruff will preserve the original quote style for the following cases:
412+
413+
When the target Python version is < 3.12 and a [self-documenting f-string] contains a string
414+
literal with the [configured quote style]:
415+
416+
```python
417+
# format.quote-style = "double"
418+
419+
f'{10 + len("hello")=}'
420+
# This f-string cannot be formatted as follows when targeting Python < 3.12
421+
f"{10 + len("hello")=}"
422+
```
423+
424+
When the target Python version is < 3.12 and an f-string contains any triple-quoted string, byte
425+
or f-string literal that contains the [configured quote style]:
426+
427+
```python
428+
# format.quote-style = "double"
429+
430+
f'{"""nested " """}'`
431+
# This f-string cannot be formatted as follows when targeting Python < 3.12
432+
f"{'''nested " '''}``
433+
```
434+
435+
For all target Python versions, when a [self-documenting f-string] contains an expression between
436+
the curly braces (`{...}`) with a format specifier containing the [configured quote style]:
437+
438+
```python
439+
# format.quote-style = "double"
440+
441+
f'{1=:"foo}'
442+
# This f-string cannot be formatted as follows for all target Python versions
443+
f"{1=:"foo}"
444+
```
445+
446+
For nested f-strings, Ruff alternates quote styles, starting with the [configured quote style] for the
447+
outermost f-string. For example, consider the following f-string:
448+
449+
```python
450+
# format.quote-style = "double"
451+
452+
f"outer f-string {f"nested f-string {f"another nested f-string"} end"} end"
453+
```
454+
455+
Ruff formats it as:
456+
457+
```python
458+
f"outer f-string {f'nested f-string {f"another nested f-string"} end'} end"
459+
```
460+
461+
#### Line breaks
462+
463+
Starting with Python 3.12 ([PEP 701](https://peps.python.org/pep-0701/)), the expression parts of an f-string can
464+
span multiple lines. Ruff needs to decide when to introduce a line break in an f-string expression.
465+
This depends on the semantic content of the expression parts of an f-string - for example,
466+
introducing a line break in the middle of a natural-language sentence is undesirable. Since Ruff
467+
doesn't have enough information to make that decision, it adopts a heuristic similar to [Prettier](https://prettier.io/docs/en/next/rationale.html#template-literals):
468+
it will only split the expression parts of an f-string across multiple lines if there was already a line break
469+
within any of the expression parts.
470+
471+
For example, the following code:
472+
473+
```python
474+
f"this f-string has a multiline expression {
475+
['red', 'green', 'blue', 'yellow',]} and does not fit within the line length"
476+
```
477+
478+
... is formatted as:
479+
480+
```python
481+
# The list expression is split across multiple lines because of the trailing comma
482+
f"this f-string has a multiline expression {
483+
[
484+
'red',
485+
'green',
486+
'blue',
487+
'yellow',
488+
]
489+
} and does not fit within the line length"
490+
```
491+
492+
But, the following will not be split across multiple lines even though it exceeds the line length:
493+
494+
```python
495+
f"this f-string has a multiline expression {['red', 'green', 'blue', 'yellow']} and does not fit within the line length"
496+
```
497+
498+
If you want Ruff to split an f-string across multiple lines, ensure there's a linebreak somewhere within the
499+
`{...}` parts of an f-string.
500+
501+
[self-documenting f-string]: https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging
502+
[configured quote style]: settings.md/#format_quote-style
503+
406504
## Sorting imports
407505

408506
Currently, the Ruff formatter does not sort imports. In order to both sort imports and format,

docs/formatter/black.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ f'test{inner + "nested_string"} including math {5 ** 3 + 10}'
253253
f"test{inner + 'nested_string'} including math {5**3 + 10}"
254254
```
255255

256+
For more details on the formatting style, refer to the [f-string
257+
formatting](../formatter.md#f-string-formatting) section.
258+
256259
### Implicit concatenated strings
257260

258261
Ruff merges implicitly concatenated strings if the entire string fits on a single line:
@@ -348,7 +351,7 @@ match some_variable:
348351
) or last_condition:
349352
pass
350353

351-
354+
352355
# Ruff
353356
match some_variable:
354357
case "short-guard" if other_condition:

0 commit comments

Comments
 (0)