Compare commits
8 Commits
preview-bi
...
v0.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235cfb7976 | ||
|
|
91ae81b565 | ||
|
|
d46c5d8ac8 | ||
|
|
20217e9bbd | ||
|
|
72bf1c2880 | ||
|
|
c47ff658e4 | ||
|
|
c3bba54b6b | ||
|
|
fe79798c12 |
61
CHANGELOG.md
61
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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
6
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
2
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_2.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/ruff/RUF027_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# 测试eval函数,eval()函数用来执行一个字符串表达式,并返t表达式的值。另外,可以讲字符串转换成列表或元组或字典
|
||||
a = "{1: 'a', 2: 'b'}"
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -256,8 +256,6 @@ impl Rule {
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||
| Rule::AmbiguousUnicodeCharacterString
|
||||
| Rule::AvoidableEscapedQuote
|
||||
| Rule::BadQuotesDocstring
|
||||
| Rule::BadQuotesInlineString
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
|
||||
8
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.options.json
vendored
Normal file
8
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.options.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"preview": "enabled"
|
||||
},
|
||||
{
|
||||
"preview": "disabled"
|
||||
}
|
||||
]
|
||||
@@ -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'
|
||||
]
|
||||
} --------
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"target_version": "py312"
|
||||
}
|
||||
]
|
||||
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring_py312.py
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring_py312.py
vendored
Normal 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'}"
|
||||
@@ -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),
|
||||
|
||||
@@ -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(_)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
244
crates/ruff_python_formatter/src/other/f_string_element.rs
Normal file
244
crates/ruff_python_formatter/src/other/f_string_element.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
212
crates/ruff_python_formatter/src/string/any.rs
Normal file
212
crates/ruff_python_formatter/src/string/any.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
655
crates/ruff_python_formatter/src/string/normalize.rs
Normal file
655
crates/ruff_python_formatter/src/string/normalize.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
```
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
) #
|
||||
```
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"]}"""
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.""")
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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}
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -292,7 +292,7 @@ x6 = (
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
+ 1
|
||||
).bit_length()
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
)()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"}"
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -16,7 +16,7 @@ result = (
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
+ 1
|
||||
)[0]
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ aaaaaaaa = (
|
||||
|
||||
aaaaaaaa = (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -78,7 +78,7 @@ call(
|
||||
textwrap.dedent(
|
||||
"""dove
|
||||
coo"""
|
||||
% "cowabunga"
|
||||
% "cowabunga"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = (
|
||||
#
|
||||
|
||||
@@ -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]]] = (
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -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,
|
||||
):
|
||||
|
||||
@@ -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=[],
|
||||
-):
|
||||
- ...
|
||||
|
||||
@@ -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 | [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user