diff --git a/crates/ruff_python_formatter/README.md b/crates/ruff_python_formatter/README.md index 19f0c0065159e..d6ed9a96d8e26 100644 --- a/crates/ruff_python_formatter/README.md +++ b/crates/ruff_python_formatter/README.md @@ -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 diff --git a/docs/faq.md b/docs/faq.md index 5e15dc63f19c8..431ada6329be2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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? diff --git a/docs/formatter.md b/docs/formatter.md index 443d2712aa179..01eb4fb396ff8 100644 --- a/docs/formatter.md +++ b/docs/formatter.md @@ -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 @@ -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 @@ -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, diff --git a/docs/formatter/black.md b/docs/formatter/black.md index e696f68cfe49c..d560f1706b13d 100644 --- a/docs/formatter/black.md +++ b/docs/formatter/black.md @@ -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: @@ -348,7 +351,7 @@ match some_variable: ) or last_condition: pass - + # Ruff match some_variable: case "short-guard" if other_condition: