Compare commits

..

8 Commits

Author SHA1 Message Date
Charlie Marsh
235cfb7976 Bump version to v0.2.2 (#10018) 2024-02-17 22:15:04 +00:00
Dhruv Manilawala
91ae81b565 Move RUF001, RUF002 to AST checker (#9993)
## Summary

Part of #7595 

This PR moves the `RUF001` and `RUF002` rules to the AST checker. This
removes the use of docstring detection from these rules.

## Test Plan

As this is just a refactor, make sure existing test cases pass.
2024-02-17 17:01:31 +00:00
Adam Kuhn
d46c5d8ac8 docs: Formatter compatibility warning for D207 and D300 (#10007)
- Update docs to mention formatter compatibility interactions for
under-indentation (D207) and triple-single-quotes (D300)
- Changes verified locally with mkdocs
- Closes: https://github.com/astral-sh/ruff/issues/9675
2024-02-17 07:37:38 -05:00
Jane Lewis
20217e9bbd Fix panic on RUF027 (#9990)
## Summary

Fixes #9895 

The cause for this panic came from an offset error in the code. When
analyzing a hypothetical f-string, we attempt to re-parse it as an
f-string, and use the AST data to determine, among other things, whether
the format specifiers are correct. To determine the 'correctness' of a
format specifier, we actually have to re-parse the format specifier, and
this is where the issue lies. To get the source text for the specifier,
we were taking a slice from the original file source text... even though
the AST data for the specifier belongs to the standalone parsed f-string
expression, meaning that the ranges are going to be way off. In a file
with Unicode, this can cause panics if the slice is inside a char
boundary.

To fix this, we now slice from the temporary source we created earlier
to parse the literal as an f-string.

## Test Plan

The RUF027 snapshot test was amended to include a string with format
specifiers which we _should_ be calling out. This is to ensure we do
slice format specifiers from the source text correctly.
2024-02-16 20:04:39 +00:00
Dhruv Manilawala
72bf1c2880 Preview minimal f-string formatting (#9642)
## Summary

_This is preview only feature and is available using the `--preview`
command-line flag._

With the implementation of [PEP 701] in Python 3.12, f-strings can now
be broken into multiple lines, can contain comments, and can re-use the
same quote character. Currently, no other Python formatter formats the
f-strings so there's some discussion which needs to happen in defining
the style used for f-string formatting. Relevant discussion:
https://github.com/astral-sh/ruff/discussions/9785

The goal for this PR is to add minimal support for f-string formatting.
This would be to format expression within the replacement field without
introducing any major style changes.

### Newlines

The heuristics for adding newline is similar to that of
[Prettier](https://prettier.io/docs/en/next/rationale.html#template-literals)
where the formatter would only split an expression in the replacement
field across multiple lines if there was already a line break within the
replacement field.

In other words, the formatter would not add any newlines unless they
were already present i.e., they were added by the user. This makes
breaking any expression inside an f-string optional and in control of
the user. For example,

```python
# We wouldn't break this
aaaaaaaaaaa = f"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc"

# But, we would break the following as there's already a newline
aaaaaaaaaaa = f"asaaaaaaaaaaaaaaaa {
	aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc"
```


If there are comments in any of the replacement field of the f-string,
then it will always be a multi-line f-string in which case the formatter
would prefer to break expressions i.e., introduce newlines. For example,

```python
x = f"{ # comment
    a }"
```

### Quotes

The logic for formatting quotes remains unchanged. The existing logic is
used to determine the necessary quote char and is used accordingly.

Now, if the expression inside an f-string is itself a string like, then
we need to make sure to preserve the existing quote and not change it to
the preferred quote unless it's 3.12. For example,

```python
f"outer {'inner'} outer"

# For pre 3.12, preserve the single quote
f"outer {'inner'} outer"

# While for 3.12 and later, the quotes can be changed
f"outer {"inner"} outer"
```

But, for triple-quoted strings, we can re-use the same quote char unless
the inner string is itself a triple-quoted string.

```python
f"""outer {"inner"} outer"""  # valid
f"""outer {'''inner'''} outer"""  # preserve the single quote char for the inner string
```

### Debug expressions

If debug expressions are present in the replacement field of a f-string,
then the whitespace needs to be preserved as they will be rendered as it
is (for example, `f"{ x = }"`. If there are any nested f-strings, then
the whitespace in them needs to be preserved as well which means that
we'll stop formatting the f-string as soon as we encounter a debug
expression.

```python
f"outer {   x =  !s  :.3f}"
#                  ^^
#                  We can remove these whitespaces
```

Now, the whitespace doesn't need to be preserved around conversion spec
and format specifiers, so we'll format them as usual but we won't be
formatting any nested f-string within the format specifier.

### Miscellaneous

- The
[`hug_parens_with_braces_and_square_brackets`](https://github.com/astral-sh/ruff/issues/8279)
preview style isn't implemented w.r.t. the f-string curly braces.
- The
[indentation](https://github.com/astral-sh/ruff/discussions/9785#discussioncomment-8470590)
is always relative to the f-string containing statement

## Test Plan

* Add new test cases
* Review existing snapshot changes
* Review the ecosystem changes

[PEP 701]: https://peps.python.org/pep-0701/
2024-02-16 20:28:11 +05:30
Jacob Coffee
c47ff658e4 chore(docs): update Discord invite to permalink (#10005)
## Summary

Update the Discord unique-id invite links to use the company permalink.

## Test Plan

Visiting the links
2024-02-15 23:16:02 -05:00
Adrien Ball
c3bba54b6b Fix SIM113 false positive with async for loops (#9996)
## Summary
Ignore `async for` loops when checking the SIM113 rule.

Closes #9995 

## Test Plan
A new test case was added to SIM113.py with an async for loop.
2024-02-15 22:40:01 -05:00
Micha Reiser
fe79798c12 split string module (#9987) 2024-02-14 18:54:55 +01:00
109 changed files with 3825 additions and 3838 deletions

View File

@@ -1,5 +1,66 @@
# Changelog
## 0.2.2
Highlights include:
- Initial support formatting f-strings (in `--preview`).
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config`
argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
- Significant performance improvements in Ruff's lexer, parser, and lint rules.
### Preview features
- Implement minimal f-string formatting ([#9642](https://github.com/astral-sh/ruff/pull/9642))
- \[`pycodestyle`\] Add blank line(s) rules (`E301`, `E302`, `E303`, `E304`, `E305`, `E306`) ([#9266](https://github.com/astral-sh/ruff/pull/9266))
- \[`refurb`\] Implement `readlines_in_for` (`FURB129`) ([#9880](https://github.com/astral-sh/ruff/pull/9880))
### Rule changes
- \[`ruff`\] Ensure closing parentheses for multiline sequences are always on their own line (`RUF022`, `RUF023`) ([#9793](https://github.com/astral-sh/ruff/pull/9793))
- \[`numpy`\] Add missing deprecation violations (`NPY002`) ([#9862](https://github.com/astral-sh/ruff/pull/9862))
- \[`flake8-bandit`\] Detect `mark_safe` usages in decorators ([#9887](https://github.com/astral-sh/ruff/pull/9887))
- \[`ruff`\] Expand `asyncio-dangling-task` (`RUF006`) to include `new_event_loop` ([#9976](https://github.com/astral-sh/ruff/pull/9976))
- \[`flake8-pyi`\] Ignore 'unused' private type dicts in class scopes ([#9952](https://github.com/astral-sh/ruff/pull/9952))
### Formatter
- Docstring formatting: Preserve tab indentation when using `indent-style=tabs` ([#9915](https://github.com/astral-sh/ruff/pull/9915))
- Disable top-level docstring formatting for notebooks ([#9957](https://github.com/astral-sh/ruff/pull/9957))
- Stabilize quote-style's `preserve` mode ([#9922](https://github.com/astral-sh/ruff/pull/9922))
### CLI
- Allow arbitrary configuration options to be overridden via the CLI ([#9599](https://github.com/astral-sh/ruff/pull/9599))
### Bug fixes
- Make `show-settings` filters directory-agnostic ([#9866](https://github.com/astral-sh/ruff/pull/9866))
- Respect duplicates when rewriting type aliases ([#9905](https://github.com/astral-sh/ruff/pull/9905))
- Respect tuple assignments in typing analyzer ([#9969](https://github.com/astral-sh/ruff/pull/9969))
- Use atomic write when persisting cache ([#9981](https://github.com/astral-sh/ruff/pull/9981))
- Use non-parenthesized range for `DebugText` ([#9953](https://github.com/astral-sh/ruff/pull/9953))
- \[`flake8-simplify`\] Avoid false positive with `async` for loops (`SIM113`) ([#9996](https://github.com/astral-sh/ruff/pull/9996))
- \[`flake8-trio`\] Respect `async with` in `timeout-without-await` ([#9859](https://github.com/astral-sh/ruff/pull/9859))
- \[`perflint`\] Catch a wider range of mutations in `PERF101` ([#9955](https://github.com/astral-sh/ruff/pull/9955))
- \[`pycodestyle`\] Fix `E30X` panics on blank lines with trailing white spaces ([#9907](https://github.com/astral-sh/ruff/pull/9907))
- \[`pydocstyle`\] Allow using `parameters` as a subsection header (`D405`) ([#9894](https://github.com/astral-sh/ruff/pull/9894))
- \[`pydocstyle`\] Fix blank-line docstring rules for module-level docstrings ([#9878](https://github.com/astral-sh/ruff/pull/9878))
- \[`pylint`\] Accept 0.0 and 1.0 as common magic values (`PLR2004`) ([#9964](https://github.com/astral-sh/ruff/pull/9964))
- \[`pylint`\] Avoid suggesting set rewrites for non-hashable types ([#9956](https://github.com/astral-sh/ruff/pull/9956))
- \[`ruff`\] Avoid false negatives with string literals inside of method calls (`RUF027`) ([#9865](https://github.com/astral-sh/ruff/pull/9865))
- \[`ruff`\] Fix panic on with f-string detection (`RUF027`) ([#9990](https://github.com/astral-sh/ruff/pull/9990))
- \[`ruff`\] Ignore builtins when detecting missing f-strings ([#9849](https://github.com/astral-sh/ruff/pull/9849))
### Performance
- Use `memchr` for string lexing ([#9888](https://github.com/astral-sh/ruff/pull/9888))
- Use `memchr` for tab-indentation detection ([#9853](https://github.com/astral-sh/ruff/pull/9853))
- Reduce `Result<Tok, LexicalError>` size by using `Box<str>` instead of `String` ([#9885](https://github.com/astral-sh/ruff/pull/9885))
- Reduce size of `Expr` from 80 to 64 bytes ([#9900](https://github.com/astral-sh/ruff/pull/9900))
- Improve trailing comma rule performance ([#9867](https://github.com/astral-sh/ruff/pull/9867))
- Remove unnecessary string cloning from the parser ([#9884](https://github.com/astral-sh/ruff/pull/9884))
## 0.2.1
This release includes support for range formatting (i.e., the ability to format specific lines

View File

@@ -39,7 +39,7 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)

6
Cargo.lock generated
View File

@@ -1979,7 +1979,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"anyhow",
"argfile",
@@ -2140,7 +2140,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2394,7 +2394,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"anyhow",
"clap",

View File

@@ -8,7 +8,7 @@
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
[**Discord**](https://discord.com/invite/astral-sh) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
An extremely fast Python linter and code formatter, written in Rust.
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.2.1
rev: v0.2.2
hooks:
# Run the linter.
- id: ruff
@@ -341,14 +341,14 @@ For a complete enumeration of the supported rules, see [_Rules_](https://docs.as
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
You can also join us on [**Discord**](https://discord.com/invite/astral-sh).
## Support
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
You can also ask for help on [**Discord**](https://discord.com/invite/astral-sh).
## Acknowledgements

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.2.1"
version = "0.2.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,8 +1,7 @@
use std::cell::Cell;
use std::num::NonZeroU8;
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
use std::cell::Cell;
use std::num::NonZeroU8;
/// A Tag marking the start and end of some content to which some special formatting should be applied.
///
@@ -100,10 +99,6 @@ pub enum Tag {
}
impl Tag {
pub const fn align(count: NonZeroU8) -> Tag {
Tag::StartAlign(Align(count))
}
/// Returns `true` if `self` is any start tag.
pub const fn is_start(&self) -> bool {
matches!(

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.2.1"
version = "0.2.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -193,3 +193,11 @@ def func():
for y in range(5):
g(x, idx)
idx += 1
async def func():
# OK (for loop is async)
idx = 0
async for x in async_gen():
g(x, idx)
idx += 1

View File

@@ -68,3 +68,7 @@ def method_calls():
first = "Wendy"
last = "Appleseed"
value.method("{first} {last}") # RUF027
def format_specifiers():
a = 4
b = "{a:b} {a:^5}"

View File

@@ -0,0 +1,2 @@
# 测试eval函数,eval()函数用来执行一个字符串表达式并返t表达式的值。另外可以讲字符串转换成列表或元组或字典
a = "{1: 'a', 2: 'b'}"

View File

@@ -2,10 +2,16 @@ use ruff_python_ast::StringLike;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bandit, flake8_pyi};
use crate::rules::{flake8_bandit, flake8_pyi, ruff};
/// Run lint rules over a [`StringLike`] syntax nodes.
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
if checker.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
]) {
ruff::rules::ambiguous_unicode_character_string(checker, string_like);
}
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
flake8_bandit::rules::hardcoded_bind_all_interfaces(checker, string_like);
}

View File

@@ -6,17 +6,14 @@ use ruff_notebook::CellOffsets;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::BlankLinesChecker;
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
@@ -66,31 +63,15 @@ pub(crate) fn check_tokens(
pylint::rules::empty_comments(&mut diagnostics, indexer, locator);
}
if settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]) {
let mut state_machine = StateMachine::default();
for &(ref tok, range) in tokens.iter().flatten() {
let is_docstring = state_machine.consume(tok);
let context = match tok {
Tok::String { .. } => {
if is_docstring {
Context::Docstring
} else {
Context::String
}
}
Tok::FStringMiddle { .. } => Context::String,
Tok::Comment(_) => Context::Comment,
_ => continue,
};
ruff::rules::ambiguous_unicode_character(
if settings
.rules
.enabled(Rule::AmbiguousUnicodeCharacterComment)
{
for range in indexer.comment_ranges() {
ruff::rules::ambiguous_unicode_character_comment(
&mut diagnostics,
locator,
range,
context,
*range,
settings,
);
}

View File

@@ -256,8 +256,6 @@ impl Rule {
| Rule::MixedSpacesAndTabs
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
| Rule::AvoidableEscapedQuote
| Rule::BadQuotesDocstring
| Rule::BadQuotesInlineString

View File

@@ -49,6 +49,11 @@ impl Violation for EnumerateForLoop {
/// SIM113
pub(crate) fn enumerate_for_loop(checker: &mut Checker, for_stmt: &ast::StmtFor) {
// If the loop is async, abort.
if for_stmt.is_async {
return;
}
// If the loop contains a `continue`, abort.
let mut visitor = LoopControlFlowVisitor::default();
visitor.visit_body(&for_stmt.body);

View File

@@ -87,12 +87,17 @@ impl Violation for IndentWithSpaces {
/// """
/// ```
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent indentation, making the rule redundant.
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
/// [formatter]: https://docs.astral.sh/ruff/formatter/
#[violation]
pub struct UnderIndentation;

View File

@@ -27,10 +27,16 @@ use crate::docstrings::Docstring;
/// """Return the pathname of the KOS root directory."""
/// ```
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent quotes, making the rule redundant.
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [formatter]: https://docs.astral.sh/ruff/formatter/
#[violation]
pub struct TripleSingleQuotes {
expected_quote: Quote,

View File

@@ -48,6 +48,7 @@ mod tests {
#[test_case(Rule::DefaultFactoryKwarg, Path::new("RUF026.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_0.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_1.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_2.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -4,9 +4,11 @@ use bitflags::bitflags;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::StringLike;
use ruff_source_file::Locator;
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::ruff::rules::confusables::confusable;
use crate::rules::ruff::rules::Context;
@@ -171,16 +173,59 @@ impl Violation for AmbiguousUnicodeCharacterComment {
}
}
/// RUF001, RUF002, RUF003
pub(crate) fn ambiguous_unicode_character(
/// RUF003
pub(crate) fn ambiguous_unicode_character_comment(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
range: TextRange,
settings: &LinterSettings,
) {
let text = locator.slice(range);
ambiguous_unicode_character(diagnostics, text, range, Context::Comment, settings);
}
/// RUF001, RUF002
pub(crate) fn ambiguous_unicode_character_string(checker: &mut Checker, string_like: StringLike) {
let context = if checker.semantic().in_docstring() {
Context::Docstring
} else {
Context::String
};
match string_like {
StringLike::StringLiteral(string_literal) => {
for string in &string_literal.value {
let text = checker.locator().slice(string);
ambiguous_unicode_character(
&mut checker.diagnostics,
text,
string.range(),
context,
checker.settings,
);
}
}
StringLike::FStringLiteral(f_string_literal) => {
let text = checker.locator().slice(f_string_literal);
ambiguous_unicode_character(
&mut checker.diagnostics,
text,
f_string_literal.range(),
context,
checker.settings,
);
}
StringLike::BytesLiteral(_) => (),
}
}
fn ambiguous_unicode_character(
diagnostics: &mut Vec<Diagnostic>,
text: &str,
range: TextRange,
context: Context,
settings: &LinterSettings,
) {
let text = locator.slice(range);
// Most of the time, we don't need to check for ambiguous unicode characters at all.
if text.is_ascii() {
return;

View File

@@ -88,8 +88,10 @@ fn should_be_fstring(
return false;
}
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) =
parse_expression(&format!("f{}", locator.slice(literal.range())))
let fstring_expr = format!("f{}", locator.slice(literal));
// Note: Range offsets for `value` are based on `fstring_expr`
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) = parse_expression(&fstring_expr)
else {
return false;
};
@@ -159,7 +161,7 @@ fn should_be_fstring(
has_name = true;
}
if let Some(spec) = &element.format_spec {
let spec = locator.slice(spec.range());
let spec = &fstring_expr[spec.range()];
if FormatSpec::parse(spec).is_err() {
return false;
}

View File

@@ -285,6 +285,8 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
69 | last = "Appleseed"
70 | value.method("{first} {last}") # RUF027
| ^^^^^^^^^^^^^^^^ RUF027
71 |
72 | def format_specifiers():
|
= help: Add `f` prefix
@@ -294,5 +296,24 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
69 69 | last = "Appleseed"
70 |- value.method("{first} {last}") # RUF027
70 |+ value.method(f"{first} {last}") # RUF027
71 71 |
72 72 | def format_specifiers():
73 73 | a = 4
RUF027_0.py:74:9: RUF027 [*] Possible f-string without an `f` prefix
|
72 | def format_specifiers():
73 | a = 4
74 | b = "{a:b} {a:^5}"
| ^^^^^^^^^^^^^^ RUF027
|
= help: Add `f` prefix
Unsafe fix
71 71 |
72 72 | def format_specifiers():
73 73 | a = 4
74 |- b = "{a:b} {a:^5}"
74 |+ b = f"{a:b} {a:^5}"

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---

View File

@@ -0,0 +1,8 @@
[
{
"preview": "enabled"
},
{
"preview": "disabled"
}
]

View File

@@ -30,22 +30,22 @@ result_f = (
# an expression inside a formatted value
(
f'{1}'
# comment
# comment 1
''
)
(
f'{1}' # comment
f'{1}' # comment 2
f'{2}'
)
(
f'{1}'
f'{2}' # comment
f'{2}' # comment 3
)
(
1, ( # comment
1, ( # comment 4
f'{2}'
)
)
@@ -53,7 +53,7 @@ result_f = (
(
(
f'{1}'
# comment
# comment 5
),
2
)
@@ -62,3 +62,221 @@ result_f = (
x = f'''a{""}b'''
y = f'''c{1}d"""e'''
z = f'''a{""}b''' f'''c{1}d"""e'''
# F-String formatting test cases (Preview)
# Simple expression with a mix of debug expression and comments.
x = f"{a}"
x = f"{
a = }"
x = f"{ # comment 6
a }"
x = f"{ # comment 7
a = }"
# Remove the parentheses as adding them doesn't make then fit within the line length limit.
# This is similar to how we format it before f-string formatting.
aaaaaaaaaaa = (
f"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd } cccccccccc"
)
# Here, we would use the best fit layout to put the f-string indented on the next line
# similar to the next example.
aaaaaaaaaaa = f"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc"
aaaaaaaaaaa = (
f"asaaaaaaaaaaaaaaaa { aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc } cccccccccc"
)
# This should never add the optional parentheses because even after adding them, the
# f-string exceeds the line length limit.
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc"
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc"
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 8
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc"
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc"
# Multiple larger expressions which exceeds the line length limit. Here, we need to decide
# whether to split at the first or second expression. This should work similarly to the
# assignment statement formatting where we split from right to left in preview mode.
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee"
# The above example won't split but when we start introducing line breaks:
x = f"aaaaaaaaaaaa {
bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee"
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb
} cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee"
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc {
ddddddddddddddd } eeeeeeeeeeeeee"
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb } cccccccccccccccccccc { ddddddddddddddd
} eeeeeeeeeeeeee"
# But, in case comments are present, we would split at the expression containing the
# comments:
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb # comment 10
} cccccccccccccccccccc { ddddddddddddddd } eeeeeeeeeeeeee"
x = f"aaaaaaaaaaaa { bbbbbbbbbbbbbb
} cccccccccccccccccccc { # comment 11
ddddddddddddddd } eeeeeeeeeeeeee"
# Here, the expression part itself starts with a curly brace so we need to add an extra
# space between the opening curly brace and the expression.
x = f"{ {'x': 1, 'y': 2} }"
# Although the extra space isn't required before the ending curly brace, we add it for
# consistency.
x = f"{ {'x': 1, 'y': 2}}"
x = f"{ {'x': 1, 'y': 2} = }"
x = f"{ # comment 12
{'x': 1, 'y': 2} }"
x = f"{ # comment 13
{'x': 1, 'y': 2} = }"
# But, in this case, we would split the expression itself because it exceeds the line
# length limit so we need not add the extra space.
xxxxxxx = f"{
{'aaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbb', 'ccccccccccccccccccccc'}
}"
# And, split the expression itself because it exceeds the line length.
xxxxxxx = f"{
{'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'}
}"
# Quotes
f"foo 'bar' {x}"
f"foo \"bar\" {x}"
f'foo "bar" {x}'
f'foo \'bar\' {x}'
f"foo {"bar"}"
f"foo {'\'bar\''}"
# Here, the formatter will remove the escapes which is correct because they aren't allowed
# pre 3.12. This means we can assume that the f-string is used in the context of 3.12.
f"foo {'\"bar\"'}"
# Triple-quoted strings
# It's ok to use the same quote char for the inner string if it's single-quoted.
f"""test {'inner'}"""
f"""test {"inner"}"""
# But if the inner string is also triple-quoted then we should preserve the existing quotes.
f"""test {'''inner'''}"""
# Magic trailing comma
#
# The expression formatting will result in breaking it across multiple lines with a
# trailing comma but as the expression isn't already broken, we will remove all the line
# breaks which results in the trailing comma being present. This test case makes sure
# that the trailing comma is removed as well.
f"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee']} aaaaaaa"
# And, if the trailing comma is already present, we still need to remove it.
f"aaaaaaa {['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbb', 'ccccccccccccccccc', 'ddddddddddddddd', 'eeeeeeeeeeeeee',]} aaaaaaa"
# Keep this Multiline by breaking it at the square brackets.
f"""aaaaaa {[
xxxxxxxx,
yyyyyyyy,
]} ccc"""
# Add the magic trailing comma because the elements don't fit within the line length limit
# when collapsed.
f"aaaaaa {[
xxxxxxxxxxxx,
xxxxxxxxxxxx,
xxxxxxxxxxxx,
xxxxxxxxxxxx,
xxxxxxxxxxxx,
xxxxxxxxxxxx,
yyyyyyyyyyyy
]} ccccccc"
# Remove the parenthese because they aren't required
xxxxxxxxxxxxxxx = (
f"aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbb {
xxxxxxxxxxx # comment 14
+ yyyyyyyyyy
} dddddddddd"
)
# Comments
# No comments should be dropped!
f"{ # comment 15
# comment 16
foo # comment 17
# comment 18
}" # comment 19
# comment 20
# Conversion flags
#
# This is not a valid Python code because of the additional whitespace between the `!`
# and conversion type. But, our parser isn't strict about this. This should probably be
# removed once we have a strict parser.
x = f"aaaaaaaaa { x ! r }"
# Even in the case of debug expresions, we only need to preserve the whitespace within
# the expression part of the replacement field.
x = f"aaaaaaaaa { x = ! r }"
# Combine conversion flags with format specifiers
x = f"{x = ! s
:>0
}"
# This is interesting. There can be a comment after the format specifier but only if it's
# on it's own line. Refer to https://github.com/astral-sh/ruff/pull/7787 for more details.
# We'll format is as trailing comments.
x = f"{x !s
:>0
# comment 21
}"
x = f"""
{ # comment 22
x = :.0{y # comment 23
}f}"""
# Here, the debug expression is in a nested f-string so we should start preserving
# whitespaces from that point onwards. This means we should format the outer f-string.
x = f"""{"foo " + # comment 24
f"{ x =
}" # comment 25
}
"""
# Mix of various features.
f"{ # comment 26
foo # after foo
:>{
x # after x
}
# comment 27
# comment 28
} woah {x}"
# Indentation
# What should be the indentation?
# https://github.com/astral-sh/ruff/discussions/9785#discussioncomment-8470590
if indent0:
if indent1:
if indent2:
foo = f"""hello world
hello {
f"aaaaaaa {
[
'aaaaaaaaaaaaaaaaaaaaa',
'bbbbbbbbbbbbbbbbbbbbb',
'ccccccccccccccccccccc',
'ddddddddddddddddddddd'
]
} bbbbbbbb" +
[
'aaaaaaaaaaaaaaaaaaaaa',
'bbbbbbbbbbbbbbbbbbbbb',
'ccccccccccccccccccccc',
'ddddddddddddddddddddd'
]
} --------
"""

View File

@@ -0,0 +1,5 @@
[
{
"target_version": "py312"
}
]

View File

@@ -0,0 +1,6 @@
# This file contains test cases only for cases where the logic tests for whether
# the target version is 3.12 or later. A user can have 3.12 syntax even if the target
# version isn't set.
# Quotes re-use
f"{'a'}"

View File

@@ -1,7 +1,7 @@
use ruff_formatter::{write, Argument, Arguments};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::context::{NodeLevel, WithNodeLevel};
use crate::context::{FStringState, NodeLevel, WithNodeLevel};
use crate::other::commas::has_magic_trailing_comma;
use crate::prelude::*;
@@ -206,6 +206,16 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
pub(crate) fn finish(&mut self) -> FormatResult<()> {
self.result.and_then(|()| {
// If the formatter is inside an f-string expression element, and the layout
// is flat, then we don't need to add a trailing comma.
if let FStringState::InsideExpressionElement(context) =
self.fmt.context().f_string_state()
{
if context.layout().is_flat() {
return Ok(());
}
}
if let Some(last_end) = self.entries.position() {
let magic_trailing_comma = has_magic_trailing_comma(
TextRange::new(last_end, self.sequence_end),

View File

@@ -289,6 +289,28 @@ fn handle_enclosed_comment<'a>(
}
}
AnyNodeRef::FString(fstring) => CommentPlacement::dangling(fstring, comment),
AnyNodeRef::FStringExpressionElement(_) => {
// Handle comments after the format specifier (should be rare):
//
// ```python
// f"literal {
// expr:.3f
// # comment
// }"
// ```
//
// This is a valid comment placement.
if matches!(
comment.preceding_node(),
Some(
AnyNodeRef::FStringExpressionElement(_) | AnyNodeRef::FStringLiteralElement(_)
)
) {
CommentPlacement::trailing(comment.enclosing_node(), comment)
} else {
handle_bracketed_end_of_line_comment(comment, locator)
}
}
AnyNodeRef::ExprList(_)
| AnyNodeRef::ExprSet(_)
| AnyNodeRef::ExprListComp(_)

View File

@@ -1,4 +1,5 @@
use crate::comments::Comments;
use crate::other::f_string::FStringContext;
use crate::string::QuoteChar;
use crate::PyFormatOptions;
use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode};
@@ -22,6 +23,8 @@ pub struct PyFormatContext<'a> {
/// quote style that is inverted from the one here in order to ensure that
/// the formatted Python code will be valid.
docstring: Option<QuoteChar>,
/// The state of the formatter with respect to f-strings.
f_string_state: FStringState,
}
impl<'a> PyFormatContext<'a> {
@@ -33,6 +36,7 @@ impl<'a> PyFormatContext<'a> {
node_level: NodeLevel::TopLevel(TopLevelStatementPosition::Other),
indent_level: IndentLevel::new(0),
docstring: None,
f_string_state: FStringState::Outside,
}
}
@@ -86,6 +90,14 @@ impl<'a> PyFormatContext<'a> {
}
}
pub(crate) fn f_string_state(&self) -> FStringState {
self.f_string_state
}
pub(crate) fn set_f_string_state(&mut self, f_string_state: FStringState) {
self.f_string_state = f_string_state;
}
/// Returns `true` if preview mode is enabled.
pub(crate) const fn is_preview(&self) -> bool {
self.options.preview().is_enabled()
@@ -115,6 +127,18 @@ impl Debug for PyFormatContext<'_> {
}
}
#[derive(Copy, Clone, Debug, Default)]
pub(crate) enum FStringState {
/// The formatter is inside an f-string expression element i.e., between the
/// curly brace in `f"foo {x}"`.
///
/// The containing `FStringContext` is the surrounding f-string context.
InsideExpressionElement(FStringContext),
/// The formatter is outside an f-string.
#[default]
Outside,
}
/// The position of a top-level statement in the module.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub(crate) enum TopLevelStatementPosition {
@@ -332,3 +356,65 @@ where
.set_indent_level(self.saved_level);
}
}
pub(crate) struct WithFStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
buffer: D,
saved_location: FStringState,
}
impl<'a, B, D> WithFStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
pub(crate) fn new(expr_location: FStringState, mut buffer: D) -> Self {
let context = buffer.state_mut().context_mut();
let saved_location = context.f_string_state();
context.set_f_string_state(expr_location);
Self {
buffer,
saved_location,
}
}
}
impl<'a, B, D> Deref for WithFStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
type Target = B;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl<'a, B, D> DerefMut for WithFStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl<'a, B, D> Drop for WithFStringState<'a, B, D>
where
D: DerefMut<Target = B>,
B: Buffer<Context = PyFormatContext<'a>>,
{
fn drop(&mut self) {
self.buffer
.state_mut()
.context_mut()
.set_f_string_state(self.saved_location);
}
}

View File

@@ -13,11 +13,10 @@ use ruff_text_size::{Ranged, TextRange};
use crate::comments::{leading_comments, trailing_comments, Comments, SourceComment};
use crate::expression::parentheses::{
in_parentheses_only_group, in_parentheses_only_if_group_breaks, in_parentheses_only_indent_end,
in_parentheses_only_indent_start, in_parentheses_only_soft_line_break,
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized,
write_in_parentheses_only_group_end_tag, write_in_parentheses_only_group_start_tag,
Parentheses,
in_parentheses_only_group, in_parentheses_only_if_group_breaks,
in_parentheses_only_soft_line_break, in_parentheses_only_soft_line_break_or_space,
is_expression_parenthesized, write_in_parentheses_only_group_end_tag,
write_in_parentheses_only_group_start_tag, Parentheses,
};
use crate::expression::OperatorPrecedence;
use crate::prelude::*;
@@ -288,7 +287,7 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
let flat_binary = self.flatten(&comments, f.context().source());
if self.is_bool_op() {
return in_parentheses_only_group(&flat_binary).fmt(f);
return in_parentheses_only_group(&&*flat_binary).fmt(f);
}
let source = f.context().source();
@@ -482,7 +481,7 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
// Finish the group that wraps all implicit concatenated strings
write_in_parentheses_only_group_end_tag(f);
} else {
in_parentheses_only_group(&flat_binary).fmt(f)?;
in_parentheses_only_group(&&*flat_binary).fmt(f)?;
}
Ok(())
@@ -528,12 +527,6 @@ impl<'a> Deref for FlatBinaryExpression<'a> {
}
}
impl Format<PyFormatContext<'_>> for FlatBinaryExpression<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
Format::fmt(&**self, f)
}
}
/// Binary chain represented as a flat vector where operands are stored at even indices and operators
/// add odd indices.
///
@@ -649,7 +642,7 @@ impl<'a> FlatBinaryExpressionSlice<'a> {
}
/// Formats a binary chain slice by inserting soft line breaks before the lowest-precedence operators.
/// In other words: It splits the line before the lowest precedence operators (and it either splits
/// In other words: It splits the line before by the lowest precedence operators (and it either splits
/// all of them or none). For example, the lowest precedence operator for `a + b * c + d` is the `+` operator.
/// The expression either gets formatted as `a + b * c + d` if it fits on the line or as
/// ```python
@@ -685,64 +678,59 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
let mut last_operator: Option<OperatorIndex> = None;
let lowest_precedence = self.lowest_precedence();
let lowest_precedence_operators = self
.operators()
.filter(|(_, operator)| operator.precedence() == lowest_precedence);
for (index, operator_part) in lowest_precedence_operators {
let left = self.between_operators(last_operator, index);
let right = self.after_operator(index);
for (index, operator_part) in self.operators() {
if operator_part.precedence() == lowest_precedence {
let left = self.between_operators(last_operator, index);
let right = self.after_operator(index);
let is_pow = operator_part.symbol.is_pow()
&& is_simple_power_expression(
left.last_operand().expression(),
right.first_operand().expression(),
f.context().comments().ranges(),
f.context().source(),
);
let is_pow = operator_part.symbol.is_pow()
&& is_simple_power_expression(
left.last_operand().expression(),
right.first_operand().expression(),
f.context().comments().ranges(),
f.context().source(),
);
if let Some(leading) = left.first_operand().leading_binary_comments() {
leading_comments(leading).fmt(f)?;
}
match &left.0 {
[OperandOrOperator::Operand(operand)] => operand.fmt(f)?,
_ => in_parentheses_only_group(&left).fmt(f)?,
}
if last_operator.is_none() {
in_parentheses_only_indent_start().fmt(f)?;
}
if let Some(trailing) = left.last_operand().trailing_binary_comments() {
trailing_comments(trailing).fmt(f)?;
}
if is_pow {
in_parentheses_only_soft_line_break().fmt(f)?;
} else {
in_parentheses_only_soft_line_break_or_space().fmt(f)?;
}
operator_part.fmt(f)?;
// Format the operator on its own line if the right side has any leading comments.
if operator_part.has_trailing_comments()
|| right.first_operand().has_unparenthesized_leading_comments(
f.context().comments(),
f.context().source(),
)
{
hard_line_break().fmt(f)?;
} else if is_pow {
if is_fix_power_op_line_length_enabled(f.context()) {
in_parentheses_only_if_group_breaks(&space()).fmt(f)?;
if let Some(leading) = left.first_operand().leading_binary_comments() {
leading_comments(leading).fmt(f)?;
}
} else {
space().fmt(f)?;
}
last_operator = Some(index);
match &left.0 {
[OperandOrOperator::Operand(operand)] => operand.fmt(f)?,
_ => in_parentheses_only_group(&left).fmt(f)?,
}
if let Some(trailing) = left.last_operand().trailing_binary_comments() {
trailing_comments(trailing).fmt(f)?;
}
if is_pow {
in_parentheses_only_soft_line_break().fmt(f)?;
} else {
in_parentheses_only_soft_line_break_or_space().fmt(f)?;
}
operator_part.fmt(f)?;
// Format the operator on its own line if the right side has any leading comments.
if operator_part.has_trailing_comments()
|| right.first_operand().has_unparenthesized_leading_comments(
f.context().comments(),
f.context().source(),
)
{
hard_line_break().fmt(f)?;
} else if is_pow {
if is_fix_power_op_line_length_enabled(f.context()) {
in_parentheses_only_if_group_breaks(&space()).fmt(f)?;
}
} else {
space().fmt(f)?;
}
last_operator = Some(index);
}
}
// Format the last right side
@@ -757,11 +745,9 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
}
match &right.0 {
[OperandOrOperator::Operand(operand)] => operand.fmt(f)?,
_ => in_parentheses_only_group(&right).fmt(f)?,
[OperandOrOperator::Operand(operand)] => operand.fmt(f),
_ => in_parentheses_only_group(&right).fmt(f),
}
in_parentheses_only_indent_end().fmt(f)
}
}

View File

@@ -48,6 +48,24 @@ impl NeedsParentheses for ExprFString {
) -> OptionalParentheses {
if self.value.is_implicit_concatenated() {
OptionalParentheses::Multiline
// TODO(dhruvmanila): Ideally what we want here is a new variant which
// is something like:
// - If the expression fits by just adding the parentheses, then add them and
// avoid breaking the f-string expression. So,
// ```
// xxxxxxxxx = (
// f"aaaaaaaaaaaa { xxxxxxx + yyyyyyyy } bbbbbbbbbbbbb"
// )
// ```
// - But, if the expression is too long to fit even with parentheses, then
// don't add the parentheses and instead break the expression at `soft_line_break`.
// ```
// xxxxxxxxx = f"aaaaaaaaaaaa {
// xxxxxxxxx + yyyyyyyyyy
// } bbbbbbbbbbbbb"
// ```
// This isn't decided yet, refer to the relevant discussion:
// https://github.com/astral-sh/ruff/discussions/9785
} else if AnyString::FString(self).is_multiline(context.source()) {
OptionalParentheses::Never
} else {

View File

@@ -379,42 +379,6 @@ where
})
}
pub(super) fn in_parentheses_only_indent_start<'a>() -> impl Format<PyFormatContext<'a>> {
format_with(|f: &mut PyFormatter| {
match f.context().node_level() {
NodeLevel::TopLevel(_) | NodeLevel::CompoundStatement | NodeLevel::Expression(None) => {
// no-op, not parenthesized
}
NodeLevel::Expression(Some(parentheses_id)) => f.write_element(FormatElement::Tag(
Tag::StartIndentIfGroupBreaks(parentheses_id),
)),
NodeLevel::ParenthesizedExpression => {
f.write_element(FormatElement::Tag(Tag::StartIndent))
}
}
Ok(())
})
}
pub(super) fn in_parentheses_only_indent_end<'a>() -> impl Format<PyFormatContext<'a>> {
format_with(|f: &mut PyFormatter| {
match f.context().node_level() {
NodeLevel::TopLevel(_) | NodeLevel::CompoundStatement | NodeLevel::Expression(None) => {
// no-op, not parenthesized
}
NodeLevel::Expression(Some(_)) => {
f.write_element(FormatElement::Tag(Tag::EndIndentIfGroupBreaks))
}
NodeLevel::ParenthesizedExpression => {
f.write_element(FormatElement::Tag(Tag::EndIndent))
}
}
Ok(())
})
}
/// Format comments inside empty parentheses, brackets or curly braces.
///
/// Empty `()`, `[]` and `{}` are special because there can be dangling comments, and they can be in

View File

@@ -466,3 +466,12 @@ pub enum PythonVersion {
Py311,
Py312,
}
impl PythonVersion {
/// Return `true` if the current version supports [PEP 701].
///
/// [PEP 701]: https://peps.python.org/pep-0701/
pub fn supports_pep_701(self) -> bool {
self >= Self::Py312
}
}

View File

@@ -1,8 +1,13 @@
use ruff_formatter::write;
use ruff_python_ast::FString;
use ruff_source_file::Locator;
use ruff_text_size::Ranged;
use crate::prelude::*;
use crate::string::{Quoting, StringNormalizer, StringPart};
use crate::preview::is_f_string_formatting_enabled;
use crate::string::{Quoting, StringNormalizer, StringPart, StringPrefix, StringQuotes};
use super::f_string_element::FormatFStringElement;
/// Formats an f-string which is part of a larger f-string expression.
///
@@ -25,25 +30,126 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let locator = f.context().locator();
let result = StringNormalizer::from_context(f.context())
let string = StringPart::from_source(self.value.range(), &locator);
let normalizer = StringNormalizer::from_context(f.context())
.with_quoting(self.quoting)
.with_preferred_quote_style(f.options().quote_style())
.normalize(
&StringPart::from_source(self.value.range(), &locator),
&locator,
.with_preferred_quote_style(f.options().quote_style());
// If f-string formatting is disabled (not in preview), then we will
// fall back to the previous behavior of normalizing the f-string.
if !is_f_string_formatting_enabled(f.context()) {
let result = normalizer.normalize(&string, &locator).fmt(f);
let comments = f.context().comments();
self.value.elements.iter().for_each(|value| {
comments.mark_verbatim_node_comments_formatted(value.into());
// Above method doesn't mark the trailing comments of the f-string elements
// as formatted, so we need to do it manually. For example,
//
// ```python
// f"""foo {
// x:.3f
// # comment
// }"""
// ```
for trailing_comment in comments.trailing(value) {
trailing_comment.mark_formatted();
}
});
return result;
}
let quotes = normalizer.choose_quotes(&string, &locator);
let context = FStringContext::new(
string.prefix(),
quotes,
FStringLayout::from_f_string(self.value, &locator),
);
// Starting prefix and quote
write!(f, [string.prefix(), quotes])?;
f.join()
.entries(
self.value
.elements
.iter()
.map(|element| FormatFStringElement::new(element, context)),
)
.fmt(f);
.finish()?;
// TODO(dhruvmanila): With PEP 701, comments can be inside f-strings.
// This is to mark all of those comments as formatted but we need to
// figure out how to handle them. Note that this needs to be done only
// after the f-string is formatted, so only for all the non-formatted
// comments.
let comments = f.context().comments();
self.value.elements.iter().for_each(|value| {
comments.mark_verbatim_node_comments_formatted(value.into());
});
result
// Ending quote
quotes.fmt(f)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct FStringContext {
prefix: StringPrefix,
quotes: StringQuotes,
layout: FStringLayout,
}
impl FStringContext {
const fn new(prefix: StringPrefix, quotes: StringQuotes, layout: FStringLayout) -> Self {
Self {
prefix,
quotes,
layout,
}
}
pub(crate) const fn quotes(self) -> StringQuotes {
self.quotes
}
pub(crate) const fn prefix(self) -> StringPrefix {
self.prefix
}
pub(crate) const fn layout(self) -> FStringLayout {
self.layout
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum FStringLayout {
/// Original f-string is flat.
/// Don't break expressions to keep the string flat.
Flat,
/// Original f-string has multiline expressions in the replacement fields.
/// Allow breaking expressions across multiple lines.
Multiline,
}
impl FStringLayout {
fn from_f_string(f_string: &FString, locator: &Locator) -> Self {
// Heuristic: Allow breaking the f-string expressions across multiple lines
// only if there already is at least one multiline expression. This puts the
// control in the hands of the user to decide if they want to break the
// f-string expressions across multiple lines or not. This is similar to
// how Prettier does it for template literals in JavaScript.
//
// If it's single quoted f-string and it contains a multiline expression, then we
// assume that the target version of Python supports it (3.12+). If there are comments
// used in any of the expression of the f-string, then it's always going to be multiline
// and we assume that the target version of Python supports it (3.12+).
//
// Reference: https://prettier.io/docs/en/next/rationale.html#template-literals
if f_string
.elements
.iter()
.filter_map(|element| element.as_expression())
.any(|expr| memchr::memchr2(b'\n', b'\r', locator.slice(expr).as_bytes()).is_some())
{
Self::Multiline
} else {
Self::Flat
}
}
pub(crate) const fn is_flat(self) -> bool {
matches!(self, Self::Flat)
}
}

View File

@@ -0,0 +1,244 @@
use std::borrow::Cow;
use ruff_formatter::{format_args, write, Buffer, RemoveSoftLinesBuffer};
use ruff_python_ast::{
ConversionFlag, Expr, FStringElement, FStringExpressionElement, FStringLiteralElement,
};
use ruff_text_size::Ranged;
use crate::comments::{dangling_open_parenthesis_comments, trailing_comments};
use crate::context::{FStringState, NodeLevel, WithFStringState, WithNodeLevel};
use crate::prelude::*;
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
use crate::string::normalize_string;
use crate::verbatim::verbatim_text;
use super::f_string::FStringContext;
/// Formats an f-string element which is either a literal or a formatted expression.
///
/// This delegates the actual formatting to the appropriate formatter.
pub(crate) struct FormatFStringElement<'a> {
element: &'a FStringElement,
context: FStringContext,
}
impl<'a> FormatFStringElement<'a> {
pub(crate) fn new(element: &'a FStringElement, context: FStringContext) -> Self {
Self { element, context }
}
}
impl Format<PyFormatContext<'_>> for FormatFStringElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self.element {
FStringElement::Literal(string_literal) => {
FormatFStringLiteralElement::new(string_literal, self.context).fmt(f)
}
FStringElement::Expression(expression) => {
FormatFStringExpressionElement::new(expression, self.context).fmt(f)
}
}
}
}
/// Formats an f-string literal element.
pub(crate) struct FormatFStringLiteralElement<'a> {
element: &'a FStringLiteralElement,
context: FStringContext,
}
impl<'a> FormatFStringLiteralElement<'a> {
pub(crate) fn new(element: &'a FStringLiteralElement, context: FStringContext) -> Self {
Self { element, context }
}
}
impl Format<PyFormatContext<'_>> for FormatFStringLiteralElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let literal_content = f.context().locator().slice(self.element.range());
let normalized = normalize_string(
literal_content,
self.context.quotes(),
self.context.prefix(),
is_hex_codes_in_unicode_sequences_enabled(f.context()),
);
match &normalized {
Cow::Borrowed(_) => source_text_slice(self.element.range()).fmt(f),
Cow::Owned(normalized) => text(normalized).fmt(f),
}
}
}
/// Formats an f-string expression element.
pub(crate) struct FormatFStringExpressionElement<'a> {
element: &'a FStringExpressionElement,
context: FStringContext,
}
impl<'a> FormatFStringExpressionElement<'a> {
pub(crate) fn new(element: &'a FStringExpressionElement, context: FStringContext) -> Self {
Self { element, context }
}
}
impl Format<PyFormatContext<'_>> for FormatFStringExpressionElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let FStringExpressionElement {
expression,
debug_text,
conversion,
format_spec,
..
} = self.element;
if let Some(debug_text) = debug_text {
token("{").fmt(f)?;
let comments = f.context().comments();
// If the element has a debug text, preserve the same formatting as
// in the source code (`verbatim`). This requires us to mark all of
// the surrounding comments as formatted.
comments.mark_verbatim_node_comments_formatted(self.element.into());
// Above method doesn't mark the leading and trailing comments of the element.
// There can't be any leading comments for an expression element, but there
// can be trailing comments. For example,
//
// ```python
// f"""foo {
// x:.3f
// # trailing comment
// }"""
// ```
for trailing_comment in comments.trailing(self.element) {
trailing_comment.mark_formatted();
}
write!(
f,
[
text(&debug_text.leading),
verbatim_text(&**expression),
text(&debug_text.trailing),
]
)?;
// Even if debug text is present, any whitespace between the
// conversion flag and the format spec doesn't need to be preserved.
match conversion {
ConversionFlag::Str => text("!s").fmt(f)?,
ConversionFlag::Ascii => text("!a").fmt(f)?,
ConversionFlag::Repr => text("!r").fmt(f)?,
ConversionFlag::None => (),
}
if let Some(format_spec) = format_spec.as_deref() {
write!(f, [token(":"), verbatim_text(format_spec)])?;
}
token("}").fmt(f)
} else {
let comments = f.context().comments().clone();
let dangling_item_comments = comments.dangling(self.element);
let item = format_with(|f| {
let bracket_spacing = match expression.as_ref() {
// If an expression starts with a `{`, we need to add a space before the
// curly brace to avoid turning it into a literal curly with `{{`.
//
// For example,
// ```python
// f"{ {'x': 1, 'y': 2} }"
// # ^ ^
// ```
//
// We need to preserve the space highlighted by `^`. The whitespace
// before the closing curly brace is not strictly necessary, but it's
// added to maintain consistency.
Expr::Dict(_) | Expr::DictComp(_) | Expr::Set(_) | Expr::SetComp(_) => {
Some(format_with(|f| {
if self.context.layout().is_flat() {
space().fmt(f)
} else {
soft_line_break_or_space().fmt(f)
}
}))
}
_ => None,
};
// Update the context to be inside the f-string expression element.
let f = &mut WithFStringState::new(
FStringState::InsideExpressionElement(self.context),
f,
);
write!(f, [bracket_spacing, expression.format()])?;
// Conversion comes first, then the format spec.
match conversion {
ConversionFlag::Str => text("!s").fmt(f)?,
ConversionFlag::Ascii => text("!a").fmt(f)?,
ConversionFlag::Repr => text("!r").fmt(f)?,
ConversionFlag::None => (),
}
if let Some(format_spec) = format_spec.as_deref() {
token(":").fmt(f)?;
f.join()
.entries(
format_spec
.elements
.iter()
.map(|element| FormatFStringElement::new(element, self.context)),
)
.finish()?;
// These trailing comments can only occur if the format specifier is
// present. For example,
//
// ```python
// f"{
// x:.3f
// # comment
// }"
// ```
//
// Any other trailing comments are attached to the expression itself.
trailing_comments(comments.trailing(self.element)).fmt(f)?;
}
bracket_spacing.fmt(f)
});
let open_parenthesis_comments = if dangling_item_comments.is_empty() {
None
} else {
Some(dangling_open_parenthesis_comments(dangling_item_comments))
};
token("{").fmt(f)?;
{
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
if self.context.layout().is_flat() {
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [open_parenthesis_comments, item])?;
} else {
group(&format_args![
open_parenthesis_comments,
soft_block_indent(&item)
])
.fmt(&mut f)?;
}
}
token("}").fmt(f)
}
}
}

View File

@@ -7,6 +7,7 @@ pub(crate) mod decorator;
pub(crate) mod elif_else_clause;
pub(crate) mod except_handler_except_handler;
pub(crate) mod f_string;
pub(crate) mod f_string_element;
pub(crate) mod f_string_part;
pub(crate) mod identifier;
pub(crate) mod keyword;

View File

@@ -81,3 +81,8 @@ pub(crate) const fn is_multiline_string_handling_enabled(context: &PyFormatConte
pub(crate) const fn is_format_module_docstring_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
/// Returns `true` if the [`f-string formatting`](https://github.com/astral-sh/ruff/issues/7594) preview style is enabled.
pub(crate) fn is_f_string_formatting_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}

View File

@@ -0,0 +1,212 @@
use std::iter::FusedIterator;
use memchr::memchr2;
use ruff_python_ast::{
self as ast, AnyNodeRef, Expr, ExprBytesLiteral, ExprFString, ExprStringLiteral, ExpressionRef,
StringLiteral,
};
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextLen, TextRange};
use crate::expression::expr_f_string::f_string_quoting;
use crate::other::f_string::FormatFString;
use crate::other::string_literal::{FormatStringLiteral, StringLiteralKind};
use crate::prelude::*;
use crate::string::{Quoting, StringPrefix, StringQuotes};
/// Represents any kind of string expression. This could be either a string,
/// bytes or f-string.
#[derive(Copy, Clone, Debug)]
pub(crate) enum AnyString<'a> {
String(&'a ExprStringLiteral),
Bytes(&'a ExprBytesLiteral),
FString(&'a ExprFString),
}
impl<'a> AnyString<'a> {
/// Creates a new [`AnyString`] from the given [`Expr`].
///
/// Returns `None` if the expression is not either a string, bytes or f-string.
pub(crate) fn from_expression(expression: &'a Expr) -> Option<AnyString<'a>> {
match expression {
Expr::StringLiteral(string) => Some(AnyString::String(string)),
Expr::BytesLiteral(bytes) => Some(AnyString::Bytes(bytes)),
Expr::FString(fstring) => Some(AnyString::FString(fstring)),
_ => None,
}
}
/// Returns `true` if the string is implicitly concatenated.
pub(crate) fn is_implicit_concatenated(self) -> bool {
match self {
Self::String(ExprStringLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::Bytes(ExprBytesLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::FString(ExprFString { value, .. }) => value.is_implicit_concatenated(),
}
}
/// Returns the quoting to be used for this string.
pub(super) fn quoting(self, locator: &Locator<'_>) -> Quoting {
match self {
Self::String(_) | Self::Bytes(_) => Quoting::CanChange,
Self::FString(f_string) => f_string_quoting(f_string, locator),
}
}
/// Returns a vector of all the [`AnyStringPart`] of this string.
pub(super) fn parts(self, quoting: Quoting) -> AnyStringPartsIter<'a> {
match self {
Self::String(ExprStringLiteral { value, .. }) => {
AnyStringPartsIter::String(value.iter())
}
Self::Bytes(ExprBytesLiteral { value, .. }) => AnyStringPartsIter::Bytes(value.iter()),
Self::FString(ExprFString { value, .. }) => {
AnyStringPartsIter::FString(value.iter(), quoting)
}
}
}
pub(crate) fn is_multiline(self, source: &str) -> bool {
match self {
AnyString::String(_) | AnyString::Bytes(_) => {
let contents = &source[self.range()];
let prefix = StringPrefix::parse(contents);
let quotes = StringQuotes::parse(
&contents[TextRange::new(prefix.text_len(), contents.text_len())],
);
quotes.is_some_and(StringQuotes::is_triple)
&& memchr2(b'\n', b'\r', contents.as_bytes()).is_some()
}
AnyString::FString(fstring) => {
memchr2(b'\n', b'\r', source[fstring.range].as_bytes()).is_some()
}
}
}
}
impl Ranged for AnyString<'_> {
fn range(&self) -> TextRange {
match self {
Self::String(expr) => expr.range(),
Self::Bytes(expr) => expr.range(),
Self::FString(expr) => expr.range(),
}
}
}
impl<'a> From<&AnyString<'a>> for AnyNodeRef<'a> {
fn from(value: &AnyString<'a>) -> Self {
match value {
AnyString::String(expr) => AnyNodeRef::ExprStringLiteral(expr),
AnyString::Bytes(expr) => AnyNodeRef::ExprBytesLiteral(expr),
AnyString::FString(expr) => AnyNodeRef::ExprFString(expr),
}
}
}
impl<'a> From<AnyString<'a>> for AnyNodeRef<'a> {
fn from(value: AnyString<'a>) -> Self {
AnyNodeRef::from(&value)
}
}
impl<'a> From<&AnyString<'a>> for ExpressionRef<'a> {
fn from(value: &AnyString<'a>) -> Self {
match value {
AnyString::String(expr) => ExpressionRef::StringLiteral(expr),
AnyString::Bytes(expr) => ExpressionRef::BytesLiteral(expr),
AnyString::FString(expr) => ExpressionRef::FString(expr),
}
}
}
pub(super) enum AnyStringPartsIter<'a> {
String(std::slice::Iter<'a, StringLiteral>),
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
FString(std::slice::Iter<'a, ast::FStringPart>, Quoting),
}
impl<'a> Iterator for AnyStringPartsIter<'a> {
type Item = AnyStringPart<'a>;
fn next(&mut self) -> Option<Self::Item> {
let part = match self {
Self::String(inner) => {
let part = inner.next()?;
AnyStringPart::String {
part,
layout: StringLiteralKind::String,
}
}
Self::Bytes(inner) => AnyStringPart::Bytes(inner.next()?),
Self::FString(inner, quoting) => {
let part = inner.next()?;
match part {
ast::FStringPart::Literal(string_literal) => AnyStringPart::String {
part: string_literal,
layout: StringLiteralKind::InImplicitlyConcatenatedFString(*quoting),
},
ast::FStringPart::FString(f_string) => AnyStringPart::FString {
part: f_string,
quoting: *quoting,
},
}
}
};
Some(part)
}
}
impl FusedIterator for AnyStringPartsIter<'_> {}
/// Represents any kind of string which is part of an implicitly concatenated
/// string. This could be either a string, bytes or f-string.
///
/// This is constructed from the [`AnyString::parts`] method on [`AnyString`].
#[derive(Clone, Debug)]
pub(super) enum AnyStringPart<'a> {
String {
part: &'a ast::StringLiteral,
layout: StringLiteralKind,
},
Bytes(&'a ast::BytesLiteral),
FString {
part: &'a ast::FString,
quoting: Quoting,
},
}
impl<'a> From<&AnyStringPart<'a>> for AnyNodeRef<'a> {
fn from(value: &AnyStringPart<'a>) -> Self {
match value {
AnyStringPart::String { part, .. } => AnyNodeRef::StringLiteral(part),
AnyStringPart::Bytes(part) => AnyNodeRef::BytesLiteral(part),
AnyStringPart::FString { part, .. } => AnyNodeRef::FString(part),
}
}
}
impl Ranged for AnyStringPart<'_> {
fn range(&self) -> TextRange {
match self {
Self::String { part, .. } => part.range(),
Self::Bytes(part) => part.range(),
Self::FString { part, .. } => part.range(),
}
}
}
impl Format<PyFormatContext<'_>> for AnyStringPart<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self {
AnyStringPart::String { part, layout } => {
FormatStringLiteral::new(part, *layout).fmt(f)
}
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
AnyStringPart::FString { part, quoting } => FormatFString::new(part, *quoting).fmt(f),
}
}
}

View File

@@ -109,7 +109,7 @@ use super::{NormalizedString, QuoteChar};
/// `indent-width * spaces` to tabs because doing so could break ASCII art and other docstrings
/// that use spaces for alignment.
pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> FormatResult<()> {
let docstring = &normalized.text;
let docstring = &normalized.text();
// Black doesn't change the indentation of docstrings that contain an escaped newline
if contains_unescaped_newline(docstring) {
@@ -125,7 +125,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
let mut lines = docstring.split('\n').peekable();
// Start the string
write!(f, [normalized.prefix, normalized.quotes])?;
write!(f, [normalized.prefix(), normalized.quotes()])?;
// We track where in the source docstring we are (in source code byte offsets)
let mut offset = normalized.start();
@@ -141,7 +141,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
// Edge case: The first line is `""" "content`, so we need to insert chaperone space that keep
// inner quotes and closing quotes from getting to close to avoid `""""content`
if trim_both.starts_with(normalized.quotes.quote_char.as_char()) {
if trim_both.starts_with(normalized.quotes().quote_char.as_char()) {
space().fmt(f)?;
}
@@ -168,7 +168,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
{
space().fmt(f)?;
}
normalized.quotes.fmt(f)?;
normalized.quotes().fmt(f)?;
return Ok(());
}
@@ -194,7 +194,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
offset,
stripped_indentation,
already_normalized,
quote_char: normalized.quotes.quote_char,
quote_char: normalized.quotes().quote_char,
code_example: CodeExample::default(),
}
.add_iter(lines)?;
@@ -207,7 +207,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
space().fmt(f)?;
}
write!(f, [normalized.quotes])
write!(f, [normalized.quotes()])
}
fn contains_unescaped_newline(haystack: &str) -> bool {
@@ -1569,7 +1569,7 @@ fn docstring_format_source(
/// that avoids `content""""` and `content\"""`. This does only applies to un-escaped backslashes,
/// so `content\\ """` doesn't need a space while `content\\\ """` does.
fn needs_chaperone_space(normalized: &NormalizedString, trim_end: &str) -> bool {
trim_end.ends_with(normalized.quotes.quote_char.as_char())
trim_end.ends_with(normalized.quotes().quote_char.as_char())
|| trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
}

View File

@@ -1,27 +1,19 @@
use std::borrow::Cow;
use std::iter::FusedIterator;
use bitflags::bitflags;
use memchr::memchr2;
use ruff_formatter::{format_args, write};
use ruff_python_ast::{
self as ast, Expr, ExprBytesLiteral, ExprFString, ExprStringLiteral, ExpressionRef,
};
use ruff_python_ast::{AnyNodeRef, StringLiteral};
pub(crate) use any::AnyString;
pub(crate) use normalize::{normalize_string, NormalizedString, StringNormalizer};
use ruff_formatter::format_args;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::comments::{leading_comments, trailing_comments};
use crate::expression::expr_f_string::f_string_quoting;
use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space;
use crate::other::f_string::FormatFString;
use crate::other::string_literal::{FormatStringLiteral, StringLiteralKind};
use crate::prelude::*;
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
use crate::QuoteStyle;
mod any;
pub(crate) mod docstring;
mod normalize;
#[derive(Copy, Clone, Debug, Default)]
pub(crate) enum Quoting {
@@ -30,202 +22,6 @@ pub(crate) enum Quoting {
Preserve,
}
/// Represents any kind of string expression. This could be either a string,
/// bytes or f-string.
#[derive(Copy, Clone, Debug)]
pub(crate) enum AnyString<'a> {
String(&'a ExprStringLiteral),
Bytes(&'a ExprBytesLiteral),
FString(&'a ExprFString),
}
impl<'a> AnyString<'a> {
/// Creates a new [`AnyString`] from the given [`Expr`].
///
/// Returns `None` if the expression is not either a string, bytes or f-string.
pub(crate) fn from_expression(expression: &'a Expr) -> Option<AnyString<'a>> {
match expression {
Expr::StringLiteral(string) => Some(AnyString::String(string)),
Expr::BytesLiteral(bytes) => Some(AnyString::Bytes(bytes)),
Expr::FString(fstring) => Some(AnyString::FString(fstring)),
_ => None,
}
}
/// Returns `true` if the string is implicitly concatenated.
pub(crate) fn is_implicit_concatenated(self) -> bool {
match self {
Self::String(ExprStringLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::Bytes(ExprBytesLiteral { value, .. }) => value.is_implicit_concatenated(),
Self::FString(ExprFString { value, .. }) => value.is_implicit_concatenated(),
}
}
/// Returns the quoting to be used for this string.
fn quoting(self, locator: &Locator<'_>) -> Quoting {
match self {
Self::String(_) | Self::Bytes(_) => Quoting::CanChange,
Self::FString(f_string) => f_string_quoting(f_string, locator),
}
}
/// Returns a vector of all the [`AnyStringPart`] of this string.
fn parts(self, quoting: Quoting) -> AnyStringPartsIter<'a> {
match self {
Self::String(ExprStringLiteral { value, .. }) => {
AnyStringPartsIter::String(value.iter())
}
Self::Bytes(ExprBytesLiteral { value, .. }) => AnyStringPartsIter::Bytes(value.iter()),
Self::FString(ExprFString { value, .. }) => {
AnyStringPartsIter::FString(value.iter(), quoting)
}
}
}
pub(crate) fn is_multiline(self, source: &str) -> bool {
match self {
AnyString::String(_) | AnyString::Bytes(_) => {
let contents = &source[self.range()];
let prefix = StringPrefix::parse(contents);
let quotes = StringQuotes::parse(
&contents[TextRange::new(prefix.text_len(), contents.text_len())],
);
quotes.is_some_and(StringQuotes::is_triple)
&& memchr2(b'\n', b'\r', contents.as_bytes()).is_some()
}
AnyString::FString(fstring) => {
memchr2(b'\n', b'\r', source[fstring.range].as_bytes()).is_some()
}
}
}
}
impl Ranged for AnyString<'_> {
fn range(&self) -> TextRange {
match self {
Self::String(expr) => expr.range(),
Self::Bytes(expr) => expr.range(),
Self::FString(expr) => expr.range(),
}
}
}
impl<'a> From<&AnyString<'a>> for AnyNodeRef<'a> {
fn from(value: &AnyString<'a>) -> Self {
match value {
AnyString::String(expr) => AnyNodeRef::ExprStringLiteral(expr),
AnyString::Bytes(expr) => AnyNodeRef::ExprBytesLiteral(expr),
AnyString::FString(expr) => AnyNodeRef::ExprFString(expr),
}
}
}
impl<'a> From<AnyString<'a>> for AnyNodeRef<'a> {
fn from(value: AnyString<'a>) -> Self {
AnyNodeRef::from(&value)
}
}
impl<'a> From<&AnyString<'a>> for ExpressionRef<'a> {
fn from(value: &AnyString<'a>) -> Self {
match value {
AnyString::String(expr) => ExpressionRef::StringLiteral(expr),
AnyString::Bytes(expr) => ExpressionRef::BytesLiteral(expr),
AnyString::FString(expr) => ExpressionRef::FString(expr),
}
}
}
enum AnyStringPartsIter<'a> {
String(std::slice::Iter<'a, StringLiteral>),
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
FString(std::slice::Iter<'a, ast::FStringPart>, Quoting),
}
impl<'a> Iterator for AnyStringPartsIter<'a> {
type Item = AnyStringPart<'a>;
fn next(&mut self) -> Option<Self::Item> {
let part = match self {
Self::String(inner) => {
let part = inner.next()?;
AnyStringPart::String {
part,
layout: StringLiteralKind::String,
}
}
Self::Bytes(inner) => AnyStringPart::Bytes(inner.next()?),
Self::FString(inner, quoting) => {
let part = inner.next()?;
match part {
ast::FStringPart::Literal(string_literal) => AnyStringPart::String {
part: string_literal,
layout: StringLiteralKind::InImplicitlyConcatenatedFString(*quoting),
},
ast::FStringPart::FString(f_string) => AnyStringPart::FString {
part: f_string,
quoting: *quoting,
},
}
}
};
Some(part)
}
}
impl FusedIterator for AnyStringPartsIter<'_> {}
/// Represents any kind of string which is part of an implicitly concatenated
/// string. This could be either a string, bytes or f-string.
///
/// This is constructed from the [`AnyString::parts`] method on [`AnyString`].
#[derive(Clone, Debug)]
enum AnyStringPart<'a> {
String {
part: &'a ast::StringLiteral,
layout: StringLiteralKind,
},
Bytes(&'a ast::BytesLiteral),
FString {
part: &'a ast::FString,
quoting: Quoting,
},
}
impl<'a> From<&AnyStringPart<'a>> for AnyNodeRef<'a> {
fn from(value: &AnyStringPart<'a>) -> Self {
match value {
AnyStringPart::String { part, .. } => AnyNodeRef::StringLiteral(part),
AnyStringPart::Bytes(part) => AnyNodeRef::BytesLiteral(part),
AnyStringPart::FString { part, .. } => AnyNodeRef::FString(part),
}
}
}
impl Ranged for AnyStringPart<'_> {
fn range(&self) -> TextRange {
match self {
Self::String { part, .. } => part.range(),
Self::Bytes(part) => part.range(),
Self::FString { part, .. } => part.range(),
}
}
}
impl Format<PyFormatContext<'_>> for AnyStringPart<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self {
AnyStringPart::String { part, layout } => {
FormatStringLiteral::new(part, *layout).fmt(f)
}
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
AnyStringPart::FString { part, quoting } => FormatFString::new(part, *quoting).fmt(f),
}
}
}
/// Formats any implicitly concatenated string. This could be any valid combination
/// of string, bytes or f-string literals.
pub(crate) struct FormatStringContinuation<'a> {
@@ -308,167 +104,6 @@ impl StringPart {
}
}
pub(crate) struct StringNormalizer {
quoting: Quoting,
preferred_quote_style: QuoteStyle,
parent_docstring_quote_char: Option<QuoteChar>,
normalize_hex: bool,
}
impl StringNormalizer {
pub(crate) fn from_context(context: &PyFormatContext<'_>) -> Self {
Self {
quoting: Quoting::default(),
preferred_quote_style: QuoteStyle::default(),
parent_docstring_quote_char: context.docstring(),
normalize_hex: is_hex_codes_in_unicode_sequences_enabled(context),
}
}
pub(crate) fn with_preferred_quote_style(mut self, quote_style: QuoteStyle) -> Self {
self.preferred_quote_style = quote_style;
self
}
pub(crate) fn with_quoting(mut self, quoting: Quoting) -> Self {
self.quoting = quoting;
self
}
/// Computes the strings preferred quotes.
pub(crate) fn choose_quotes(&self, string: &StringPart, locator: &Locator) -> StringQuotes {
// Per PEP 8, always prefer double quotes for triple-quoted strings.
// Except when using quote-style-preserve.
let preferred_style = if string.quotes().triple {
// ... unless we're formatting a code snippet inside a docstring,
// then we specifically want to invert our quote style to avoid
// writing out invalid Python.
//
// It's worth pointing out that we can actually wind up being
// somewhat out of sync with PEP8 in this case. Consider this
// example:
//
// def foo():
// '''
// Something.
//
// >>> """tricksy"""
// '''
// pass
//
// Ideally, this would be reformatted as:
//
// def foo():
// """
// Something.
//
// >>> '''tricksy'''
// """
// pass
//
// But the logic here results in the original quoting being
// preserved. This is because the quoting style of the outer
// docstring is determined, in part, by looking at its contents. In
// this case, it notices that it contains a `"""` and thus infers
// that using `'''` would overall read better because it avoids
// the need to escape the interior `"""`. Except... in this case,
// the `"""` is actually part of a code snippet that could get
// reformatted to using a different quoting style itself.
//
// Fixing this would, I believe, require some fairly seismic
// changes to how formatting strings works. Namely, we would need
// to look for code snippets before normalizing the docstring, and
// then figure out the quoting style more holistically by looking
// at the various kinds of quotes used in the code snippets and
// what reformatting them might look like.
//
// Overall this is a bit of a corner case and just inverting the
// style from what the parent ultimately decided upon works, even
// if it doesn't have perfect alignment with PEP8.
if let Some(quote) = self.parent_docstring_quote_char {
QuoteStyle::from(quote.invert())
} else if self.preferred_quote_style.is_preserve() {
QuoteStyle::Preserve
} else {
QuoteStyle::Double
}
} else {
self.preferred_quote_style
};
match self.quoting {
Quoting::Preserve => string.quotes(),
Quoting::CanChange => {
if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) {
let raw_content = locator.slice(string.content_range());
if string.prefix().is_raw_string() {
choose_quotes_for_raw_string(raw_content, string.quotes(), preferred_quote)
} else {
choose_quotes_impl(raw_content, string.quotes(), preferred_quote)
}
} else {
string.quotes()
}
}
}
}
/// Computes the strings preferred quotes and normalizes its content.
pub(crate) fn normalize<'a>(
&self,
string: &StringPart,
locator: &'a Locator,
) -> NormalizedString<'a> {
let raw_content = locator.slice(string.content_range());
let quotes = self.choose_quotes(string, locator);
let normalized = normalize_string(raw_content, quotes, string.prefix(), self.normalize_hex);
NormalizedString {
prefix: string.prefix(),
content_range: string.content_range(),
text: normalized,
quotes,
}
}
}
#[derive(Debug)]
pub(crate) struct NormalizedString<'a> {
prefix: StringPrefix,
/// The quotes of the normalized string (preferred quotes)
quotes: StringQuotes,
/// The range of the string's content in the source (minus prefix and quotes).
content_range: TextRange,
/// The normalized text
text: Cow<'a, str>,
}
impl Ranged for NormalizedString<'_> {
fn range(&self) -> TextRange {
self.content_range
}
}
impl Format<PyFormatContext<'_>> for NormalizedString<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
write!(f, [self.prefix, self.quotes])?;
match &self.text {
Cow::Borrowed(_) => {
source_text_slice(self.range()).fmt(f)?;
}
Cow::Owned(normalized) => {
text(normalized).fmt(f)?;
}
}
self.quotes.fmt(f)
}
}
bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct StringPrefix: u8 {
@@ -549,175 +184,6 @@ impl Format<PyFormatContext<'_>> for StringPrefix {
}
}
/// Choose the appropriate quote style for a raw string.
///
/// The preferred quote style is chosen unless the string contains unescaped quotes of the
/// preferred style. For example, `r"foo"` is chosen over `r'foo'` if the preferred quote
/// style is double quotes.
fn choose_quotes_for_raw_string(
input: &str,
quotes: StringQuotes,
preferred_quote: QuoteChar,
) -> StringQuotes {
let preferred_quote_char = preferred_quote.as_char();
let mut chars = input.chars().peekable();
let contains_unescaped_configured_quotes = loop {
match chars.next() {
Some('\\') => {
// Ignore escaped characters
chars.next();
}
// `"` or `'`
Some(c) if c == preferred_quote_char => {
if !quotes.triple {
break true;
}
match chars.peek() {
// We can't turn `r'''\""'''` into `r"""\"""""`, this would confuse the parser
// about where the closing triple quotes start
None => break true,
Some(next) if *next == preferred_quote_char => {
// `""` or `''`
chars.next();
// We can't turn `r'''""'''` into `r""""""""`, nor can we have
// `"""` or `'''` respectively inside the string
if chars.peek().is_none() || chars.peek() == Some(&preferred_quote_char) {
break true;
}
}
_ => {}
}
}
Some(_) => continue,
None => break false,
}
};
StringQuotes {
triple: quotes.triple,
quote_char: if contains_unescaped_configured_quotes {
quotes.quote_char
} else {
preferred_quote
},
}
}
/// Choose the appropriate quote style for a string.
///
/// For single quoted strings, the preferred quote style is used, unless the alternative quote style
/// would require fewer escapes.
///
/// For triple quoted strings, the preferred quote style is always used, unless the string contains
/// a triplet of the quote character (e.g., if double quotes are preferred, double quotes will be
/// used unless the string contains `"""`).
fn choose_quotes_impl(
input: &str,
quotes: StringQuotes,
preferred_quote: QuoteChar,
) -> StringQuotes {
let quote = if quotes.triple {
// True if the string contains a triple quote sequence of the configured quote style.
let mut uses_triple_quotes = false;
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
let preferred_quote_char = preferred_quote.as_char();
match c {
'\\' => {
if matches!(chars.peek(), Some('"' | '\\')) {
chars.next();
}
}
// `"` or `'`
c if c == preferred_quote_char => {
match chars.peek().copied() {
Some(c) if c == preferred_quote_char => {
// `""` or `''`
chars.next();
match chars.peek().copied() {
Some(c) if c == preferred_quote_char => {
// `"""` or `'''`
chars.next();
uses_triple_quotes = true;
break;
}
Some(_) => {}
None => {
// Handle `''' ""'''`. At this point we have consumed both
// double quotes, so on the next iteration the iterator is empty
// and we'd miss the string ending with a preferred quote
uses_triple_quotes = true;
break;
}
}
}
Some(_) => {
// A single quote char, this is ok
}
None => {
// Trailing quote at the end of the comment
uses_triple_quotes = true;
break;
}
}
}
_ => continue,
}
}
if uses_triple_quotes {
// String contains a triple quote sequence of the configured quote style.
// Keep the existing quote style.
quotes.quote_char
} else {
preferred_quote
}
} else {
let mut single_quotes = 0u32;
let mut double_quotes = 0u32;
for c in input.chars() {
match c {
'\'' => {
single_quotes += 1;
}
'"' => {
double_quotes += 1;
}
_ => continue,
}
}
match preferred_quote {
QuoteChar::Single => {
if single_quotes > double_quotes {
QuoteChar::Double
} else {
QuoteChar::Single
}
}
QuoteChar::Double => {
if double_quotes > single_quotes {
QuoteChar::Single
} else {
QuoteChar::Double
}
}
}
};
StringQuotes {
triple: quotes.triple,
quote_char: quote,
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct StringQuotes {
triple: bool,
@@ -821,269 +287,3 @@ impl TryFrom<char> for QuoteChar {
}
}
}
/// Adds the necessary quote escapes and removes unnecessary escape sequences when quoting `input`
/// with the provided [`StringQuotes`] style.
///
/// Returns the normalized string and whether it contains new lines.
pub(crate) fn normalize_string(
input: &str,
quotes: StringQuotes,
prefix: StringPrefix,
normalize_hex: bool,
) -> Cow<str> {
// The normalized string if `input` is not yet normalized.
// `output` must remain empty if `input` is already normalized.
let mut output = String::new();
// Tracks the last index of `input` that has been written to `output`.
// If `last_index` is `0` at the end, then the input is already normalized and can be returned as is.
let mut last_index = 0;
let quote = quotes.quote_char;
let preferred_quote = quote.as_char();
let opposite_quote = quote.invert().as_char();
let mut chars = input.char_indices().peekable();
let is_raw = prefix.is_raw_string();
let is_fstring = prefix.is_fstring();
let mut formatted_value_nesting = 0u32;
while let Some((index, c)) = chars.next() {
if is_fstring && matches!(c, '{' | '}') {
if chars.peek().copied().is_some_and(|(_, next)| next == c) {
// Skip over the second character of the double braces
chars.next();
} else if c == '{' {
formatted_value_nesting += 1;
} else {
// Safe to assume that `c == '}'` here because of the matched pattern above
formatted_value_nesting = formatted_value_nesting.saturating_sub(1);
}
continue;
}
if c == '\r' {
output.push_str(&input[last_index..index]);
// Skip over the '\r' character, keep the `\n`
if chars.peek().copied().is_some_and(|(_, next)| next == '\n') {
chars.next();
}
// Replace the `\r` with a `\n`
else {
output.push('\n');
}
last_index = index + '\r'.len_utf8();
} else if !is_raw {
if c == '\\' {
if let Some((_, next)) = chars.clone().next() {
if next == '\\' {
// Skip over escaped backslashes
chars.next();
} else if normalize_hex {
if let Some(normalised) = UnicodeEscape::new(next, !prefix.is_byte())
.and_then(|escape| {
escape.normalize(&input[index + c.len_utf8() + next.len_utf8()..])
})
{
// Length of the `\` plus the length of the escape sequence character (`u` | `U` | `x`)
let escape_start_len = '\\'.len_utf8() + next.len_utf8();
let escape_start_offset = index + escape_start_len;
if let Cow::Owned(normalised) = &normalised {
output.push_str(&input[last_index..escape_start_offset]);
output.push_str(normalised);
last_index = escape_start_offset + normalised.len();
};
// Move the `chars` iterator passed the escape sequence.
// Simply reassigning `chars` doesn't work because the indices` would
// then be off.
for _ in 0..next.len_utf8() + normalised.len() {
chars.next();
}
}
}
if !quotes.triple {
#[allow(clippy::if_same_then_else)]
if next == opposite_quote && formatted_value_nesting == 0 {
// Remove the escape by ending before the backslash and starting again with the quote
chars.next();
output.push_str(&input[last_index..index]);
last_index = index + '\\'.len_utf8();
} else if next == preferred_quote {
// Quote is already escaped, skip over it.
chars.next();
}
}
}
} else if !quotes.triple && c == preferred_quote && formatted_value_nesting == 0 {
// Escape the quote
output.push_str(&input[last_index..index]);
output.push('\\');
output.push(c);
last_index = index + preferred_quote.len_utf8();
}
}
}
let normalized = if last_index == 0 {
Cow::Borrowed(input)
} else {
output.push_str(&input[last_index..]);
Cow::Owned(output)
};
normalized
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum UnicodeEscape {
/// A hex escape sequence of either 2 (`\x`), 4 (`\u`) or 8 (`\U`) hex characters.
Hex(usize),
/// An escaped unicode name (`\N{name}`)
CharacterName,
}
impl UnicodeEscape {
fn new(first: char, allow_unicode: bool) -> Option<UnicodeEscape> {
Some(match first {
'x' => UnicodeEscape::Hex(2),
'u' if allow_unicode => UnicodeEscape::Hex(4),
'U' if allow_unicode => UnicodeEscape::Hex(8),
'N' if allow_unicode => UnicodeEscape::CharacterName,
_ => return None,
})
}
/// Normalises `\u..`, `\U..`, `\x..` and `\N{..}` escape sequences to:
///
/// * `\u`, `\U'` and `\x`: To use lower case for the characters `a-f`.
/// * `\N`: To use uppercase letters
fn normalize(self, input: &str) -> Option<Cow<str>> {
let mut normalised = String::new();
let len = match self {
UnicodeEscape::Hex(len) => {
// It's not a valid escape sequence if the input string has fewer characters
// left than required by the escape sequence.
if input.len() < len {
return None;
}
for (index, c) in input.char_indices().take(len) {
match c {
'0'..='9' | 'a'..='f' => {
if !normalised.is_empty() {
normalised.push(c);
}
}
'A'..='F' => {
if normalised.is_empty() {
normalised.reserve(len);
normalised.push_str(&input[..index]);
normalised.push(c.to_ascii_lowercase());
} else {
normalised.push(c.to_ascii_lowercase());
}
}
_ => {
// not a valid escape sequence
return None;
}
}
}
len
}
UnicodeEscape::CharacterName => {
let mut char_indices = input.char_indices();
if !matches!(char_indices.next(), Some((_, '{'))) {
return None;
}
loop {
if let Some((index, c)) = char_indices.next() {
match c {
'}' => {
if !normalised.is_empty() {
normalised.push('}');
}
// Name must be at least two characters long.
if index < 3 {
return None;
}
break index + '}'.len_utf8();
}
'0'..='9' | 'A'..='Z' | ' ' | '-' => {
if !normalised.is_empty() {
normalised.push(c);
}
}
'a'..='z' => {
if normalised.is_empty() {
normalised.reserve(c.len_utf8() + '}'.len_utf8());
normalised.push_str(&input[..index]);
normalised.push(c.to_ascii_uppercase());
} else {
normalised.push(c.to_ascii_uppercase());
}
}
_ => {
// Seems like an invalid escape sequence, don't normalise it.
return None;
}
}
} else {
// Unterminated escape sequence, don't normalise it.
return None;
}
}
}
};
Some(if normalised.is_empty() {
Cow::Borrowed(&input[..len])
} else {
Cow::Owned(normalised)
})
}
}
#[cfg(test)]
mod tests {
use crate::string::{normalize_string, QuoteChar, StringPrefix, StringQuotes, UnicodeEscape};
use std::borrow::Cow;
#[test]
fn normalize_32_escape() {
let escape_sequence = UnicodeEscape::new('U', true).unwrap();
assert_eq!(
Some(Cow::Owned("0001f60e".to_string())),
escape_sequence.normalize("0001F60E")
);
}
#[test]
fn normalize_hex_in_byte_string() {
let input = r"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
let normalized = normalize_string(
input,
StringQuotes {
triple: false,
quote_char: QuoteChar::Double,
},
StringPrefix::BYTE,
true,
);
assert_eq!(r"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", &normalized);
}
}

View File

@@ -0,0 +1,655 @@
use std::borrow::Cow;
use ruff_formatter::FormatContext;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::context::FStringState;
use crate::options::PythonVersion;
use crate::prelude::*;
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
use crate::string::{QuoteChar, Quoting, StringPart, StringPrefix, StringQuotes};
use crate::QuoteStyle;
pub(crate) struct StringNormalizer {
quoting: Quoting,
preferred_quote_style: QuoteStyle,
parent_docstring_quote_char: Option<QuoteChar>,
f_string_state: FStringState,
target_version: PythonVersion,
normalize_hex: bool,
}
impl StringNormalizer {
pub(crate) fn from_context(context: &PyFormatContext<'_>) -> Self {
Self {
quoting: Quoting::default(),
preferred_quote_style: QuoteStyle::default(),
parent_docstring_quote_char: context.docstring(),
f_string_state: context.f_string_state(),
target_version: context.options().target_version(),
normalize_hex: is_hex_codes_in_unicode_sequences_enabled(context),
}
}
pub(crate) fn with_preferred_quote_style(mut self, quote_style: QuoteStyle) -> Self {
self.preferred_quote_style = quote_style;
self
}
pub(crate) fn with_quoting(mut self, quoting: Quoting) -> Self {
self.quoting = quoting;
self
}
/// Computes the strings preferred quotes.
pub(crate) fn choose_quotes(&self, string: &StringPart, locator: &Locator) -> StringQuotes {
// Per PEP 8, always prefer double quotes for triple-quoted strings.
// Except when using quote-style-preserve.
let preferred_style = if string.quotes().triple {
// ... unless we're formatting a code snippet inside a docstring,
// then we specifically want to invert our quote style to avoid
// writing out invalid Python.
//
// It's worth pointing out that we can actually wind up being
// somewhat out of sync with PEP8 in this case. Consider this
// example:
//
// def foo():
// '''
// Something.
//
// >>> """tricksy"""
// '''
// pass
//
// Ideally, this would be reformatted as:
//
// def foo():
// """
// Something.
//
// >>> '''tricksy'''
// """
// pass
//
// But the logic here results in the original quoting being
// preserved. This is because the quoting style of the outer
// docstring is determined, in part, by looking at its contents. In
// this case, it notices that it contains a `"""` and thus infers
// that using `'''` would overall read better because it avoids
// the need to escape the interior `"""`. Except... in this case,
// the `"""` is actually part of a code snippet that could get
// reformatted to using a different quoting style itself.
//
// Fixing this would, I believe, require some fairly seismic
// changes to how formatting strings works. Namely, we would need
// to look for code snippets before normalizing the docstring, and
// then figure out the quoting style more holistically by looking
// at the various kinds of quotes used in the code snippets and
// what reformatting them might look like.
//
// Overall this is a bit of a corner case and just inverting the
// style from what the parent ultimately decided upon works, even
// if it doesn't have perfect alignment with PEP8.
if let Some(quote) = self.parent_docstring_quote_char {
QuoteStyle::from(quote.invert())
} else if self.preferred_quote_style.is_preserve() {
QuoteStyle::Preserve
} else {
QuoteStyle::Double
}
} else {
self.preferred_quote_style
};
let quoting = if let FStringState::InsideExpressionElement(context) = self.f_string_state {
// If we're inside an f-string, we need to make sure to preserve the
// existing quotes unless we're inside a triple-quoted f-string and
// the inner string itself isn't triple-quoted. For example:
//
// ```python
// f"""outer {"inner"}""" # Valid
// f"""outer {"""inner"""}""" # Invalid
// ```
//
// Or, if the target version supports PEP 701.
//
// The reason to preserve the quotes is based on the assumption that
// the original f-string is valid in terms of quoting, and we don't
// want to change that to make it invalid.
if (context.quotes().is_triple() && !string.quotes().is_triple())
|| self.target_version.supports_pep_701()
{
self.quoting
} else {
Quoting::Preserve
}
} else {
self.quoting
};
match quoting {
Quoting::Preserve => string.quotes(),
Quoting::CanChange => {
if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) {
let raw_content = locator.slice(string.content_range());
if string.prefix().is_raw_string() {
choose_quotes_for_raw_string(raw_content, string.quotes(), preferred_quote)
} else {
choose_quotes_impl(raw_content, string.quotes(), preferred_quote)
}
} else {
string.quotes()
}
}
}
}
/// Computes the strings preferred quotes and normalizes its content.
pub(crate) fn normalize<'a>(
&self,
string: &StringPart,
locator: &'a Locator,
) -> NormalizedString<'a> {
let raw_content = locator.slice(string.content_range());
let quotes = self.choose_quotes(string, locator);
let normalized = normalize_string(raw_content, quotes, string.prefix(), self.normalize_hex);
NormalizedString {
prefix: string.prefix(),
content_range: string.content_range(),
text: normalized,
quotes,
}
}
}
#[derive(Debug)]
pub(crate) struct NormalizedString<'a> {
prefix: crate::string::StringPrefix,
/// The quotes of the normalized string (preferred quotes)
quotes: StringQuotes,
/// The range of the string's content in the source (minus prefix and quotes).
content_range: TextRange,
/// The normalized text
text: Cow<'a, str>,
}
impl<'a> NormalizedString<'a> {
pub(crate) fn text(&self) -> &Cow<'a, str> {
&self.text
}
pub(crate) fn quotes(&self) -> StringQuotes {
self.quotes
}
pub(crate) fn prefix(&self) -> StringPrefix {
self.prefix
}
}
impl Ranged for NormalizedString<'_> {
fn range(&self) -> TextRange {
self.content_range
}
}
impl Format<PyFormatContext<'_>> for NormalizedString<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
ruff_formatter::write!(f, [self.prefix, self.quotes])?;
match &self.text {
Cow::Borrowed(_) => {
source_text_slice(self.range()).fmt(f)?;
}
Cow::Owned(normalized) => {
text(normalized).fmt(f)?;
}
}
self.quotes.fmt(f)
}
}
/// Choose the appropriate quote style for a raw string.
///
/// The preferred quote style is chosen unless the string contains unescaped quotes of the
/// preferred style. For example, `r"foo"` is chosen over `r'foo'` if the preferred quote
/// style is double quotes.
fn choose_quotes_for_raw_string(
input: &str,
quotes: StringQuotes,
preferred_quote: QuoteChar,
) -> StringQuotes {
let preferred_quote_char = preferred_quote.as_char();
let mut chars = input.chars().peekable();
let contains_unescaped_configured_quotes = loop {
match chars.next() {
Some('\\') => {
// Ignore escaped characters
chars.next();
}
// `"` or `'`
Some(c) if c == preferred_quote_char => {
if !quotes.triple {
break true;
}
match chars.peek() {
// We can't turn `r'''\""'''` into `r"""\"""""`, this would confuse the parser
// about where the closing triple quotes start
None => break true,
Some(next) if *next == preferred_quote_char => {
// `""` or `''`
chars.next();
// We can't turn `r'''""'''` into `r""""""""`, nor can we have
// `"""` or `'''` respectively inside the string
if chars.peek().is_none() || chars.peek() == Some(&preferred_quote_char) {
break true;
}
}
_ => {}
}
}
Some(_) => continue,
None => break false,
}
};
StringQuotes {
triple: quotes.triple,
quote_char: if contains_unescaped_configured_quotes {
quotes.quote_char
} else {
preferred_quote
},
}
}
/// Choose the appropriate quote style for a string.
///
/// For single quoted strings, the preferred quote style is used, unless the alternative quote style
/// would require fewer escapes.
///
/// For triple quoted strings, the preferred quote style is always used, unless the string contains
/// a triplet of the quote character (e.g., if double quotes are preferred, double quotes will be
/// used unless the string contains `"""`).
fn choose_quotes_impl(
input: &str,
quotes: StringQuotes,
preferred_quote: QuoteChar,
) -> StringQuotes {
let quote = if quotes.triple {
// True if the string contains a triple quote sequence of the configured quote style.
let mut uses_triple_quotes = false;
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
let preferred_quote_char = preferred_quote.as_char();
match c {
'\\' => {
if matches!(chars.peek(), Some('"' | '\\')) {
chars.next();
}
}
// `"` or `'`
c if c == preferred_quote_char => {
match chars.peek().copied() {
Some(c) if c == preferred_quote_char => {
// `""` or `''`
chars.next();
match chars.peek().copied() {
Some(c) if c == preferred_quote_char => {
// `"""` or `'''`
chars.next();
uses_triple_quotes = true;
break;
}
Some(_) => {}
None => {
// Handle `''' ""'''`. At this point we have consumed both
// double quotes, so on the next iteration the iterator is empty
// and we'd miss the string ending with a preferred quote
uses_triple_quotes = true;
break;
}
}
}
Some(_) => {
// A single quote char, this is ok
}
None => {
// Trailing quote at the end of the comment
uses_triple_quotes = true;
break;
}
}
}
_ => continue,
}
}
if uses_triple_quotes {
// String contains a triple quote sequence of the configured quote style.
// Keep the existing quote style.
quotes.quote_char
} else {
preferred_quote
}
} else {
let mut single_quotes = 0u32;
let mut double_quotes = 0u32;
for c in input.chars() {
match c {
'\'' => {
single_quotes += 1;
}
'"' => {
double_quotes += 1;
}
_ => continue,
}
}
match preferred_quote {
QuoteChar::Single => {
if single_quotes > double_quotes {
QuoteChar::Double
} else {
QuoteChar::Single
}
}
QuoteChar::Double => {
if double_quotes > single_quotes {
QuoteChar::Single
} else {
QuoteChar::Double
}
}
}
};
StringQuotes {
triple: quotes.triple,
quote_char: quote,
}
}
/// Adds the necessary quote escapes and removes unnecessary escape sequences when quoting `input`
/// with the provided [`StringQuotes`] style.
///
/// Returns the normalized string and whether it contains new lines.
pub(crate) fn normalize_string(
input: &str,
quotes: StringQuotes,
prefix: StringPrefix,
normalize_hex: bool,
) -> Cow<str> {
// The normalized string if `input` is not yet normalized.
// `output` must remain empty if `input` is already normalized.
let mut output = String::new();
// Tracks the last index of `input` that has been written to `output`.
// If `last_index` is `0` at the end, then the input is already normalized and can be returned as is.
let mut last_index = 0;
let quote = quotes.quote_char;
let preferred_quote = quote.as_char();
let opposite_quote = quote.invert().as_char();
let mut chars = input.char_indices().peekable();
let is_raw = prefix.is_raw_string();
let is_fstring = prefix.is_fstring();
let mut formatted_value_nesting = 0u32;
while let Some((index, c)) = chars.next() {
if is_fstring && matches!(c, '{' | '}') {
if chars.peek().copied().is_some_and(|(_, next)| next == c) {
// Skip over the second character of the double braces
chars.next();
} else if c == '{' {
formatted_value_nesting += 1;
} else {
// Safe to assume that `c == '}'` here because of the matched pattern above
formatted_value_nesting = formatted_value_nesting.saturating_sub(1);
}
continue;
}
if c == '\r' {
output.push_str(&input[last_index..index]);
// Skip over the '\r' character, keep the `\n`
if chars.peek().copied().is_some_and(|(_, next)| next == '\n') {
chars.next();
}
// Replace the `\r` with a `\n`
else {
output.push('\n');
}
last_index = index + '\r'.len_utf8();
} else if !is_raw {
if c == '\\' {
if let Some((_, next)) = chars.clone().next() {
if next == '\\' {
// Skip over escaped backslashes
chars.next();
} else if normalize_hex {
if let Some(normalised) = UnicodeEscape::new(next, !prefix.is_byte())
.and_then(|escape| {
escape.normalize(&input[index + c.len_utf8() + next.len_utf8()..])
})
{
// Length of the `\` plus the length of the escape sequence character (`u` | `U` | `x`)
let escape_start_len = '\\'.len_utf8() + next.len_utf8();
let escape_start_offset = index + escape_start_len;
if let Cow::Owned(normalised) = &normalised {
output.push_str(&input[last_index..escape_start_offset]);
output.push_str(normalised);
last_index = escape_start_offset + normalised.len();
};
// Move the `chars` iterator passed the escape sequence.
// Simply reassigning `chars` doesn't work because the indices` would
// then be off.
for _ in 0..next.len_utf8() + normalised.len() {
chars.next();
}
}
}
if !quotes.triple {
#[allow(clippy::if_same_then_else)]
if next == opposite_quote && formatted_value_nesting == 0 {
// Remove the escape by ending before the backslash and starting again with the quote
chars.next();
output.push_str(&input[last_index..index]);
last_index = index + '\\'.len_utf8();
} else if next == preferred_quote {
// Quote is already escaped, skip over it.
chars.next();
}
}
}
} else if !quotes.triple && c == preferred_quote && formatted_value_nesting == 0 {
// Escape the quote
output.push_str(&input[last_index..index]);
output.push('\\');
output.push(c);
last_index = index + preferred_quote.len_utf8();
}
}
}
let normalized = if last_index == 0 {
Cow::Borrowed(input)
} else {
output.push_str(&input[last_index..]);
Cow::Owned(output)
};
normalized
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum UnicodeEscape {
/// A hex escape sequence of either 2 (`\x`), 4 (`\u`) or 8 (`\U`) hex characters.
Hex(usize),
/// An escaped unicode name (`\N{name}`)
CharacterName,
}
impl UnicodeEscape {
fn new(first: char, allow_unicode: bool) -> Option<UnicodeEscape> {
Some(match first {
'x' => UnicodeEscape::Hex(2),
'u' if allow_unicode => UnicodeEscape::Hex(4),
'U' if allow_unicode => UnicodeEscape::Hex(8),
'N' if allow_unicode => UnicodeEscape::CharacterName,
_ => return None,
})
}
/// Normalises `\u..`, `\U..`, `\x..` and `\N{..}` escape sequences to:
///
/// * `\u`, `\U'` and `\x`: To use lower case for the characters `a-f`.
/// * `\N`: To use uppercase letters
fn normalize(self, input: &str) -> Option<Cow<str>> {
let mut normalised = String::new();
let len = match self {
UnicodeEscape::Hex(len) => {
// It's not a valid escape sequence if the input string has fewer characters
// left than required by the escape sequence.
if input.len() < len {
return None;
}
for (index, c) in input.char_indices().take(len) {
match c {
'0'..='9' | 'a'..='f' => {
if !normalised.is_empty() {
normalised.push(c);
}
}
'A'..='F' => {
if normalised.is_empty() {
normalised.reserve(len);
normalised.push_str(&input[..index]);
normalised.push(c.to_ascii_lowercase());
} else {
normalised.push(c.to_ascii_lowercase());
}
}
_ => {
// not a valid escape sequence
return None;
}
}
}
len
}
UnicodeEscape::CharacterName => {
let mut char_indices = input.char_indices();
if !matches!(char_indices.next(), Some((_, '{'))) {
return None;
}
loop {
if let Some((index, c)) = char_indices.next() {
match c {
'}' => {
if !normalised.is_empty() {
normalised.push('}');
}
// Name must be at least two characters long.
if index < 3 {
return None;
}
break index + '}'.len_utf8();
}
'0'..='9' | 'A'..='Z' | ' ' | '-' => {
if !normalised.is_empty() {
normalised.push(c);
}
}
'a'..='z' => {
if normalised.is_empty() {
normalised.reserve(c.len_utf8() + '}'.len_utf8());
normalised.push_str(&input[..index]);
normalised.push(c.to_ascii_uppercase());
} else {
normalised.push(c.to_ascii_uppercase());
}
}
_ => {
// Seems like an invalid escape sequence, don't normalise it.
return None;
}
}
} else {
// Unterminated escape sequence, don't normalise it.
return None;
}
}
}
};
Some(if normalised.is_empty() {
Cow::Borrowed(&input[..len])
} else {
Cow::Owned(normalised)
})
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use crate::string::{QuoteChar, StringPrefix, StringQuotes};
use super::{normalize_string, UnicodeEscape};
#[test]
fn normalize_32_escape() {
let escape_sequence = UnicodeEscape::new('U', true).unwrap();
assert_eq!(
Some(Cow::Owned("0001f60e".to_string())),
escape_sequence.normalize("0001F60E")
);
}
#[test]
fn normalize_hex_in_byte_string() {
let input = r"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
let normalized = normalize_string(
input,
StringQuotes {
triple: false,
quote_char: QuoteChar::Double,
},
StringPrefix::BYTE,
true,
);
assert_eq!(r"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", &normalized);
}
}

View File

@@ -873,11 +873,11 @@ impl Ranged for LogicalLine {
}
}
struct VerbatimText {
pub(crate) struct VerbatimText {
verbatim_range: TextRange,
}
fn verbatim_text<T>(item: T) -> VerbatimText
pub(crate) fn verbatim_text<T>(item: T) -> VerbatimText
where
T: Ranged,
{

View File

@@ -1,314 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/collections.py
---
## Input
```python
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {1,2,3,}
b = {
1,2,
3}
c = {
1,
2,
3,
}
x = 1,
y = narf(),
nested = {(1,2,3),(4,5,6),}
nested_no_trailing_comma = {(1,2,3),(4,5,6)}
nested_long_lines = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccc", (1, 2, 3), "dddddddddddddddddddddddddddddddddddddddd"]
{"oneple": (1,),}
{"oneple": (1,)}
['ls', 'lsoneple/%s' % (foo,)]
x = {"oneple": (1,)}
y = {"oneple": (1,),}
assert False, ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" % bar)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[1, 2, 3,]
division_result_tuple = (6/2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter('instance_stopped').wait(
InstanceIds=[instance.id],
WaiterConfig={
'Delay': 5,
})
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={"Delay": 5,},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id], WaiterConfig={"Delay": 5,},
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -54,7 +54,7 @@
}
assert False, (
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s"
- % bar
+ % bar
)
# looping over a 1-tuple should also not get wrapped
@@ -75,7 +75,7 @@
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
- | {pylons.controllers.WSGIController}
+ | {pylons.controllers.WSGIController}
)
if True:
```
## Ruff Output
```python
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {
1,
2,
3,
}
b = {1, 2, 3}
c = {
1,
2,
3,
}
x = (1,)
y = (narf(),)
nested = {
(1, 2, 3),
(4, 5, 6),
}
nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)}
nested_long_lines = [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccc",
(1, 2, 3),
"dddddddddddddddddddddddddddddddddddddddd",
]
{
"oneple": (1,),
}
{"oneple": (1,)}
["ls", "lsoneple/%s" % (foo,)]
x = {"oneple": (1,)}
y = {
"oneple": (1,),
}
assert False, (
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s"
% bar
)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[
1,
2,
3,
]
division_result_tuple = (6 / 2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
```
## Black Output
```python
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {
1,
2,
3,
}
b = {1, 2, 3}
c = {
1,
2,
3,
}
x = (1,)
y = (narf(),)
nested = {
(1, 2, 3),
(4, 5, 6),
}
nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)}
nested_long_lines = [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccc",
(1, 2, 3),
"dddddddddddddddddddddddddddddddddddddddd",
]
{
"oneple": (1,),
}
{"oneple": (1,)}
["ls", "lsoneple/%s" % (foo,)]
x = {"oneple": (1,)}
y = {
"oneple": (1,),
}
assert False, (
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s"
% bar
)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[
1,
2,
3,
]
division_result_tuple = (6 / 2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
```

View File

@@ -192,7 +192,7 @@ instruction()#comment with bad spacing
children[0],
body,
children[-1], # type: ignore
@@ -72,14 +76,18 @@
@@ -72,7 +76,11 @@
body,
parameters.children[-1], # )2
]
@@ -204,19 +204,7 @@ instruction()#comment with bad spacing
+ ] # type: ignore
if (
self._proc is not None
- # has the child process finished?
- and self._returncode is None
- # the child process has finished, but the
- # transport hasn't been notified yet?
- and self._proc.poll() is None
+ # has the child process finished?
+ and self._returncode is None
+ # the child process has finished, but the
+ # transport hasn't been notified yet?
+ and self._proc.poll() is None
):
pass
# no newline before or after
# has the child process finished?
@@ -115,7 +123,9 @@
arg3=True,
)
@@ -240,23 +228,14 @@ instruction()#comment with bad spacing
)
@@ -151,14 +164,17 @@
[
CONFIG_FILE,
]
- + SHARED_CONFIG_FILES
- + USER_CONFIG_FILES
+ + SHARED_CONFIG_FILES
+ + USER_CONFIG_FILES
) # type: Final
@@ -158,7 +171,10 @@
class Test:
def _init_host(self, parsed) -> None:
- if parsed.hostname is None or not parsed.hostname.strip(): # type: ignore
+ if (
+ parsed.hostname is None # type: ignore
+ or not parsed.hostname.strip()
+ or not parsed.hostname.strip()
+ ):
pass
@@ -351,11 +330,11 @@ def inline_comments_in_brackets_ruin_everything():
] # type: ignore
if (
self._proc is not None
# has the child process finished?
and self._returncode is None
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll() is None
# has the child process finished?
and self._returncode is None
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll() is None
):
pass
# no newline before or after
@@ -432,8 +411,8 @@ CONFIG_FILES = (
[
CONFIG_FILE,
]
+ SHARED_CONFIG_FILES
+ USER_CONFIG_FILES
+ SHARED_CONFIG_FILES
+ USER_CONFIG_FILES
) # type: Final
@@ -441,7 +420,7 @@ class Test:
def _init_host(self, parsed) -> None:
if (
parsed.hostname is None # type: ignore
or not parsed.hostname.strip()
or not parsed.hostname.strip()
):
pass

View File

@@ -141,23 +141,6 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite
an_element_with_a_long_value = calls() or more_calls() and more() # type: bool
tup = (
@@ -61,11 +59,11 @@
a = (
element
- + another_element
- + another_element_with_long_name
- + element
- + another_element
- + another_element_with_long_name
+ + another_element
+ + another_element_with_long_name
+ + element
+ + another_element
+ + another_element_with_long_name
) # type: int
@@ -100,7 +98,13 @@
)
@@ -260,11 +243,11 @@ def f(
a = (
element
+ another_element
+ another_element_with_long_name
+ element
+ another_element
+ another_element_with_long_name
+ another_element
+ another_element_with_long_name
+ element
+ another_element
+ another_element_with_long_name
) # type: int

View File

@@ -146,40 +146,6 @@ if (
# b comment
None
)
@@ -92,20 +91,20 @@
# comment1
a
# comment2
- or (
- # comment3
- (
- # comment4
- b
- )
- # comment5
- and
- # comment6
- c
or (
- # comment7
- d
+ # comment3
+ (
+ # comment4
+ b
+ )
+ # comment5
+ and
+ # comment6
+ c
+ or (
+ # comment7
+ d
+ )
)
- )
):
print("Foo")
```
## Ruff Output
@@ -278,21 +244,21 @@ if (
# comment1
a
# comment2
or (
# comment3
(
# comment4
b
)
# comment5
and
# comment6
c
or (
# comment7
d
)
or (
# comment3
(
# comment4
b
)
# comment5
and
# comment6
c
or (
# comment7
d
)
)
):
print("Foo")
```

View File

@@ -193,26 +193,6 @@ class C:
```diff
--- Black
+++ Ruff
@@ -22,8 +22,8 @@
if (
# Rule 1
i % 2 == 0
- # Rule 2
- and i % 3 == 0
+ # Rule 2
+ and i % 3 == 0
):
while (
# Just a comment
@@ -41,7 +41,7 @@
)
return (
'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s'
- % (test.name, test.filename, lineno, lname, err)
+ % (test.name, test.filename, lineno, lname, err)
)
def omitting_trailers(self) -> None:
@@ -110,19 +110,20 @@
value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected"
@@ -242,12 +222,12 @@ class C:
+ key8: value8,
+ key9: value9,
+ }
+ == expected
+ == expected
+ ), "Not what we expected and the message is too long to fit in one line"
assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True
@@ -161,21 +162,19 @@
@@ -161,9 +162,7 @@
8 STORE_ATTR 0 (x)
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
@@ -258,29 +238,6 @@ class C:
assert (
expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect
- == {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
+ == {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ }
)
```
## Ruff Output
@@ -310,8 +267,8 @@ class C:
if (
# Rule 1
i % 2 == 0
# Rule 2
and i % 3 == 0
# Rule 2
and i % 3 == 0
):
while (
# Just a comment
@@ -329,7 +286,7 @@ class C:
)
return (
'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s'
% (test.name, test.filename, lineno, lname, err)
% (test.name, test.filename, lineno, lname, err)
)
def omitting_trailers(self) -> None:
@@ -410,7 +367,7 @@ class C:
key8: value8,
key9: value9,
}
== expected
== expected
), "Not what we expected and the message is too long to fit in one line"
assert expected(
@@ -454,17 +411,17 @@ class C:
assert (
expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
)
```

View File

@@ -193,26 +193,6 @@ class C:
```diff
--- Black
+++ Ruff
@@ -22,8 +22,8 @@
if (
# Rule 1
i % 2 == 0
- # Rule 2
- and i % 3 == 0
+ # Rule 2
+ and i % 3 == 0
):
while (
# Just a comment
@@ -41,7 +41,7 @@
)
return (
'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s'
- % (test.name, test.filename, lineno, lname, err)
+ % (test.name, test.filename, lineno, lname, err)
)
def omitting_trailers(self) -> None:
@@ -110,19 +110,20 @@
value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected"
@@ -242,12 +222,12 @@ class C:
+ key8: value8,
+ key9: value9,
+ }
+ == expected
+ == expected
+ ), "Not what we expected and the message is too long to fit in one line"
assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True
@@ -161,21 +162,19 @@
@@ -161,9 +162,7 @@
8 STORE_ATTR 0 (x)
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
@@ -258,29 +238,6 @@ class C:
assert (
expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect
- == {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
+ == {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ }
)
```
## Ruff Output
@@ -310,8 +267,8 @@ class C:
if (
# Rule 1
i % 2 == 0
# Rule 2
and i % 3 == 0
# Rule 2
and i % 3 == 0
):
while (
# Just a comment
@@ -329,7 +286,7 @@ class C:
)
return (
'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s'
% (test.name, test.filename, lineno, lname, err)
% (test.name, test.filename, lineno, lname, err)
)
def omitting_trailers(self) -> None:
@@ -410,7 +367,7 @@ class C:
key8: value8,
key9: value9,
}
== expected
== expected
), "Not what we expected and the message is too long to fit in one line"
assert expected(
@@ -454,17 +411,17 @@ class C:
assert (
expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
)
```

View File

@@ -275,131 +275,6 @@ last_call()
) # note: no trailing comma pre-3.6
call(*gidgets[:2])
call(a, *gidgets[:2])
@@ -277,95 +277,95 @@
pass
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+ in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+ not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+ is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
- is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+ is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
if (
threading.current_thread() != threading.main_thread()
- and threading.current_thread() != threading.main_thread()
- or signal.getsignal(signal.SIGINT) != signal.default_int_handler
+ and threading.current_thread() != threading.main_thread()
+ or signal.getsignal(signal.SIGINT) != signal.default_int_handler
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
- | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
+ | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
):
return True
if (
~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
- | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
- ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
+ | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
+ ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
):
return True
if (
~aaaaaaaaaaaaaaaa.a
- + aaaaaaaaaaaaaaaa.b
- - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
- | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
- ^ aaaaaaaaaaaaaaaa.i
- << aaaaaaaaaaaaaaaa.k
- >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
+ + aaaaaaaaaaaaaaaa.b
+ - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
+ | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
+ ^ aaaaaaaaaaaaaaaa.i
+ << aaaaaaaaaaaaaaaa.k
+ >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
):
return True
(
aaaaaaaaaaaaaaaa
- + aaaaaaaaaaaaaaaa
- - aaaaaaaaaaaaaaaa
- * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
- / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
+ + aaaaaaaaaaaaaaaa
+ - aaaaaaaaaaaaaaaa
+ * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
+ / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
)
aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
bbbb >> bbbb * bbbb
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
last_call()
# standalone comment at ENDMARKER
```
## Ruff Output
@@ -684,95 +559,95 @@ for (
pass
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
)
if (
threading.current_thread() != threading.main_thread()
and threading.current_thread() != threading.main_thread()
or signal.getsignal(signal.SIGINT) != signal.default_int_handler
and threading.current_thread() != threading.main_thread()
or signal.getsignal(signal.SIGINT) != signal.default_int_handler
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
/ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
/ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
return True
if (
~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
| aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
| aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
):
return True
if (
~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
| aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
| aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
):
return True
if (
~aaaaaaaaaaaaaaaa.a
+ aaaaaaaaaaaaaaaa.b
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
| aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
^ aaaaaaaaaaaaaaaa.i
<< aaaaaaaaaaaaaaaa.k
>> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
+ aaaaaaaaaaaaaaaa.b
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
| aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
^ aaaaaaaaaaaaaaaa.i
<< aaaaaaaaaaaaaaaa.k
>> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
):
return True
(
aaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaa
- aaaaaaaaaaaaaaaa
* (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
/ (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
+ aaaaaaaaaaaaaaaa
- aaaaaaaaaaaaaaaa
* (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
/ (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa)
)
aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
<< aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
<< aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
bbbb >> bbbb * bbbb
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
last_call()
# standalone comment at ENDMARKER

View File

@@ -110,15 +110,6 @@ elif unformatted:
},
)
@@ -19,7 +18,7 @@
"-la",
]
# fmt: on
- + path,
+ + path,
check=True,
)
@@ -82,6 +81,6 @@
if x:
return x
@@ -152,7 +143,7 @@ run(
"-la",
]
# fmt: on
+ path,
+ path,
check=True,
)

View File

@@ -21,17 +21,15 @@ else:
```diff
--- Black
+++ Ruff
@@ -1,8 +1,8 @@
@@ -1,7 +1,7 @@
a, b, c = 3, 4, 5
if (
a == 3
- and b != 9 # fmt: skip
- and c is not None
+ and b != 9 # fmt: skip
+ and c is not None
+ and b != 9 # fmt: skip
and c is not None
):
print("I'm good!")
else:
```
## Ruff Output
@@ -40,8 +38,8 @@ else:
a, b, c = 3, 4, 5
if (
a == 3
and b != 9 # fmt: skip
and c is not None
and b != 9 # fmt: skip
and c is not None
):
print("I'm good!")
else:

View File

@@ -1,343 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/function_trailing_comma.py
---
## Input
```python
def f(a,):
d = {'key': 'value',}
tup = (1,)
def f2(a,b,):
d = {'key': 'value', 'key2': 'value2',}
tup = (1,2,)
def f(a:int=1,):
call(arg={'explode': 'this',})
call2(arg=[1,2,3],)
x = {
"a": 1,
"b": 2,
}["a"]
if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]:
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
]:
json = {"k": {"k2": {"k3": [1,]}}}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(very_long_parameter_so_yeah: str, another_long_parameter: int) -> (
another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not
):
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(this_shouldn_t_get_a_trailing_comma_too)
):
pass
def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
))
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1, (one, two,), argument4, argument5, argument6
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -38,16 +38,16 @@
}["a"]
if (
a
- == {
- "a": 1,
- "b": 2,
- "c": 3,
- "d": 4,
- "e": 5,
- "f": 6,
- "g": 7,
- "h": 8,
- }["a"]
+ == {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5,
+ "f": 6,
+ "g": 7,
+ "h": 8,
+ }["a"]
):
pass
```
## Ruff Output
```python
def f(
a,
):
d = {
"key": "value",
}
tup = (1,)
def f2(
a,
b,
):
d = {
"key": "value",
"key2": "value2",
}
tup = (
1,
2,
)
def f(
a: int = 1,
):
call(
arg={
"explode": "this",
}
)
call2(
arg=[1, 2, 3],
)
x = {
"a": 1,
"b": 2,
}["a"]
if (
a
== {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
):
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
):
json = {
"k": {
"k2": {
"k3": [
1,
]
}
}
}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(
very_long_parameter_so_yeah: str, another_long_parameter: int
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```
## Black Output
```python
def f(
a,
):
d = {
"key": "value",
}
tup = (1,)
def f2(
a,
b,
):
d = {
"key": "value",
"key2": "value2",
}
tup = (
1,
2,
)
def f(
a: int = 1,
):
call(
arg={
"explode": "this",
}
)
call2(
arg=[1, 2, 3],
)
x = {
"a": 1,
"b": 2,
}["a"]
if (
a
== {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
):
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
):
json = {
"k": {
"k2": {
"k3": [
1,
]
}
}
}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(
very_long_parameter_so_yeah: str, another_long_parameter: int
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```

View File

@@ -1,227 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/import_spacing.py
---
## Input
```python
"""The asyncio package, tracking PEP 3156."""
# flake8: noqa
from logging import (
WARNING
)
from logging import (
ERROR,
)
import sys
# This relies on each of the submodules having an __all__ variable.
from .base_events import *
from .coroutines import *
from .events import * # comment here
from .futures import *
from .locks import * # comment here
from .protocols import *
from ..runners import * # comment here
from ..queues import *
from ..streams import *
from some_library import (
Just, Enough, Libraries, To, Fit, In, This, Nice, Split, Which, We, No, Longer, Use
)
from name_of_a_company.extremely_long_project_name.component.ttypes import CuteLittleServiceHandlerFactoryyy
from name_of_a_company.extremely_long_project_name.extremely_long_component_name.ttypes import *
from .a.b.c.subprocess import *
from . import (tasks)
from . import (A, B, C)
from . import SomeVeryLongNameAndAllOfItsAdditionalLetters1, \
SomeVeryLongNameAndAllOfItsAdditionalLetters2
__all__ = (
base_events.__all__
+ coroutines.__all__
+ events.__all__
+ futures.__all__
+ locks.__all__
+ protocols.__all__
+ runners.__all__
+ queues.__all__
+ streams.__all__
+ tasks.__all__
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -52,13 +52,13 @@
__all__ = (
base_events.__all__
- + coroutines.__all__
- + events.__all__
- + futures.__all__
- + locks.__all__
- + protocols.__all__
- + runners.__all__
- + queues.__all__
- + streams.__all__
- + tasks.__all__
+ + coroutines.__all__
+ + events.__all__
+ + futures.__all__
+ + locks.__all__
+ + protocols.__all__
+ + runners.__all__
+ + queues.__all__
+ + streams.__all__
+ + tasks.__all__
)
```
## Ruff Output
```python
"""The asyncio package, tracking PEP 3156."""
# flake8: noqa
from logging import WARNING
from logging import (
ERROR,
)
import sys
# This relies on each of the submodules having an __all__ variable.
from .base_events import *
from .coroutines import *
from .events import * # comment here
from .futures import *
from .locks import * # comment here
from .protocols import *
from ..runners import * # comment here
from ..queues import *
from ..streams import *
from some_library import (
Just,
Enough,
Libraries,
To,
Fit,
In,
This,
Nice,
Split,
Which,
We,
No,
Longer,
Use,
)
from name_of_a_company.extremely_long_project_name.component.ttypes import (
CuteLittleServiceHandlerFactoryyy,
)
from name_of_a_company.extremely_long_project_name.extremely_long_component_name.ttypes import *
from .a.b.c.subprocess import *
from . import tasks
from . import A, B, C
from . import (
SomeVeryLongNameAndAllOfItsAdditionalLetters1,
SomeVeryLongNameAndAllOfItsAdditionalLetters2,
)
__all__ = (
base_events.__all__
+ coroutines.__all__
+ events.__all__
+ futures.__all__
+ locks.__all__
+ protocols.__all__
+ runners.__all__
+ queues.__all__
+ streams.__all__
+ tasks.__all__
)
```
## Black Output
```python
"""The asyncio package, tracking PEP 3156."""
# flake8: noqa
from logging import WARNING
from logging import (
ERROR,
)
import sys
# This relies on each of the submodules having an __all__ variable.
from .base_events import *
from .coroutines import *
from .events import * # comment here
from .futures import *
from .locks import * # comment here
from .protocols import *
from ..runners import * # comment here
from ..queues import *
from ..streams import *
from some_library import (
Just,
Enough,
Libraries,
To,
Fit,
In,
This,
Nice,
Split,
Which,
We,
No,
Longer,
Use,
)
from name_of_a_company.extremely_long_project_name.component.ttypes import (
CuteLittleServiceHandlerFactoryyy,
)
from name_of_a_company.extremely_long_project_name.extremely_long_component_name.ttypes import *
from .a.b.c.subprocess import *
from . import tasks
from . import A, B, C
from . import (
SomeVeryLongNameAndAllOfItsAdditionalLetters1,
SomeVeryLongNameAndAllOfItsAdditionalLetters2,
)
__all__ = (
base_events.__all__
+ coroutines.__all__
+ events.__all__
+ futures.__all__
+ locks.__all__
+ protocols.__all__
+ runners.__all__
+ queues.__all__
+ streams.__all__
+ tasks.__all__
)
```

View File

@@ -304,52 +304,7 @@ long_unmergable_string_with_pragma = (
```diff
--- Black
+++ Ruff
@@ -40,11 +40,11 @@
sooo="soooo", x=2
),
"A %s %s"
- % (
- "formatted",
- "string",
- ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
- % ("soooo", 2),
+ % (
+ "formatted",
+ "string",
+ ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
+ % ("soooo", 2),
}
func_with_keywords(
@@ -123,7 +123,7 @@
old_fmt_string1 = (
"While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it."
- % ("formatting", "code")
+ % ("formatting", "code")
)
old_fmt_string2 = "This is a %s %s %s %s" % (
@@ -135,12 +135,12 @@
old_fmt_string3 = (
"Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s"
- % (
- "really really really really really",
- "old",
- "way to format strings!",
- "Use f-strings instead!",
- )
+ % (
+ "really really really really really",
+ "old",
+ "way to format strings!",
+ "Use f-strings instead!",
+ )
)
fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one."
@@ -165,36 +165,32 @@
@@ -165,13 +165,9 @@
triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched."""
@@ -365,50 +320,6 @@ long_unmergable_string_with_pragma = (
"formatting"
)
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s."
- % "formatting"
+ % "formatting"
)
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s."
- % ("string", "formatting")
+ % ("string", "formatting")
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
- + added
- + " to a variable and then added to another string."
+ + added
+ + " to a variable and then added to another string."
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
- + added
- + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
+ + added
+ + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
"and a second argument",
and_a_third,
)
@@ -249,10 +245,10 @@
annotated_variable: Final = (
"This is a large "
- + STRING
- + " that has been "
- + CONCATENATED
- + "using the '+' operator."
+ + STRING
+ + " that has been "
+ + CONCATENATED
+ + "using the '+' operator."
)
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal[
```
## Ruff Output
@@ -456,11 +367,11 @@ D4 = {
sooo="soooo", x=2
),
"A %s %s"
% (
"formatted",
"string",
): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
% ("soooo", 2),
% (
"formatted",
"string",
): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
% ("soooo", 2),
}
func_with_keywords(
@@ -539,7 +450,7 @@ fmt_string2 = "But what about when the string is {} but {}".format(
old_fmt_string1 = (
"While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it."
% ("formatting", "code")
% ("formatting", "code")
)
old_fmt_string2 = "This is a %s %s %s %s" % (
@@ -551,12 +462,12 @@ old_fmt_string2 = "This is a %s %s %s %s" % (
old_fmt_string3 = (
"Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s"
% (
"really really really really really",
"old",
"way to format strings!",
"Use f-strings instead!",
)
% (
"really really really really really",
"old",
"way to format strings!",
"Use f-strings instead!",
)
)
fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one."
@@ -589,24 +500,24 @@ assert some_type_of_boolean_expression, "Followed by a really really really long
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s."
% "formatting"
% "formatting"
)
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s."
% ("string", "formatting")
% ("string", "formatting")
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
+ added
+ " to a variable and then added to another string."
+ added
+ " to a variable and then added to another string."
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
+ added
+ " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
+ added
+ " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
"and a second argument",
and_a_third,
)
@@ -661,10 +572,10 @@ func_with_bad_parens(
annotated_variable: Final = (
"This is a large "
+ STRING
+ " that has been "
+ CONCATENATED
+ "using the '+' operator."
+ STRING
+ " that has been "
+ CONCATENATED
+ "using the '+' operator."
)
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal[

View File

@@ -95,96 +95,7 @@ def f(
```diff
--- Black
+++ Ruff
@@ -1,17 +1,17 @@
# This has always worked
z = (
Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
)
# "AnnAssign"s now also work
z: (
Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
)
z: Short | Short2 | Short3 | Short4
z: int
@@ -20,9 +20,9 @@
z: (
Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
+ | Loooooooooooooooooooooooong
) = 7
z: Short | Short2 | Short3 | Short4 = 8
z: int = 2.3
@@ -31,39 +31,39 @@
# In case I go for not enforcing parantheses, this might get improved at the same time
x = (
z
- == 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999,
+ == 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999,
y
- == 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999,
+ == 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999,
)
x = (
z
- == (
- 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999
- | 9999999999999999999999999999999999999999
- ),
+ == (
+ 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999
+ | 9999999999999999999999999999999999999999
+ ),
y
- == (
- 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999
- + 9999999999999999999999999999999999999999
- ),
+ == (
+ 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999
+ + 9999999999999999999999999999999999999999
+ ),
)
# handle formatting of "tname"s in parameter list
@@ -63,7 +63,7 @@
# remove unnecessary paren
@@ -199,12 +110,14 @@ def f(
i: int,
- x: (
- Loooooooooooooooooooooooong
+ x: Loooooooooooooooooooooooong
| Looooooooooooooooong
| Looooooooooooooooooooong
- | Looooooooooooooooong
- | Looooooooooooooooooooong
- | Looooooong
- ),
+ | Looooooong,
+ x: Loooooooooooooooooooooooong
+ | Looooooooooooooooong
+ | Looooooooooooooooooooong
+ | Looooooong,
*,
s: str,
) -> None:
@@ -225,17 +138,17 @@ def f(
# This has always worked
z = (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
)
# "AnnAssign"s now also work
z: (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
)
z: Short | Short2 | Short3 | Short4
z: int
@@ -244,9 +157,9 @@ z: int
z: (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
) = 7
z: Short | Short2 | Short3 | Short4 = 8
z: int = 2.3
@@ -255,32 +168,32 @@ z: int = foo()
# In case I go for not enforcing parantheses, this might get improved at the same time
x = (
z
== 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999,
== 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999,
y
== 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999,
== 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999,
)
x = (
z
== (
9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
),
== (
9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
),
y
== (
9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
),
== (
9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
),
)
# handle formatting of "tname"s in parameter list
@@ -297,9 +210,9 @@ def foo(i: (int,)) -> None: ...
def foo(
i: int,
x: Loooooooooooooooooooooooong
| Looooooooooooooooong
| Looooooooooooooooooooong
| Looooooong,
| Looooooooooooooooong
| Looooooooooooooooooooong
| Looooooong,
*,
s: str,
) -> None:

View File

@@ -1,75 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_604.py
---
## Input
```python
def some_very_long_name_function() -> my_module.Asdf | my_module.AnotherType | my_module.YetAnotherType | None:
pass
def some_very_long_name_function() -> my_module.Asdf | my_module.AnotherType | my_module.YetAnotherType | my_module.EvenMoreType | None:
pass
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -6,9 +6,9 @@
def some_very_long_name_function() -> (
my_module.Asdf
- | my_module.AnotherType
- | my_module.YetAnotherType
- | my_module.EvenMoreType
- | None
+ | my_module.AnotherType
+ | my_module.YetAnotherType
+ | my_module.EvenMoreType
+ | None
):
pass
```
## Ruff Output
```python
def some_very_long_name_function() -> (
my_module.Asdf | my_module.AnotherType | my_module.YetAnotherType | None
):
pass
def some_very_long_name_function() -> (
my_module.Asdf
| my_module.AnotherType
| my_module.YetAnotherType
| my_module.EvenMoreType
| None
):
pass
```
## Black Output
```python
def some_very_long_name_function() -> (
my_module.Asdf | my_module.AnotherType | my_module.YetAnotherType | None
):
pass
def some_very_long_name_function() -> (
my_module.Asdf
| my_module.AnotherType
| my_module.YetAnotherType
| my_module.EvenMoreType
| None
):
pass
```

View File

@@ -13,14 +13,12 @@ importA;()<<0**0#
```diff
--- Black
+++ Ruff
@@ -1,6 +1,6 @@
importA
@@ -2,5 +2,5 @@
(
()
- << 0
<< 0
- ** 0
+ << 0
+ **0
+ **0
) #
```
@@ -30,8 +28,8 @@ importA;()<<0**0#
importA
(
()
<< 0
**0
<< 0
**0
) #
```

View File

@@ -85,18 +85,21 @@ class Random:
- a_very_long_variable * and_a_very_long_function_call() / 100000.0
- )
+ "a key in my dict": a_very_long_variable
+ * and_a_very_long_function_call()
+ / 100000.0
+ * and_a_very_long_function_call()
+ / 100000.0
}
my_dict = {
- "a key in my dict": (
- a_very_long_variable
+ "a key in my dict": a_very_long_variable
* and_a_very_long_function_call()
* and_another_long_func()
/ 100000.0
- * and_a_very_long_function_call()
- * and_another_long_func()
- / 100000.0
- )
+ "a key in my dict": a_very_long_variable
+ * and_a_very_long_function_call()
+ * and_another_long_func()
+ / 100000.0
}
my_dict = {
@@ -137,15 +140,15 @@ my_dict = {
my_dict = {
"a key in my dict": a_very_long_variable
* and_a_very_long_function_call()
/ 100000.0
* and_a_very_long_function_call()
/ 100000.0
}
my_dict = {
"a key in my dict": a_very_long_variable
* and_a_very_long_function_call()
* and_another_long_func()
/ 100000.0
* and_a_very_long_function_call()
* and_another_long_func()
/ 100000.0
}
my_dict = {

View File

@@ -439,11 +439,11 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
- "This is a really really really long string that has to go inside of a"
- " dictionary. It is %s bad (#%d)." % ("soooo", 2)
- ),
+ % (
+ "formatted",
+ "string",
+ ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
+ % ("soooo", 2),
+ % (
+ "formatted",
+ "string",
+ ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
+ % ("soooo", 2),
}
D5 = { # Test for https://github.com/psf/black/issues/3261
@@ -622,29 +622,22 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
- "While we are on the topic of %s, we should also note that old-style formatting"
- " must also be preserved, since some %s still uses it." % ("formatting", "code")
+ "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it."
+ % ("formatting", "code")
+ % ("formatting", "code")
)
old_fmt_string2 = "This is a %s %s %s %s" % (
@@ -271,36 +201,23 @@
@@ -271,8 +201,7 @@
)
old_fmt_string3 = (
- "Whereas only the strings after the percent sign were long in the last example,"
- " this example uses a long initial string as well. This is another %s %s %s %s"
- % (
- "really really really really really",
- "old",
- "way to format strings!",
- "Use f-strings instead!",
- )
+ "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s"
+ % (
+ "really really really really really",
+ "old",
+ "way to format strings!",
+ "Use f-strings instead!",
+ )
% (
"really really really really really",
"old",
@@ -281,26 +210,14 @@
)
)
-fstring = (
@@ -695,37 +688,33 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
- "Followed by a really really really long string that is used to provide context to"
- " the AssertionError exception, which uses dynamic string %s." % "formatting"
+ "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s."
+ % "formatting"
+ % "formatting"
)
assert some_type_of_boolean_expression, (
- "Followed by a really really really long string that is used to provide context to"
- " the AssertionError exception, which uses dynamic %s %s."
- % ("string", "formatting")
+ "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s."
+ % ("string", "formatting")
% ("string", "formatting")
)
some_function_call(
- "With a reallly generic name and with a really really long string that is, at some"
- " point down the line, "
- + added
- + " to a variable and then added to another string."
+ "With a reallly generic name and with a really really long string that is, at some point down the line, "
+ + added
+ + " to a variable and then added to another string."
+ added
+ " to a variable and then added to another string."
)
some_function_call(
- "With a reallly generic name and with a really really long string that is, at some"
- " point down the line, "
- + added
+ "With a reallly generic name and with a really really long string that is, at some point down the line, "
+ added
- + " to a variable and then added to another string. But then what happens when the"
- " final string is also supppppperrrrr long?! Well then that second (realllllllly"
- " long) string should be split too.",
+ "With a reallly generic name and with a really really long string that is, at some point down the line, "
+ + added
+ + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
+ + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
"and a second argument",
and_a_third,
)
@@ -783,7 +772,7 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
x,
y,
z,
@@ -397,61 +306,38 @@
@@ -397,7 +306,7 @@
func_with_bad_parens(
x,
y,
@@ -792,21 +781,14 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
z,
)
annotated_variable: Final = (
"This is a large "
- + STRING
- + " that has been "
- + CONCATENATED
- + "using the '+' operator."
-)
@@ -408,50 +317,27 @@
+ CONCATENATED
+ "using the '+' operator."
)
-annotated_variable: Final = (
- "This is a large string that has a type annotation attached to it. A type"
- " annotation should NOT stop a long string from being wrapped."
+ + STRING
+ + " that has been "
+ + CONCATENATED
+ + "using the '+' operator."
)
-)
+annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal["fakse_literal"] = (
- "This is a large string that has a type annotation attached to it. A type"
@@ -920,7 +902,7 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
)
dict_with_lambda_values = {
@@ -524,61 +383,54 @@
@@ -524,65 +383,58 @@
# Complex string concatenations with a method call in the middle.
code = (
@@ -928,16 +910,15 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
- + ", \n".join(
- " (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
- for attrname, visit_name in names
- )
- + "\n ]\n"
+ (" return [\n")
+ + (
+ ", \n".join(
+ " (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
+ for attrname, visit_name in names
+ )
+ + (
+ ", \n".join(
+ " (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
+ for attrname, visit_name in names
+ )
+ + ("\n ]\n")
)
- + "\n ]\n"
+ + ("\n ]\n")
)
@@ -960,7 +941,7 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
log.info(
- "Skipping:"
- f" {desc['db_id']} {foo('bar',x=123)} {'foo' != 'bar'} {(x := 'abc=')} {pos_share=} {desc['status']} {desc['exposure_max']}"
+ f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
+ f'Skipping: {desc["db_id"]} {foo("bar", x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)
log.info(
@@ -1000,6 +981,18 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
)
log.info(
- f"""Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
+ f"""Skipping: {"a" == "b"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)
log.info(
@@ -590,5 +442,5 @@
)
log.info(
- f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"""
+ f"""Skipping: {"a" == "b"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)
```
## Ruff Output
@@ -1047,11 +1040,11 @@ D4 = {
sooo="soooo", x=2
),
"A %s %s"
% (
"formatted",
"string",
): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
% ("soooo", 2),
% (
"formatted",
"string",
): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)."
% ("soooo", 2),
}
D5 = { # Test for https://github.com/psf/black/issues/3261
@@ -1197,7 +1190,7 @@ fmt_string2 = "But what about when the string is {} but {}".format(
old_fmt_string1 = (
"While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it."
% ("formatting", "code")
% ("formatting", "code")
)
old_fmt_string2 = "This is a %s %s %s %s" % (
@@ -1209,12 +1202,12 @@ old_fmt_string2 = "This is a %s %s %s %s" % (
old_fmt_string3 = (
"Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s"
% (
"really really really really really",
"old",
"way to format strings!",
"Use f-strings instead!",
)
% (
"really really really really really",
"old",
"way to format strings!",
"Use f-strings instead!",
)
)
fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one."
@@ -1247,24 +1240,24 @@ assert some_type_of_boolean_expression, "Followed by a really really really long
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s."
% "formatting"
% "formatting"
)
assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s."
% ("string", "formatting")
% ("string", "formatting")
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
+ added
+ " to a variable and then added to another string."
+ added
+ " to a variable and then added to another string."
)
some_function_call(
"With a reallly generic name and with a really really long string that is, at some point down the line, "
+ added
+ " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
+ added
+ " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.",
"and a second argument",
and_a_third,
)
@@ -1319,10 +1312,10 @@ func_with_bad_parens(
annotated_variable: Final = (
"This is a large "
+ STRING
+ " that has been "
+ CONCATENATED
+ "using the '+' operator."
+ STRING
+ " that has been "
+ CONCATENATED
+ "using the '+' operator."
)
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal["fakse_literal"] = (
@@ -1391,13 +1384,13 @@ dict_with_lambda_values = {
# Complex string concatenations with a method call in the middle.
code = (
(" return [\n")
+ (
", \n".join(
" (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
for attrname, visit_name in names
)
+ (
", \n".join(
" (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
for attrname, visit_name in names
)
+ ("\n ]\n")
)
+ ("\n ]\n")
)
@@ -1413,7 +1406,7 @@ log.info(
)
log.info(
f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
f'Skipping: {desc["db_id"]} {foo("bar", x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
)
log.info(
@@ -1441,7 +1434,7 @@ log.info(
)
log.info(
f"""Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
f"""Skipping: {"a" == "b"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)
log.info(
@@ -1449,7 +1442,7 @@ log.info(
)
log.info(
f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"""
f"""Skipping: {"a" == "b"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}"""
)
```

View File

@@ -99,19 +99,18 @@ msg += "This long string should not be split at any point ever since it is just
some_string_inside_a_variable
- + "Some string that is just long enough to cause a split to take"
- " place.............",
+ + "Some string that is just long enough to cause a split to take place.............",
+ + "Some string that is just long enough to cause a split to take place.............",
xyz,
- "Some really long string that needs to get split eventually but I'm running out of"
- " things to say"
- + some_string_inside_a_variable,
+ "Some really long string that needs to get split eventually but I'm running out of things to say"
+ + some_string_inside_a_variable,
+ some_string_inside_a_variable,
)
addition_inside_tuple = (
some_string_inside_a_variable
- + "Some string that is just long enough to cause a split to take"
- " place.............."
+ + "Some string that is just long enough to cause a split to take place.............."
+ + "Some string that is just long enough to cause a split to take place.............."
)
return (
"Hi there. This is areally really reallllly long string that needs to be split!!!"
@@ -132,21 +131,20 @@ msg += "This long string should not be split at any point ever since it is just
str(result)
- == "This long string should be split at some point right close to or around"
- " hereeeeeee"
+ == "This long string should be split at some point right close to or around hereeeeeee"
+ == "This long string should be split at some point right close to or around hereeeeeee"
)
assert (
str(result)
- < "This long string should be split at some point right close to or around"
- " hereeeeee"
+ < "This long string should be split at some point right close to or around hereeeeee"
+ < "This long string should be split at some point right close to or around hereeeeee"
)
assert (
"A format string: %s"
- % "This long string should be split at some point right close to or around"
- " hereeeeeee"
- != result
+ % "This long string should be split at some point right close to or around hereeeeeee"
+ != result
+ % "This long string should be split at some point right close to or around hereeeeeee"
!= result
)
msg += (
"This long string should be wrapped in parens at some point right around hereeeee"
@@ -195,14 +193,14 @@ some_variable = (
)
addition_inside_tuple = (
some_string_inside_a_variable
+ "Some string that is just long enough to cause a split to take place.............",
+ "Some string that is just long enough to cause a split to take place.............",
xyz,
"Some really long string that needs to get split eventually but I'm running out of things to say"
+ some_string_inside_a_variable,
+ some_string_inside_a_variable,
)
addition_inside_tuple = (
some_string_inside_a_variable
+ "Some string that is just long enough to cause a split to take place.............."
+ "Some string that is just long enough to cause a split to take place.............."
)
return (
"Hi there. This is areally really reallllly long string that needs to be split!!!"
@@ -218,16 +216,16 @@ return (
return f"{x}/b/c/d/d/d/dadfjsadjsaidoaisjdsfjaofjdfijaidfjaodfjaoifjodjafojdoajaaaaaaaaaaaa"
assert (
str(result)
== "This long string should be split at some point right close to or around hereeeeeee"
== "This long string should be split at some point right close to or around hereeeeeee"
)
assert (
str(result)
< "This long string should be split at some point right close to or around hereeeeee"
< "This long string should be split at some point right close to or around hereeeeee"
)
assert (
"A format string: %s"
% "This long string should be split at some point right close to or around hereeeeeee"
!= result
% "This long string should be split at some point right close to or around hereeeeeee"
!= result
)
msg += (
"This long string should be wrapped in parens at some point right around hereeeee"

View File

@@ -660,7 +660,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- " on one line at alllll." % "formatting",
+ (
+ "A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll."
+ % "formatting"
+ % "formatting"
+ ),
)
@@ -669,7 +669,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- " one line at alllll." % ("formatting", "string"),
+ (
+ "A long string with {}. This {} is so long that it is ridiculous. It can't fit on one line at alllll."
+ % ("formatting", "string")
+ % ("formatting", "string")
+ ),
)
@@ -688,17 +688,17 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- xxxx.xxxxxxxxxxxxxx(xx),
+ (
+ "xxxxxxxxxx xxxx xx xxxxxx(%x) xx %x xxxx xx xxx %x.xx"
+ % (len(self) + 1, xxxx.xxxxxxxxxx, xxxx.xxxxxxxxxx)
)
+ + (
+ " %.3f (%s) to %.3f (%s).\n"
+ % (
+ xxxx.xxxxxxxxx,
+ xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx),
+ x,
+ xxxx.xxxxxxxxxxxxxx(xx),
+ )
+ % (len(self) + 1, xxxx.xxxxxxxxxx, xxxx.xxxxxxxxxx)
+ )
+ + (
+ " %.3f (%s) to %.3f (%s).\n"
+ % (
+ xxxx.xxxxxxxxx,
+ xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx),
+ x,
+ xxxx.xxxxxxxxxxxxxx(xx),
+ )
)
)
@@ -777,9 +777,9 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- + "xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx"
- .xxxxxx(xxxxxx_xxxxxx_xxx)
+ "xxx xxxxxx xxx xxxxxxxxx.xx xx xxxxxxxx. xxx xxxxxxxxxxxxx.xx xxxxxxx "
+ + "xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx".xxxxxx(
+ xxxxxx_xxxxxx_xxx
+ )
+ + "xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx".xxxxxx(
+ xxxxxx_xxxxxx_xxx
+ )
)
@@ -878,7 +878,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
class A:
class B:
@@ -364,11 +354,8 @@
@@ -364,10 +354,7 @@
def foo():
if not hasattr(module, name):
raise ValueError(
@@ -886,12 +886,10 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- " serialize things like inner classes. Please move the object into"
- " the main module body to use migrations.\nFor more information,"
- " see https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values"
- % (name, module_name, get_docs_version())
+ "Could not find object %s in %s.\nPlease note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.\nFor more information, see https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values"
+ % (name, module_name, get_docs_version())
% (name, module_name, get_docs_version())
)
@@ -382,23 +369,19 @@
class Step(StepBase):
@@ -930,19 +928,10 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- r"for pid in $(ps aux | grep paster | grep -v grep | grep '\-%d' | awk"
- r" '{print $2}'); do kill $pid; done" % (i)
+ r"for pid in $(ps aux | grep paster | grep -v grep | grep '\-%d' | awk '{print $2}'); do kill $pid; done"
+ % (i)
+ % (i)
)
@@ -423,7 +406,7 @@
def G():
assert (
c_float(val[0][0] / val[0][1]).value
- == c_float(value[0][0] / value[0][1]).value
+ == c_float(value[0][0] / value[0][1]).value
), "%s didn't roundtrip" % tag
@@ -432,9 +415,7 @@
assert xxxxxxx_xxxx in [
x.xxxxx.xxxxxx.xxxxx.xxxxxx,
@@ -991,7 +980,13 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
)
# The parens should NOT be removed in this case.
@@ -518,88 +494,78 @@
@@ -513,93 +489,83 @@
temp_msg = (
- f"{f'{humanize_number(pos)}.': <{pound_len+2}} "
+ f"{f'{humanize_number(pos)}.': <{pound_len + 2}} "
f"{balance: <{bal_len + 5}} "
f"<<{author.display_name}>>\n"
)
@@ -1048,14 +1043,14 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$',"
- " 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$',"
- " 'ykangaroo$']"
+ in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
+ in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
)
assert (
str(suffix_arr)
- not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$',"
- " 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$',"
- " 'rykangaroo$', 'ykangaroo$']"
+ not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
+ not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
)
message = (
f"1. Go to Google Developers Console and log in with your Google account."
@@ -1115,7 +1110,13 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
"6. Click on Create Credential at the top."
'7. At the top click the link for "API key".'
"8. No application restrictions are needed. Click Create at the bottom."
@@ -613,55 +579,40 @@
@@ -608,60 +574,45 @@
# It shouldn't matter if the string prefixes are capitalized.
temp_msg = (
- f"{F'{humanize_number(pos)}.': <{pound_len+2}} "
+ f"{f'{humanize_number(pos)}.': <{pound_len + 2}} "
f"{balance: <{bal_len + 5}} "
f"<<{author.display_name}>>\n"
)
@@ -1335,14 +1336,14 @@ func_call_where_string_arg_has_method_call_and_bad_parens(
func_call_where_string_arg_has_old_fmt_and_bad_parens(
(
"A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll."
% "formatting"
% "formatting"
),
)
func_call_where_string_arg_has_old_fmt_and_bad_parens(
(
"A long string with {}. This {} is so long that it is ridiculous. It can't fit on one line at alllll."
% ("formatting", "string")
% ("formatting", "string")
),
)
@@ -1353,17 +1354,17 @@ class A:
xxxx.xxxxxxx.xxxxx(
(
"xxxxxxxxxx xxxx xx xxxxxx(%x) xx %x xxxx xx xxx %x.xx"
% (len(self) + 1, xxxx.xxxxxxxxxx, xxxx.xxxxxxxxxx)
% (len(self) + 1, xxxx.xxxxxxxxxx, xxxx.xxxxxxxxxx)
)
+ (
" %.3f (%s) to %.3f (%s).\n"
% (
xxxx.xxxxxxxxx,
xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx),
x,
xxxx.xxxxxxxxxxxxxx(xx),
)
+ (
" %.3f (%s) to %.3f (%s).\n"
% (
xxxx.xxxxxxxxx,
xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx),
x,
xxxx.xxxxxxxxxxxxxx(xx),
)
)
)
@@ -1413,9 +1414,9 @@ class A:
if True:
xxxxx_xxxxxxxxxxxx(
"xxx xxxxxx xxx xxxxxxxxx.xx xx xxxxxxxx. xxx xxxxxxxxxxxxx.xx xxxxxxx "
+ "xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx".xxxxxx(
xxxxxx_xxxxxx_xxx
)
+ "xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx".xxxxxx(
xxxxxx_xxxxxx_xxx
)
)
@@ -1566,7 +1567,7 @@ class A:
if not hasattr(module, name):
raise ValueError(
"Could not find object %s in %s.\nPlease note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.\nFor more information, see https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values"
% (name, module_name, get_docs_version())
% (name, module_name, get_docs_version())
)
@@ -1604,7 +1605,7 @@ if __name__ == "__main__":
for i in range(4, 8):
cmd = (
r"for pid in $(ps aux | grep paster | grep -v grep | grep '\-%d' | awk '{print $2}'); do kill $pid; done"
% (i)
% (i)
)
@@ -1617,7 +1618,7 @@ def A():
def G():
assert (
c_float(val[0][0] / val[0][1]).value
== c_float(value[0][0] / value[0][1]).value
== c_float(value[0][0] / value[0][1]).value
), "%s didn't roundtrip" % tag
@@ -1700,7 +1701,7 @@ class X:
temp_msg = (
f"{f'{humanize_number(pos)}.': <{pound_len+2}} "
f"{f'{humanize_number(pos)}.': <{pound_len + 2}} "
f"{balance: <{bal_len + 5}} "
f"<<{author.display_name}>>\n"
)
@@ -1737,11 +1738,11 @@ assert str(suffix_arr) > (
)
assert (
str(suffix_arr)
in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
)
assert (
str(suffix_arr)
not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']"
)
message = (
f"1. Go to Google Developers Console and log in with your Google account."
@@ -1785,7 +1786,7 @@ message = (
# It shouldn't matter if the string prefixes are capitalized.
temp_msg = (
f"{F'{humanize_number(pos)}.': <{pound_len+2}} "
f"{f'{humanize_number(pos)}.': <{pound_len + 2}} "
f"{balance: <{bal_len + 5}} "
f"<<{author.display_name}>>\n"
)

View File

@@ -201,7 +201,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """dove
+ coo"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
)
call(
@@ -212,7 +212,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """dove
+coo"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
)
call(
@@ -222,7 +222,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """cow
+ moo"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
"dogsay",
)
@@ -234,7 +234,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """crow
+ caw"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
)
call(
@@ -244,7 +244,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """cat
+ meow"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
{"dog", "say"},
)
@@ -256,7 +256,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """horse
+ neigh"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
)
call(
@@ -267,7 +267,7 @@ this_will_also_become_one_line = ( # comment
+ textwrap.dedent(
+ """pig
+ oink"""
+ % "cowabunga"
+ % "cowabunga"
+ ),
)
textwrap.dedent("""A one-line triple-quoted string.""")
@@ -391,7 +391,7 @@ call(
textwrap.dedent(
"""dove
coo"""
% "cowabunga"
% "cowabunga"
),
)
call(
@@ -400,7 +400,7 @@ call(
textwrap.dedent(
"""dove
coo"""
% "cowabunga"
% "cowabunga"
),
)
call(
@@ -408,7 +408,7 @@ call(
textwrap.dedent(
"""cow
moo"""
% "cowabunga"
% "cowabunga"
),
"dogsay",
)
@@ -418,7 +418,7 @@ call(
textwrap.dedent(
"""crow
caw"""
% "cowabunga"
% "cowabunga"
),
)
call(
@@ -426,7 +426,7 @@ call(
textwrap.dedent(
"""cat
meow"""
% "cowabunga"
% "cowabunga"
),
{"dog", "say"},
)
@@ -436,7 +436,7 @@ call(
textwrap.dedent(
"""horse
neigh"""
% "cowabunga"
% "cowabunga"
),
)
call(
@@ -445,7 +445,7 @@ call(
textwrap.dedent(
"""pig
oink"""
% "cowabunga"
% "cowabunga"
),
)
textwrap.dedent("""A one-line triple-quoted string.""")

View File

@@ -1,354 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_power_op_spacing.py
---
## Input
```python
a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
b = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
c = 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1
d = 1**1 ** 1**1 ** 1**1 ** 1**1 ** 1**1**1 ** 1 ** 1**1 ** 1**1**1**1**1 ** 1 ** 1**1**1 **1**1** 1 ** 1 ** 1
e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟
f = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟
a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
b = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
c = 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0
d = 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0**1.0 ** 1.0 ** 1.0**1.0 ** 1.0**1.0**1.0
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,83 +1,83 @@
a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
b = (
1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
- ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
+ ** 1
)
c = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
d = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟
f = (
𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
- ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
+ ** 𨉟
)
a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
b = (
1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
- ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
+ ** 1.0
)
c = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
d = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
```
## Ruff Output
```python
a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
b = (
1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
)
c = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
d = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟
f = (
𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
)
a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
b = (
1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
)
c = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
d = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
```
## Black Output
```python
a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
b = (
1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
** 1
)
c = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
d = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟
f = (
𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
** 𨉟
)
a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
b = (
1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
** 1.0
)
c = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
d = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
```

View File

@@ -129,15 +129,6 @@ a = (
some_kind_of_table[
some_key # type: ignore # noqa: E501
@@ -79,7 +77,7 @@
# Right side of assignment contains un-nested pairs of inner parens.
some_kind_of_instance.some_kind_of_map[a_key] = (
isinstance(some_var, SomeClass)
- and table.something_and_something != table.something_else
+ and table.something_and_something != table.something_else
) or (
isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing
)
```
## Ruff Output
@@ -222,7 +213,7 @@ xxxxxxxxx_yyy_zzzzzzzz[
# Right side of assignment contains un-nested pairs of inner parens.
some_kind_of_instance.some_kind_of_map[a_key] = (
isinstance(some_var, SomeClass)
and table.something_and_something != table.something_else
and table.something_and_something != table.something_else
) or (
isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing
)

View File

@@ -127,24 +127,6 @@ def foo(a,b) -> tuple[int, int, int,]:
return 2 * a
@@ -45,7 +53,7 @@
def foo() -> (
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+ | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
):
return 2
@@ -64,7 +72,7 @@
c: int,
) -> (
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+ | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
):
return 2
@@ -124,5 +132,9 @@
# this is broken - the trailing comma is transferred to the param list. Fixed in preview
def foo(
@@ -216,7 +198,7 @@ def foo() -> (
def foo() -> (
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
):
return 2
@@ -235,7 +217,7 @@ def foo(
c: int,
) -> (
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
):
return 2

View File

@@ -41,14 +41,12 @@ assert (
```diff
--- Black
+++ Ruff
@@ -1,8 +1,8 @@
importA
@@ -2,7 +2,7 @@
(
()
- << 0
<< 0
- ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525
+ << 0
+ **101234234242352525425252352352525234890264906820496920680926538059059209922523523525
+ **101234234242352525425252352352525234890264906820496920680926538059059209922523523525
) #
assert sort_by_dependency(
@@ -72,8 +70,8 @@ assert (
importA
(
()
<< 0
**101234234242352525425252352352525234890264906820496920680926538059059209922523523525
<< 0
**101234234242352525425252352352525234890264906820496920680926538059059209922523523525
) #
assert sort_by_dependency(

View File

@@ -1,129 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/trailing_comma_optional_parens1.py
---
## Input
```python
if e1234123412341234.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
pass
if x:
if y:
new_id = max(Vegetable.objects.order_by('-id')[0].id,
Mineral.objects.order_by('-id')[0].id) + 1
class X:
def get_help_text(self):
return ngettext(
"Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.",
self.min_length,
) % {'min_length': self.min_length}
class A:
def b(self):
if self.connection.mysql_is_mariadb and (
10,
4,
3,
) < self.connection.mysql_version < (10, 5, 2):
pass
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -11,7 +11,7 @@
Vegetable.objects.order_by("-id")[0].id,
Mineral.objects.order_by("-id")[0].id,
)
- + 1
+ + 1
)
```
## Ruff Output
```python
if e1234123412341234.winerror not in (
_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY,
) or _check_timeout(t):
pass
if x:
if y:
new_id = (
max(
Vegetable.objects.order_by("-id")[0].id,
Mineral.objects.order_by("-id")[0].id,
)
+ 1
)
class X:
def get_help_text(self):
return ngettext(
"Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.",
self.min_length,
) % {"min_length": self.min_length}
class A:
def b(self):
if self.connection.mysql_is_mariadb and (
10,
4,
3,
) < self.connection.mysql_version < (10, 5, 2):
pass
```
## Black Output
```python
if e1234123412341234.winerror not in (
_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY,
) or _check_timeout(t):
pass
if x:
if y:
new_id = (
max(
Vegetable.objects.order_by("-id")[0].id,
Mineral.objects.order_by("-id")[0].id,
)
+ 1
)
class X:
def get_help_text(self):
return ngettext(
"Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.",
self.min_length,
) % {"min_length": self.min_length}
class A:
def b(self):
if self.connection.mysql_is_mariadb and (
10,
4,
3,
) < self.connection.mysql_version < (10, 5, 2):
pass
```

View File

@@ -1,59 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/trailing_comma_optional_parens3.py
---
## Input
```python
if True:
if True:
if True:
return _(
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {"reported_username": reported_username, "report_reason": report_reason}
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -3,6 +3,6 @@
if True:
return _(
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
- + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
+ + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {"reported_username": reported_username, "report_reason": report_reason}
```
## Ruff Output
```python
if True:
if True:
if True:
return _(
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {"reported_username": reported_username, "report_reason": report_reason}
```
## Black Output
```python
if True:
if True:
if True:
return _(
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {"reported_username": reported_username, "report_reason": report_reason}
```

View File

@@ -56,12 +56,7 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
# Example from https://github.com/psf/black/issues/3229
@@ -39,12 +37,10 @@
# https://github.com/psf/black/pull/3370 causes an infinite recursion.
assert (
long_module.long_class.long_func().another_func()
- == long_module.long_class.long_func()["some_key"].another_func(arg1)
+ == long_module.long_class.long_func()["some_key"].another_func(arg1)
@@ -43,8 +41,6 @@
)
# Regression test for https://github.com/psf/black/issues/3414.
@@ -117,7 +112,7 @@ def refresh_token(self, device_family, refresh_token, api_key):
# https://github.com/psf/black/pull/3370 causes an infinite recursion.
assert (
long_module.long_class.long_func().another_func()
== long_module.long_class.long_func()["some_key"].another_func(arg1)
== long_module.long_class.long_func()["some_key"].another_func(arg1)
)
# Regression test for https://github.com/psf/black/issues/3414.

View File

@@ -292,7 +292,7 @@ x6 = (
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
+ 1
).bit_length()
```

View File

@@ -78,20 +78,20 @@ result = await self.request(
result = await (
1
+ f(
1,
2,
3,
)
+ f(
1,
2,
3,
)
)
result = await (
1
+ f(
1,
2,
3,
)
+ f(
1,
2,
3,
)
)
# Optional parentheses.

View File

@@ -430,28 +430,28 @@ if True:
```python
(
aaaaaaaa
+ # trailing operator comment
b # trailing right comment
+ # trailing operator comment
b # trailing right comment
)
(
aaaaaaaa # trailing left comment
+ # trailing operator comment
# leading right comment
b
+ # trailing operator comment
# leading right comment
b
)
(
# leading left most comment
aaaaaaaa
+ # trailing operator comment
# leading b comment
b # trailing b comment
# trailing b ownline comment
+ # trailing second operator comment
# leading c comment
c # trailing c comment
+ # trailing operator comment
# leading b comment
b # trailing b comment
# trailing b ownline comment
+ # trailing second operator comment
# leading c comment
c # trailing c comment
# trailing own line comment
)
@@ -497,24 +497,21 @@ aaaaaaaaaaaaaa + {
# Wraps it in parentheses if it needs to break both left and right
(
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ [bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eee]
+ [bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eee]
) # comment
# But only for expressions that have a statement parent.
not (
aaaaaaaaaaaaaa
+ {
a
for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
}
+ {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}
)
[
a
+ [
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
]
in c
+ [
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
]
in c
]
@@ -527,13 +524,13 @@ not (
if (
aaaaaaaaaaaaaaaaaa
+
# has the child process finished?
bbbbbbbbbbbbbbb
+
# the child process has finished, but the
# transport hasn't been notified yet?
ccccccccccc
+
# has the child process finished?
bbbbbbbbbbbbbbb
+
# the child process has finished, but the
# transport hasn't been notified yet?
ccccccccccc
):
pass
@@ -556,7 +553,7 @@ if (
dddddddddddddddddddd,
eeeeeeeeee,
]
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
@@ -572,13 +569,13 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]
& [
aaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccc,
dddddddddddddddddddd,
eeeeeeeeee,
]
):
pass
@@ -677,9 +674,9 @@ if (
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]
&
# comment
a + b
&
# comment
a + b
):
pass
@@ -696,14 +693,14 @@ for user_id in set(target_user_ids) - {u.user_id for u in updates}:
# Keeps parenthesized left hand sides
(
log(self.price / self.strike)
+ (self.risk_free - self.div_cont + 0.5 * (self.sigma**2)) * self.exp_time
+ (self.risk_free - self.div_cont + 0.5 * (self.sigma**2)) * self.exp_time
) / self.sigmaT
# Stability with end-of-line comments between empty tuples and bin op
x = (
()
- ( #
)
- ( #
)
)
x = (
() - () #
@@ -732,10 +729,10 @@ expected_content = (
</sitemapindex>
"""
# Needs parentheses
% (
self.base_url,
date.today(),
)
% (
self.base_url,
date.today(),
)
)
expected_content = (
@@ -745,12 +742,12 @@ expected_content = (
</sitemap>
</sitemapindex>
"""
%
# Needs parentheses
(
self.base_url,
date.today(),
)
%
# Needs parentheses
(
self.base_url,
date.today(),
)
)
@@ -771,8 +768,8 @@ expected_content = (
</sitemap>
</sitemapindex>
"""
+ sssssssssssssssssssssssssssssssssssssssssooooo
* looooooooooooooooooooooooooooooongggggggggggg
+ sssssssssssssssssssssssssssssssssssssssssooooo
* looooooooooooooooooooooooooooooongggggggggggg
)
call(
@@ -799,10 +796,10 @@ expected_content = (
</sitemap>
</sitemapindex>
"""
% (
# Needs parentheses
self.base_url
)
% (
# Needs parentheses
self.base_url
)
)
# Skip FString content when determining whether to omit optional parentheses or not.0
@@ -810,7 +807,7 @@ expected_content = (
# (Call expressions at the beginning don't count as parenthesized because they don't start with parens).
assert (
format.format_event(spec)
== f'Event("_remove_cookie", {{key:`testkey`,options:{json.dumps(options)}}})'
== f'Event("_remove_cookie", {{key:`testkey`,options:{json.dumps(options)}}})'
)
# Avoid parentheses for this example because it starts with a tuple expression.
assert (
@@ -820,62 +817,62 @@ assert (
rowuses = [
(1 << j) # column ordinal
| (1 << (n + i - j + n - 1)) # NW-SE ordinal
| (1 << (n + 2 * n - 1 + i + j)) # NE-SW ordinal
| (1 << (n + i - j + n - 1)) # NW-SE ordinal
| (1 << (n + 2 * n - 1 + i + j)) # NE-SW ordinal
for j in rangen
]
rowuses = [
(1 << j) # column ordinal
|
# comment
(1 << (n + i - j + n - 1)) # NW-SE ordinal
| (1 << (n + 2 * n - 1 + i + j)) # NE-SW ordinal
|
# comment
(1 << (n + i - j + n - 1)) # NW-SE ordinal
| (1 << (n + 2 * n - 1 + i + j)) # NE-SW ordinal
for j in rangen
]
skip_bytes = (
header.timecnt * 5 # Transition times and types
+ header.typecnt * 6 # Local time type records
+ header.charcnt # Time zone designations
+ header.leapcnt * 8 # Leap second records
+ header.isstdcnt # Standard/wall indicators
+ header.isutcnt # UT/local indicators
+ header.typecnt * 6 # Local time type records
+ header.charcnt # Time zone designations
+ header.leapcnt * 8 # Leap second records
+ header.isstdcnt # Standard/wall indicators
+ header.isutcnt # UT/local indicators
)
if (
(1 + 2) # test
or (3 + 4) # other
or (4 + 5) # more
or (3 + 4) # other
or (4 + 5) # more
):
pass
if (
(1 and 2) # test
+ (3 and 4) # other
+ (4 and 5) # more
+ (3 and 4) # other
+ (4 and 5) # more
):
pass
if (
(1 + 2) # test
< (3 + 4) # other
> (4 + 5) # more
< (3 + 4) # other
> (4 + 5) # more
):
pass
z = (
a
+
# a: extracts this comment
# b: and this comment
(
# c: formats it as part of the expression
x and y
)
+
# a: extracts this comment
# b: and this comment
(
# c: formats it as part of the expression
x and y
)
)
z = (
@@ -885,7 +882,7 @@ z = (
)
# b: extracts this comment
# c: and this comment
+ a
+ a
)
# Test for https://github.com/astral-sh/ruff/issues/7431

View File

@@ -252,8 +252,8 @@ def test():
),
"post_processed": (
collected["post_processed"]
and ", %s post-processed" % post_processed_count
or ""
and ", %s post-processed" % post_processed_count
or ""
),
}

View File

@@ -198,37 +198,37 @@ if (self._proc
if (
self._proc
# has the child process finished?
and self._returncode
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll()
and self._returncode
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll()
):
pass
if (
self._proc
and self._returncode
and self._proc.poll()
and self._proc
and self._returncode
and self._proc.poll()
and self._returncode
and self._proc.poll()
and self._proc
and self._returncode
and self._proc.poll()
):
pass
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas
and aaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaa
):
pass
@@ -254,61 +254,61 @@ if [
# Break right only applies for boolean operations with a left and right side
if (
aaaaaaaaaaaaaaaaaaaaaaaaaa
and bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
and ccccccccccccccccc
and [dddddddddddddd, eeeeeeeeee, fffffffffffffff]
and bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
and ccccccccccccccccc
and [dddddddddddddd, eeeeeeeeee, fffffffffffffff]
):
pass
# Regression test for https://github.com/astral-sh/ruff/issues/6068
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
or numpy
and isinstance(ccccccccccc, dddddd)
or numpy
and isinstance(ccccccccccc, dddddd)
):
pass
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
and numpy
or isinstance(ccccccccccc, dddddd)
and numpy
or isinstance(ccccccccccc, dddddd)
):
pass
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
or xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
and isinstance(ccccccccccc, dddddd)
or xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
and isinstance(ccccccccccc, dddddd)
):
pass
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
and xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
or isinstance(ccccccccccc, dddddd)
and xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
or isinstance(ccccccccccc, dddddd)
):
pass
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
or (
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
)
and isinstance(ccccccccccc, dddddd)
or (
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
)
and isinstance(ccccccccccc, dddddd)
):
pass
if not (
isinstance(aaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbb)
and (
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
)
or isinstance(ccccccccccc, dddddd)
and (
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
)
or isinstance(ccccccccccc, dddddd)
):
pass
@@ -322,8 +322,8 @@ def test():
if "_continue" in request.POST or (
# Redirecting after "Save as new".
"_saveasnew" in request.POST
and self.save_as_continue
and self.has_change_permission(request, obj)
and self.save_as_continue
and self.has_change_permission(request, obj)
):
pass
@@ -333,7 +333,7 @@ if True:
if True:
if (
self.validate_max
and self.total_form_count() - len(self.deleted_forms) > self.max_num
and self.total_form_count() - len(self.deleted_forms) > self.max_num
) or self.management_form.cleaned_data[
TOTAL_FORM_COUNT
] > self.absolute_max:
@@ -343,18 +343,15 @@ if True:
if True:
if (
reference_field_name is None
or
# Unspecified to_field(s).
to_fields is None
or
# Reference to primary key.
(
None in to_fields
and (reference_field is None or reference_field.primary_key)
)
or
# Reference to field.
reference_field_name in to_fields
or
# Unspecified to_field(s).
to_fields is None
or
# Reference to primary key.
(None in to_fields and (reference_field is None or reference_field.primary_key))
or
# Reference to field.
reference_field_name in to_fields
):
pass
@@ -362,9 +359,9 @@ if True:
field = opts.get_field(name)
if (
field.is_relation
and
# Generic foreign keys OR reverse relations
((field.many_to_one and not field.related_model) or field.one_to_many)
and
# Generic foreign keys OR reverse relations
((field.many_to_one and not field.related_model) or field.one_to_many)
):
pass
@@ -372,35 +369,35 @@ if (
if True:
return (
filtered.exists()
and
# It may happen that the object is deleted from the DB right after
# this check, causing the subsequent UPDATE to return zero matching
# rows. The same result can occur in some rare cases when the
# database returns zero despite the UPDATE being executed
# successfully (a row is matched and updated). In order to
# distinguish these two cases, the object's existence in the
# database is again checked for if the UPDATE query returns 0.
(filtered._update(values) > 0 or filtered.exists())
and
# It may happen that the object is deleted from the DB right after
# this check, causing the subsequent UPDATE to return zero matching
# rows. The same result can occur in some rare cases when the
# database returns zero despite the UPDATE being executed
# successfully (a row is matched and updated). In order to
# distinguish these two cases, the object's existence in the
# database is again checked for if the UPDATE query returns 0.
(filtered._update(values) > 0 or filtered.exists())
)
if (
self._proc is not None
# has the child process finished?
and self._returncode is None
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll() is None
# has the child process finished?
and self._returncode is None
# the child process has finished, but the
# transport hasn't been notified yet?
and self._proc.poll() is None
):
pass
if (
self._proc
# has the child process finished?
* self._returncode
# the child process has finished, but the
# transport hasn't been notified yet?
+ self._proc.poll()
* self._returncode
# the child process has finished, but the
# transport hasn't been notified yet?
+ self._proc.poll()
):
pass
```

View File

@@ -320,7 +320,7 @@ f(
100000 - 100000000000
),
these_arguments_have_values_that_need_to_break_because_they_are_too_long2="akshfdlakjsdfad"
+ "asdfasdfa",
+ "asdfasdfa",
these_arguments_have_values_that_need_to_break_because_they_are_too_long3=session,
)
@@ -543,14 +543,14 @@ f( # a
# f
*args2
# g
** # h
kwargs,
** # h
kwargs,
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
+ 1
)()

View File

@@ -201,14 +201,14 @@ a not in b
(
a
==
# comment
b
==
# comment
b
)
(
a # comment
== b
== b
)
a < b > c == d
@@ -270,25 +270,25 @@ return 1 == 2 and (
self_meta_data,
self_schedule,
)
== (
name,
description,
other_default,
othr_selected,
othr_auto_generated,
othr_parameters,
othr_meta_data,
othr_schedule,
)
== (
name,
description,
other_default,
othr_selected,
othr_auto_generated,
othr_parameters,
othr_meta_data,
othr_schedule,
)
)
[
(
a
+ [
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
]
>= c
+ [
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
]
>= c
)
]
@@ -296,7 +296,7 @@ return 1 == 2 and (
def f():
return (
unicodedata.normalize("NFKC", s1).casefold()
== unicodedata.normalize("NFKC", s2).casefold()
== unicodedata.normalize("NFKC", s2).casefold()
)
@@ -336,7 +336,7 @@ ct_match = (aaaaaaaaaaaaaaaa) == self.get_content_type(
ct_match = (
aaaaaaaaaaact_id
== self.get_content_type[obj, rel_obj, using, instance._state.db].id
== self.get_content_type[obj, rel_obj, using, instance._state.db].id
)
ct_match = {aaaaaaaaaaaaaaaa} == self.get_content_type[
@@ -351,10 +351,10 @@ ct_match = (aaaaaaaaaaaaaaaa) == self.get_content_type[
c = (
1 # 1
>
# 2
3 # 3 # 4
> 5 # 5
>
# 2
3 # 3 # 4
> 5 # 5
# 6
)

View File

@@ -224,18 +224,18 @@ query = {
{
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [
dddddddddddddddddd,
eeeeeeeeeeeeeeeeeee,
]: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [
dddddddddddddddddd,
eeeeeeeeeeeeeeeeeee,
]: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [
eeeeeeeeeeeeeeeeeeeeee,
fffffffffffffffffffffffff,
] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd
if fffffffffffffffffffffffffffffffffffffffffff
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
if gggggggggggggggggggggggggggggggggggggggggggg
}

View File

@@ -0,0 +1,54 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring_py312.py
---
## Input
```python
# This file contains test cases only for cases where the logic tests for whether
# the target version is 3.12 or later. A user can have 3.12 syntax even if the target
# version isn't set.
# Quotes re-use
f"{'a'}"
```
## Outputs
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = Py312
source_type = Python
```
```python
# This file contains test cases only for cases where the logic tests for whether
# the target version is 3.12 or later. A user can have 3.12 syntax even if the target
# version isn't set.
# Quotes re-use
f"{'a'}"
```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -3,4 +3,4 @@
# version isn't set.
# Quotes re-use
-f"{'a'}"
+f"{"a"}"
```

View File

@@ -219,9 +219,9 @@ def something():
NamedValuesListIterable
if named
else FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
if flat
else ValuesListIterable
)
@@ -233,9 +233,9 @@ def something():
if named
else (
FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
+ FlatValuesListIterable
if flat
else ValuesListIterable
)

View File

@@ -152,15 +152,15 @@ aaaaaaaaaaaaaaaaaaaaa = [
[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [dddddddddddddddddd, eeeeeeeeeeeeeeeeeee]
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [dddddddddddddddddd, eeeeeeeeeeeeeeeeeee]
for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [
eeeeeeeeeeeeeeeeeeeeee,
fffffffffffffffffffffffff,
] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd
if fffffffffffffffffffffffffffffffffffffffffff
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
if gggggggggggggggggggggggggggggggggggggggggggg
]
@@ -248,7 +248,7 @@ aaaaaaaaaaaaaaaaaaaaa = [
[
1
for components in b # pylint: disable=undefined-loop-variable # integer 1 may only have decimal 01-09
+ c # negative decimal
+ c # negative decimal
]
# Parenthesized targets and iterators.

View File

@@ -100,15 +100,15 @@ selected_choices = {
{
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [dddddddddddddddddd, eeeeeeeeeeeeeeeeeee]
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ [dddddddddddddddddd, eeeeeeeeeeeeeeeeeee]
for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [
eeeeeeeeeeeeeeeeeeeeee,
fffffffffffffffffffffffff,
] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd
if fffffffffffffffffffffffffffffffffffffffffff
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
< gggggggggggggggggggggggggggggggggggggggggggggg
< hhhhhhhhhhhhhhhhhhhhhhhhhh
if gggggggggggggggggggggggggggggggggggggggggggg
}

View File

@@ -217,7 +217,7 @@ g2 = "g"[(1):(2):(3)]
def f():
return (
package_version is not None
and package_version.split(".")[:2] == package_info.version.split(".")[:2]
and package_version.split(".")[:2] == package_info.version.split(".")[:2]
)

View File

@@ -101,12 +101,12 @@ response = await sync_to_async(
# Expressions with empty parentheses.
ct_match = (
unicodedata.normalize("NFKC", s1).casefold()
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = (
unicodedata.normalize("NFKC", s1).casefold(1)
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = unicodedata.normalize("NFKC", s1).casefold(0) == unicodedata.normalize(
@@ -121,19 +121,19 @@ ct_match = (
unicodedata.normalize("NFKC", s1).casefold(
# foo
)
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold(
# foo
)
== unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold(
# foo
)
)
ct_match = (
[].unicodedata.normalize("NFKC", s1).casefold()
== [].unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== [].unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = (
[].unicodedata.normalize("NFKC", s1).casefold()
== [1].unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== [1].unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = [1].unicodedata.normalize("NFKC", s1).casefold() == [].unicodedata.normalize(
@@ -146,12 +146,12 @@ ct_match = [1].unicodedata.normalize("NFKC", s1).casefold() == [
ct_match = (
{}.unicodedata.normalize("NFKC", s1).casefold()
== {}.unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== {}.unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = (
{}.unicodedata.normalize("NFKC", s1).casefold()
== {1}.unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
== {1}.unicodedata.normalize("NFKCNFKCNFKCNFKCNFKC", s2).casefold()
)
ct_match = {1}.unicodedata.normalize("NFKC", s1).casefold() == {}.unicodedata.normalize(

View File

@@ -16,7 +16,7 @@ result = (
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
+ 1
)[0]
```

View File

@@ -205,7 +205,7 @@ def foo():
```python
if (
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -221,7 +221,7 @@ b = 10
if not (
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -229,14 +229,14 @@ if not (
if ~(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if -(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -244,14 +244,14 @@ if -(
if +(
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -259,14 +259,14 @@ if (
if (
# comment
~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -274,7 +274,7 @@ if (
if (
# comment
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -286,21 +286,21 @@ if (
not (
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
):
pass
if not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (
not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
):
pass
@@ -308,9 +308,9 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (
if (
not (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
@@ -319,7 +319,7 @@ if (
if ( # comment
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -327,14 +327,14 @@ if ( # comment
if (
# comment
~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -342,7 +342,7 @@ if (
if (
# comment
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -355,8 +355,8 @@ if not a:
# Regression test for: https://github.com/astral-sh/ruff/issues/5338
if (
a
and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
@@ -377,7 +377,7 @@ if True:
if True:
if not yn_question(
Fore.RED
+ "WARNING: Removing listed files. Do you really want to continue. yes/n)? "
+ "WARNING: Removing listed files. Do you really want to continue. yes/n)? "
):
pass

View File

@@ -154,7 +154,7 @@ aaaaaaaa = (
aaaaaaaa = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)

View File

@@ -195,8 +195,8 @@ def foo():
yield (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ ccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ ccccccccccccccccccccccccccccccccccccccccccccccccccccccc
)
@@ -259,20 +259,20 @@ result = yield
result = yield (
1
+ f(
1,
2,
3,
)
+ f(
1,
2,
3,
)
)
result = yield (
1
+ f(
1,
2,
3,
)
+ f(
1,
2,
3,
)
)
print((yield x))

View File

@@ -78,7 +78,7 @@ call(
textwrap.dedent(
"""dove
coo"""
% "cowabunga"
% "cowabunga"
),
)

View File

@@ -298,13 +298,13 @@ c1 = (
).filter(
entry__pub_date__year=2008,
)
+ Blog.objects.filter(
entry__headline__contains="McCartney",
)
.limit_results[:10]
.filter(
entry__pub_date__year=2010,
)
+ Blog.objects.filter(
entry__headline__contains="McCartney",
)
.limit_results[:10]
.filter(
entry__pub_date__year=2010,
)
).all()
# Test different cases with trailing end of line comments:
@@ -378,10 +378,10 @@ if (
pass
h2 = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ ccccccccccccccccccccccccc()
.dddddddddddddddddddddd()
.eeeeeeeeee()
.ffffffffffffffffffffff()
+ ccccccccccccccccccccccccc()
.dddddddddddddddddddddd()
.eeeeeeeeee()
.ffffffffffffffffffffff()
)
# Parentheses aren't allowed on statement level, don't use fluent style here

View File

@@ -87,12 +87,12 @@ call(
a = (
a
+ b
+ c
+ d
+ ( # Hello
e + f + g
)
+ b
+ c
+ d
+ ( # Hello
e + f + g
)
)
a = int( # type: ignore
@@ -106,23 +106,23 @@ a = int( # type: ignore
# Stability and correctness checks
b1 = (
()
- ( #
)
- ( #
)
)
(
()
- ( #
)
- ( #
)
)
b2 = (
()
- f( #
)
- f( #
)
)
(
()
- f( #
)
- f( #
)
)
b3 = (
#

View File

@@ -145,7 +145,7 @@ def f():
)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
cccccccc.ccccccccccccc.cccccccc
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
)
self._cache: dict[
@@ -211,7 +211,7 @@ def f():
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
cccccccc.ccccccccccccc(d).cccccccc + e
@@ -57,9 +56,9 @@
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
)
- self._cache: dict[
@@ -300,7 +300,7 @@ def f():
)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
cccccccc.ccccccccccccc.cccccccc
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
)
self._cache: dict[DependencyCacheKey, list[list[DependencyPackage]]] = (

View File

@@ -24,8 +24,8 @@ def format_range_after_inserted_parens ():
def needs_parentheses( ) -> bool:
return (
item.sizing_mode is None
and item.width_policy == "auto"
and item.height_policy == "automatic"
and item.width_policy == "auto"
and item.height_policy == "automatic"
)
def no_longer_needs_parentheses( ) -> bool:

View File

@@ -223,7 +223,7 @@ def test():
key8: value8,
key9: value9,
}
== expected
== expected
), "Not what we expected and the message is too long to fit ineeeeee one line"
assert (
@@ -238,7 +238,7 @@ def test():
key8: value8,
key9: value9,
}
== expected
== expected
), "Not what we expected and the message is too long to fit in one lineeeee"
assert (
@@ -253,7 +253,7 @@ def test():
key8: value8,
key9: value9,
}
== expected
== expected
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
assert (
@@ -268,7 +268,7 @@ def test():
key8: value8,
key9: value9,
}
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
), "Not what we expected and the message is too long to fit in one lin"
assert (
@@ -283,7 +283,7 @@ def test():
key8: value8,
key9: value9,
}
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeee"
assert expected == {
@@ -300,47 +300,47 @@ def test():
assert (
expected
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeeeeee"
assert (
expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
), "Not what we expected and the message is too long to fit in one lin"
assert (
expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
== {
key1: value1,
key2: value2,
key3: value3,
key4: value4,
key5: value5,
key6: value6,
key7: value7,
key8: value8,
key9: value9,
}
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeee"

View File

@@ -151,7 +151,7 @@ def main() -> None:
some_very_long_variable_name_abcdefghijk = (
some_very_long_variable_name_abcdefghijk[
some_very_long_variable_name_abcdefghijk.some_very_long_attribute_name
== "This is a very long string abcdefghijk"
== "This is a very long string abcdefghijk"
]
)

View File

@@ -17,7 +17,7 @@ tree_depth += 1
greeting += (
"This is very long, formal greeting for whomever is name here. Dear %s, it will break the line"
% len(name)
% len(name)
)
```

View File

@@ -270,10 +270,10 @@ class Test((Aaaa)):
class Test(
aaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccc
+ dddddddddddddddddddddd
+ eeeeeeeee,
+ bbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccc
+ dddddddddddddddddddddd
+ eeeeeeeee,
ffffffffffffffffff,
gggggggggggggggggg,
):
@@ -282,9 +282,9 @@ class Test(
class Test(
aaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccc
+ dddddddddddddddddddddd
+ eeeeeeeee,
+ bbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccc
+ dddddddddddddddddddddd
+ eeeeeeeee,
ffffffffffffffffff,
gggggggggggggggggg,
):

View File

@@ -505,7 +505,7 @@ def kwarg_with_leading_comments(
def argument_with_long_default(
a,
b=ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ [dddddddddddddddddddd, eeeeeeeeeeeeeeeeeeee, ffffffffffffffffffffffff],
+ [dddddddddddddddddddd, eeeeeeeeeeeeeeeeeeee, ffffffffffffffffffffffff],
h=[],
):
...
@@ -514,8 +514,8 @@ def argument_with_long_default(
def argument_with_long_type_annotation(
a,
b: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
| yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
| zzzzzzzzzzzzzzzzzzz = [0, 1, 2, 3],
| yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
| zzzzzzzzzzzzzzzzzzz = [0, 1, 2, 3],
h=[],
):
...
@@ -1064,7 +1064,7 @@ def function_with_one_argument_and_a_keyword_separator(
def argument_with_long_default(
@@ -75,8 +71,7 @@
b=ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ [dddddddddddddddddddd, eeeeeeeeeeeeeeeeeeee, ffffffffffffffffffffffff],
+ [dddddddddddddddddddd, eeeeeeeeeeeeeeeeeeee, ffffffffffffffffffffffff],
h=[],
-):
- ...
@@ -1073,8 +1073,8 @@ def function_with_one_argument_and_a_keyword_separator(
def argument_with_long_type_annotation(
@@ -85,12 +80,10 @@
| yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
| zzzzzzzzzzzzzzzzzzz = [0, 1, 2, 3],
| yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
| zzzzzzzzzzzzzzzzzzz = [0, 1, 2, 3],
h=[],
-):
- ...

View File

@@ -181,17 +181,17 @@ x6: VeryLongClassNameWithAwkwardGenericSubtype[
x7: CustomTrainingJob | CustomContainerTrainingJob | CustomPythonPackageTrainingJob
x8: (
None
| datasets.ImageDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset
| datasets.ImageDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset
) = None
x9: None | (
datasets.ImageDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset
) = None
@@ -199,11 +199,11 @@ x10: (
aaaaaaaaaaaaaaaaaaaaaaaa[
bbbbbbbbbbb,
Subscript
| None
| datasets.ImageDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset,
| None
| datasets.ImageDataset
| datasets.TabularDataset
| datasets.TextDataset
| datasets.VideoDataset,
],
bbb[other],
) = None
@@ -334,13 +334,13 @@ nested_comment: None | [
-] | Other | More | AndMore | None = None
+x1: (
+ A[b]
+ | EventHandler
+ | EventSpec
+ | list[EventHandler | EventSpec]
+ | Other
+ | More
+ | AndMore
+ | None
+ | EventHandler
+ | EventSpec
+ | list[EventHandler | EventSpec]
+ | Other
+ | More
+ | AndMore
+ | None
+) = None
-x2: "VeryLongClassNameWithAwkwardGenericSubtype[int] |" "VeryLongClassNameWithAwkwardGenericSubtype[str]"
@@ -363,13 +363,13 @@ nested_comment: None | [
-] | Other = None
+x12: (
+ None
+ | [
+ datasets.ImageDataset,
+ datasets.TabularDataset,
+ datasets.TextDataset,
+ datasets.VideoDataset,
+ ]
+ | Other
+ | [
+ datasets.ImageDataset,
+ datasets.TabularDataset,
+ datasets.TextDataset,
+ datasets.VideoDataset,
+ ]
+ | Other
+) = None
@@ -396,13 +396,13 @@ nested_comment: None | [
+ datasets.TextDataset,
+ datasets.VideoDataset,
+ ]
+ | [
+ datasets.ImageDataset,
+ datasets.TabularDataset,
+ datasets.TextDataset,
+ datasets.VideoDataset,
+ ]
+ | Other
+ | [
+ datasets.ImageDataset,
+ datasets.TabularDataset,
+ datasets.TextDataset,
+ datasets.VideoDataset,
+ ]
+ | Other
+) = None
-x16: None | Literal[
@@ -416,15 +416,15 @@ nested_comment: None | [
-] = None
+x16: (
+ None
+ | Literal[
+ "split",
+ "a bit longer",
+ "records",
+ "index",
+ "table",
+ "columns",
+ "values",
+ ]
+ | Literal[
+ "split",
+ "a bit longer",
+ "records",
+ "index",
+ "table",
+ "columns",
+ "values",
+ ]
+) = None
x17: None | [

View File

@@ -137,29 +137,29 @@ raise OsError(
raise (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd
)
raise (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ (cccccccccccccccccccccc + ddddddddddddddddddddddddd)
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ (cccccccccccccccccccccc + ddddddddddddddddddddddddd)
)
raise (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd
+ bbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd
)
raise ( # hey
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Holala
+ bbbbbbbbbbbbbbbbbbbbbbbbbb # stay
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd # where I'm going
+ bbbbbbbbbbbbbbbbbbbbbbbbbb # stay
+ cccccccccccccccccccccc
+ ddddddddddddddddddddddddd # where I'm going
# I don't know
) # whaaaaat
# the end
@@ -180,13 +180,13 @@ raise aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflak
raise (
aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
)
raise aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfk < (
aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajl
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashd
< aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashd
) # the other end
# sneaky comment

View File

@@ -76,9 +76,9 @@ return (
def f():
return (
self.get_filename()
+ ".csv"
+ "text/csv"
+ output.getvalue().encode("utf-8----------------"),
+ ".csv"
+ "text/csv"
+ output.getvalue().encode("utf-8----------------"),
)
@@ -100,9 +100,9 @@ def f():
def f():
return (
self.get_filename()
+ ".csv"
+ "text/csv"
+ output.getvalue().encode("utf-8----------------"),
+ ".csv"
+ "text/csv"
+ output.getvalue().encode("utf-8----------------"),
)

Some files were not shown because too many files have changed in this diff Show More