Skip to content

Commit

Permalink
Add f-string formatting to the docs (#15367)
Browse files Browse the repository at this point in the history
Revive #15341 as it got removed
from the latest rebase in #15238.
  • Loading branch information
dhruvmanila authored and MichaReiser committed Jan 9, 2025
1 parent 29f6653 commit f706c3f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 18 deletions.
2 changes: 1 addition & 1 deletion crates/ruff_python_formatter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ filed in the issue tracker. If you've identified a new deviation, please [file a

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

## Getting started

Expand Down
2 changes: 1 addition & 1 deletion docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ to see a few differences on the margins, but the vast majority of your code shou
When run over _non_-Black-formatted code, the formatter makes some different decisions than Black,
and so more deviations should be expected, especially around the treatment of end-of-line comments.

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

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

Expand Down
128 changes: 113 additions & 15 deletions docs/formatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ adoption is minimally disruptive for the vast majority of projects.

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

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),
which aims for "consistency, generality, readability and reducing git diffs". To give you a sense
Expand Down Expand Up @@ -373,21 +373,10 @@ Meanwhile, `ruff format --check` exits with the following status codes:
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an
internal error.

## Black compatibility
## Style Guide

The formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black).

Specifically, the formatter is intended to emit near-identical output when run over Black-formatted
code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines
are formatted identically. When migrating an existing project from Black to Ruff, you should expect
to see a few differences on the margins, but the vast majority of your code should be unchanged.

When run over _non_-Black-formatted code, the formatter makes some different decisions than Black,
and so more deviations should be expected, especially around the treatment of end-of-line comments.

If you identify deviations in your project, spot-check them against the [known deviations](formatter/black.md),
as well as the [unintentional deviations](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter)
filed in the issue tracker. If you've identified a new deviation, please [file an issue](https://github.com/astral-sh/ruff/issues/new).
This section documents the areas where the Ruff formatter goes beyond Black in terms of code style.

### Intentional deviations

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

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

### Preview style
Similar to [Black](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style), Ruff implements formatting changes

Similar to [Black](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style), Ruff implements formatting changes
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).

### F-string formatting

_Stabilized in Ruff 0.9.0_

Unlike Black, Ruff formats the expression parts of f-strings which are the parts inside the curly
braces `{...}`. This is a [known deviation](formatter/black.md#f-strings) from Black.

Ruff employs several heuristics to determine how an f-string should be formatted which are detailed
below.

#### Quotes

Ruff will use the [configured quote style] for the f-string expression unless doing so would result in
invalid syntax for the target Python version or requires more backslash escapes than the original
expression. Specifically, Ruff will preserve the original quote style for the following cases:

When the target Python version is < 3.12 and a [self-documenting f-string] contains a string
literal with the [configured quote style]:

```python
# format.quote-style = "double"

f'{10 + len("hello")=}'
# This f-string cannot be formatted as follows when targeting Python < 3.12
f"{10 + len("hello")=}"
```

When the target Python version is < 3.12 and an f-string contains any triple-quoted string, byte
or f-string literal that contains the [configured quote style]:

```python
# format.quote-style = "double"

f'{"""nested " """}'`
# This f-string cannot be formatted as follows when targeting Python < 3.12
f"{'''nested " '''}``
```
For all target Python versions, when a [self-documenting f-string] contains an expression between
the curly braces (`{...}`) with a format specifier containing the [configured quote style]:

```python
# format.quote-style = "double"

f'{1=:"foo}'
# This f-string cannot be formatted as follows for all target Python versions
f"{1=:"foo}"
```

For nested f-strings, Ruff alternates quote styles, starting with the [configured quote style] for the
outermost f-string. For example, consider the following f-string:

```python
# format.quote-style = "double"

f"outer f-string {f"nested f-string {f"another nested f-string"} end"} end"
```

Ruff formats it as:

```python
f"outer f-string {f'nested f-string {f"another nested f-string"} end'} end"
```

#### Line breaks

Starting with Python 3.12 ([PEP 701](https://peps.python.org/pep-0701/)), the expression parts of an f-string can
span multiple lines. Ruff needs to decide when to introduce a line break in an f-string expression.
This depends on the semantic content of the expression parts of an f-string - for example,
introducing a line break in the middle of a natural-language sentence is undesirable. Since Ruff
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):
it will only split the expression parts of an f-string across multiple lines if there was already a line break
within any of the expression parts.

For example, the following code:

```python
f"this f-string has a multiline expression {
['red', 'green', 'blue', 'yellow',]} and does not fit within the line length"
```

... is formatted as:

```python
# The list expression is split across multiple lines because of the trailing comma
f"this f-string has a multiline expression {
[
'red',
'green',
'blue',
'yellow',
]
} and does not fit within the line length"
```

But, the following will not be split across multiple lines even though it exceeds the line length:

```python
f"this f-string has a multiline expression {['red', 'green', 'blue', 'yellow']} and does not fit within the line length"
```

If you want Ruff to split an f-string across multiple lines, ensure there's a linebreak somewhere within the
`{...}` parts of an f-string.

[self-documenting f-string]: https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging
[configured quote style]: settings.md/#format_quote-style

## Sorting imports

Currently, the Ruff formatter does not sort imports. In order to both sort imports and format,
Expand Down
5 changes: 4 additions & 1 deletion docs/formatter/black.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ f'test{inner + "nested_string"} including math {5 ** 3 + 10}'
f"test{inner + 'nested_string'} including math {5**3 + 10}"
```

For more details on the formatting style, refer to the [f-string
formatting](../formatter.md#f-string-formatting) section.

### Implicit concatenated strings

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


# Ruff
match some_variable:
case "short-guard" if other_condition:
Expand Down

0 comments on commit f706c3f

Please sign in to comment.