Compare commits
32 Commits
charlie/ex
...
preview-bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3e160dcb7 | ||
|
|
003851b54c | ||
|
|
bb8d2034e2 | ||
|
|
f40e012b4e | ||
|
|
3e9d761b13 | ||
|
|
46db3f96ac | ||
|
|
6f9c128d77 | ||
|
|
6380c90031 | ||
|
|
d96a0dbe57 | ||
|
|
180920fdd9 | ||
|
|
1ccd8354c1 | ||
|
|
dd0ba16a79 | ||
|
|
609d0a9a65 | ||
|
|
8fba97f72f | ||
|
|
5bc0d9c324 | ||
|
|
cf77eeb913 | ||
|
|
3f4dd01e7a | ||
|
|
edfe8421ec | ||
|
|
ab2253db03 | ||
|
|
33ac2867b7 | ||
|
|
0304623878 | ||
|
|
e2785f3fb6 | ||
|
|
90f8e4baf4 | ||
|
|
8657a392ff | ||
|
|
4946a1876f | ||
|
|
6dc1b21917 | ||
|
|
2e1160e74c | ||
|
|
37ff436e4e | ||
|
|
341c2698a7 | ||
|
|
a50e2787df | ||
|
|
25868d0371 | ||
|
|
af2cba7c0a |
8
.config/nextest.toml
Normal file
8
.config/nextest.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[profile.ci]
|
||||
# Print out output for failing tests as soon as they fail, and also at the end
|
||||
# of the run (for easy scrollability).
|
||||
failure-output = "immediate-final"
|
||||
# Do not cancel the test run on the first failure.
|
||||
fail-fast = false
|
||||
|
||||
status-level = "skip"
|
||||
23
.github/workflows/ci.yaml
vendored
23
.github/workflows/ci.yaml
vendored
@@ -111,13 +111,23 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
@@ -138,15 +148,16 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo insta"
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
tool: cargo-nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||
run: cargo insta test --all --exclude ruff_dev --all-features
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
|
||||
cargo-test-wasm:
|
||||
name: "cargo test (wasm)"
|
||||
@@ -407,7 +418,7 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
|
||||
@@ -26,6 +26,10 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
- [`cargo dev`](#cargo-dev)
|
||||
- [Subsystems](#subsystems)
|
||||
- [Compilation Pipeline](#compilation-pipeline)
|
||||
- [Import Categorization](#import-categorization)
|
||||
- [Project root](#project-root)
|
||||
- [Package root](#package-root)
|
||||
- [Import categorization](#import-categorization-1)
|
||||
|
||||
## The Basics
|
||||
|
||||
@@ -63,7 +67,7 @@ You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
||||
cargo install cargo-insta
|
||||
```
|
||||
|
||||
and pre-commit to run some validation checks:
|
||||
And you'll need pre-commit to run some validation checks:
|
||||
|
||||
```shell
|
||||
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
||||
@@ -76,6 +80,16 @@ when making a commit:
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
|
||||
though it's not strictly necessary:
|
||||
|
||||
```shell
|
||||
cargo install cargo-nextest --locked
|
||||
```
|
||||
|
||||
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
|
||||
if you choose to install `nextest`.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run Ruff locally from the repository root with:
|
||||
@@ -373,6 +387,11 @@ We have several ways of benchmarking and profiling Ruff:
|
||||
- Microbenchmarks which run the linter or the formatter on individual files. These run on pull requests.
|
||||
- Profiling the linter on either the microbenchmarks or entire projects
|
||||
|
||||
> \[!NOTE\]
|
||||
> When running benchmarks, ensure that your CPU is otherwise idle (e.g., close any background
|
||||
> applications, like web browsers). You may also want to switch your CPU to a "performance"
|
||||
> mode, if it exists, especially when benchmarking short-lived processes.
|
||||
|
||||
### CPython Benchmark
|
||||
|
||||
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -273,9 +273,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.33"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -1037,9 +1037,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.7"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
|
||||
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
|
||||
dependencies = [
|
||||
"console",
|
||||
"instant",
|
||||
@@ -2944,18 +2944,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -21,7 +21,7 @@ bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
|
||||
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
@@ -44,7 +44,7 @@ hexf-parse = { version ="0.2.1"}
|
||||
ignore = { version = "0.4.22" }
|
||||
imara-diff ={ version = "0.1.5"}
|
||||
imperative = { version = "1.0.4" }
|
||||
indicatif ={ version = "0.17.7"}
|
||||
indicatif ={ version = "0.17.8"}
|
||||
indoc ={ version = "2.0.4"}
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
@@ -92,7 +92,7 @@ strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
tempfile = { version ="3.9.0"}
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.51" }
|
||||
thiserror = { version = "1.0.57" }
|
||||
tikv-jemallocator = { version ="0.5.0"}
|
||||
toml = { version = "0.8.9" }
|
||||
tracing = { version = "0.1.40" }
|
||||
|
||||
@@ -48,6 +48,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
strum = { workspace = true, features = [] }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fs::{self, File};
|
||||
use std::hash::Hasher;
|
||||
use std::io::{self, BufReader, BufWriter, Write};
|
||||
use std::io::{self, BufReader, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Mutex;
|
||||
@@ -15,6 +15,7 @@ use rayon::iter::ParallelIterator;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||
@@ -165,15 +166,29 @@ impl Cache {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = File::create(&self.path)
|
||||
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
|
||||
let writer = BufWriter::new(file);
|
||||
bincode::serialize_into(writer, &self.package).with_context(|| {
|
||||
// Write the cache to a temporary file first and then rename it for an "atomic" write.
|
||||
// Protects against data loss if the process is killed during the write and races between different ruff
|
||||
// processes, resulting in a corrupted cache file. https://github.com/astral-sh/ruff/issues/8147#issuecomment-1943345964
|
||||
let mut temp_file =
|
||||
NamedTempFile::new_in(self.path.parent().expect("Write path must have a parent"))
|
||||
.context("Failed to create temporary file")?;
|
||||
|
||||
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
|
||||
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
|
||||
let serialized =
|
||||
bincode::serialize(&self.package).context("Failed to serialize cache data")?;
|
||||
temp_file
|
||||
.write_all(&serialized)
|
||||
.context("Failed to write serialized cache to temporary file.")?;
|
||||
|
||||
temp_file.persist(&self.path).with_context(|| {
|
||||
format!(
|
||||
"Failed to serialise cache to file '{}'",
|
||||
"Failed to rename temporary cache file to {}",
|
||||
self.path.display()
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies the pending changes without storing the cache to disk.
|
||||
|
||||
@@ -25,6 +25,15 @@ import cycles. They also increase the cognitive load of reading the code.
|
||||
If an import statement is used to check for the availability or existence
|
||||
of a module, consider using `importlib.util.find_spec` instead.
|
||||
|
||||
If an import statement is used to re-export a symbol as part of a module's
|
||||
public interface, consider using a "redundant" import alias, which
|
||||
instructs Ruff (and other tools) to respect the re-export, and avoid
|
||||
marking it as unused, as in:
|
||||
|
||||
```python
|
||||
from module import member as member
|
||||
```
|
||||
|
||||
## Example
|
||||
```python
|
||||
import numpy as np # unused import
|
||||
@@ -51,11 +60,12 @@ else:
|
||||
```
|
||||
|
||||
## Options
|
||||
- `lint.pyflakes.extend-generics`
|
||||
- `lint.ignore-init-module-imports`
|
||||
|
||||
## References
|
||||
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
||||
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
||||
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
doctest = false
|
||||
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::format_element::PrintMode;
|
||||
use crate::{GroupId, TextSize};
|
||||
use std::cell::Cell;
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use crate::format_element::PrintMode;
|
||||
use crate::{GroupId, TextSize};
|
||||
|
||||
/// A Tag marking the start and end of some content to which some special formatting should be applied.
|
||||
///
|
||||
/// Tags always come in pairs of a start and an end tag and the styling defined by this tag
|
||||
@@ -99,6 +100,10 @@ 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!(
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
|
||||
@@ -11,13 +11,25 @@ class _UnusedTypedDict2(typing.TypedDict):
|
||||
|
||||
|
||||
class _UsedTypedDict(TypedDict):
|
||||
foo: bytes
|
||||
foo: bytes
|
||||
|
||||
|
||||
class _CustomClass(_UsedTypedDict):
|
||||
bar: list[int]
|
||||
|
||||
|
||||
_UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
||||
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||
|
||||
|
||||
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
||||
|
||||
|
||||
# In `.py` files, we don't flag unused definitions in class scopes (unlike in `.pyi`
|
||||
# files).
|
||||
class _CustomClass3:
|
||||
class _UnusedTypeDict4(TypedDict):
|
||||
pass
|
||||
|
||||
def method(self) -> None:
|
||||
_CustomClass3._UnusedTypeDict4()
|
||||
|
||||
@@ -35,3 +35,13 @@ _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
||||
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||
|
||||
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
||||
|
||||
|
||||
# In `.pyi` files, we flag unused definitions in class scopes as well as in the global
|
||||
# scope (unlike in `.py` files).
|
||||
class _CustomClass3:
|
||||
class _UnusedTypeDict4(TypedDict):
|
||||
pass
|
||||
|
||||
def method(self) -> None:
|
||||
_CustomClass3._UnusedTypeDict4()
|
||||
|
||||
@@ -36,35 +36,47 @@ for i in list( # Comment
|
||||
): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_dict): # Ok
|
||||
for i in list(foo_dict): # OK
|
||||
pass
|
||||
|
||||
for i in list(1): # Ok
|
||||
for i in list(1): # OK
|
||||
pass
|
||||
|
||||
for i in list(foo_int): # Ok
|
||||
for i in list(foo_int): # OK
|
||||
pass
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
for i in itertools.product(foo_int): # Ok
|
||||
for i in itertools.product(foo_int): # OK
|
||||
pass
|
||||
|
||||
for i in list(foo_list): # Ok
|
||||
for i in list(foo_list): # OK
|
||||
foo_list.append(i + 1)
|
||||
|
||||
for i in list(foo_list): # PERF101
|
||||
# Make sure we match the correct list
|
||||
other_list.append(i + 1)
|
||||
|
||||
for i in list(foo_tuple): # Ok
|
||||
for i in list(foo_tuple): # OK
|
||||
foo_tuple.append(i + 1)
|
||||
|
||||
for i in list(foo_set): # Ok
|
||||
for i in list(foo_set): # OK
|
||||
foo_set.append(i + 1)
|
||||
|
||||
x, y, nested_tuple = (1, 2, (3, 4, 5))
|
||||
|
||||
for i in list(nested_tuple): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_list): # OK
|
||||
if True:
|
||||
foo_list.append(i + 1)
|
||||
|
||||
for i in list(foo_list): # OK
|
||||
if True:
|
||||
foo_list[i] = i + 1
|
||||
|
||||
for i in list(foo_list): # OK
|
||||
if True:
|
||||
del foo_list[i + 1]
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
1 in (
|
||||
1, 2, 3
|
||||
)
|
||||
|
||||
# OK
|
||||
fruits = ["cherry", "grapes"]
|
||||
"cherry" in fruits
|
||||
_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in ("a", "b")}
|
||||
|
||||
# OK
|
||||
fruits in [[1, 2, 3], [4, 5, 6]]
|
||||
fruits in [1, 2, 3]
|
||||
1 in [[1, 2, 3], [4, 5, 6]]
|
||||
_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in (["a", "b"], ["c", "d"])}
|
||||
|
||||
@@ -35,6 +35,15 @@ if argc != 0: # correct
|
||||
if argc != 1: # correct
|
||||
pass
|
||||
|
||||
if argc != -1.0: # correct
|
||||
pass
|
||||
|
||||
if argc != 0.0: # correct
|
||||
pass
|
||||
|
||||
if argc != 1.0: # correct
|
||||
pass
|
||||
|
||||
if argc != 2: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
@@ -44,6 +53,12 @@ if argc != -2: # [magic-value-comparison]
|
||||
if argc != +2: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if argc != -2.0: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if argc != +2.0: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if __name__ == "__main__": # correct
|
||||
pass
|
||||
|
||||
|
||||
75
crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py
vendored
Normal file
75
crates/ruff_linter/resources/test/fixtures/refurb/FURB129.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import codecs
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
# Errors
|
||||
with open("FURB129.py") as f:
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
a = [line.lower() for line in f.readlines()]
|
||||
b = {line.upper() for line in f.readlines()}
|
||||
c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
|
||||
with Path("FURB129.py").open() as f:
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
|
||||
for _line in open("FURB129.py").readlines():
|
||||
pass
|
||||
|
||||
for _line in Path("FURB129.py").open().readlines():
|
||||
pass
|
||||
|
||||
|
||||
def func():
|
||||
f = Path("FURB129.py").open()
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
f.close()
|
||||
|
||||
|
||||
def func(f: io.BytesIO):
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
|
||||
|
||||
def func():
|
||||
with (open("FURB129.py") as f, foo as bar):
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
for _line in bar.readlines():
|
||||
pass
|
||||
|
||||
|
||||
# False positives
|
||||
def func(f):
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
|
||||
|
||||
def func(f: codecs.StreamReader):
|
||||
for _line in f.readlines():
|
||||
pass
|
||||
|
||||
|
||||
def func():
|
||||
class A:
|
||||
def readlines(self) -> list[str]:
|
||||
return ["a", "b", "c"]
|
||||
|
||||
return A()
|
||||
|
||||
|
||||
for _line in func().readlines():
|
||||
pass
|
||||
|
||||
# OK
|
||||
for _line in ["a", "b", "c"]:
|
||||
pass
|
||||
with open("FURB129.py") as f:
|
||||
for _line in f:
|
||||
pass
|
||||
for _line in f.readlines(10):
|
||||
pass
|
||||
for _not_line in f.readline():
|
||||
pass
|
||||
@@ -162,3 +162,26 @@ async def f(x: bool):
|
||||
T = asyncio.create_task(asyncio.sleep(1))
|
||||
else:
|
||||
T = None
|
||||
|
||||
|
||||
# Error
|
||||
def f():
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.create_task(main()) # Error
|
||||
|
||||
# Error
|
||||
def f():
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(main()) # Error
|
||||
|
||||
# OK
|
||||
def f():
|
||||
global task
|
||||
loop = asyncio.new_event_loop()
|
||||
task = loop.create_task(main()) # Error
|
||||
|
||||
# OK
|
||||
def f():
|
||||
global task
|
||||
loop = asyncio.get_event_loop()
|
||||
task = loop.create_task(main()) # Error
|
||||
|
||||
@@ -2,11 +2,14 @@ use ruff_python_ast::Comprehension;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::flake8_simplify;
|
||||
use crate::rules::{flake8_simplify, refurb};
|
||||
|
||||
/// Run lint rules over a [`Comprehension`] syntax nodes.
|
||||
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
|
||||
}
|
||||
if checker.enabled(Rule::ReadlinesInFor) {
|
||||
refurb::rules::readlines_in_comprehension(checker, comprehension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,17 +281,21 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
if checker.source_type.is_stub()
|
||||
|| matches!(scope.kind, ScopeKind::Module | ScopeKind::Function(_))
|
||||
{
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::AsyncioDanglingTask) {
|
||||
|
||||
@@ -1317,6 +1317,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup(checker, for_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::ReadlinesInFor) {
|
||||
refurb::rules::readlines_in_for(checker, for_stmt);
|
||||
}
|
||||
if !is_async {
|
||||
if checker.enabled(Rule::ReimplementedBuiltin) {
|
||||
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, to_module_path,
|
||||
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
|
||||
};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::str::trailing_quote;
|
||||
@@ -71,6 +71,38 @@ mod analyze;
|
||||
mod annotation;
|
||||
mod deferred;
|
||||
|
||||
/// State representing whether a docstring is expected or not for the next statement.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
||||
enum DocstringState {
|
||||
/// The next statement is expected to be a docstring, but not necessarily so.
|
||||
///
|
||||
/// For example, in the following code:
|
||||
///
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// def bar(x, y):
|
||||
/// """Docstring."""
|
||||
/// return x + y
|
||||
/// ```
|
||||
///
|
||||
/// For `Foo`, the state is expected when the checker is visiting the class
|
||||
/// body but isn't going to be present. While, for `bar` function, the docstring
|
||||
/// is expected and present.
|
||||
#[default]
|
||||
Expected,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl DocstringState {
|
||||
/// Returns `true` if the next statement is expected to be a docstring.
|
||||
const fn is_expected(self) -> bool {
|
||||
matches!(self, DocstringState::Expected)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Checker<'a> {
|
||||
/// The [`Path`] to the file under analysis.
|
||||
path: &'a Path,
|
||||
@@ -114,6 +146,8 @@ pub(crate) struct Checker<'a> {
|
||||
pub(crate) flake8_bugbear_seen: Vec<TextRange>,
|
||||
/// The end offset of the last visited statement.
|
||||
last_stmt_end: TextSize,
|
||||
/// A state describing if a docstring is expected or not.
|
||||
docstring_state: DocstringState,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -153,6 +187,7 @@ impl<'a> Checker<'a> {
|
||||
cell_offsets,
|
||||
notebook_index,
|
||||
last_stmt_end: TextSize::default(),
|
||||
docstring_state: DocstringState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,19 +340,16 @@ where
|
||||
self.semantic.flags -= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
// Track whether we've seen module docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
Stmt::Expr(ast::StmtExpr { value, .. })
|
||||
if !self
|
||||
.semantic
|
||||
.flags
|
||||
.intersects(SemanticModelFlags::MODULE_DOCSTRING)
|
||||
if !self.semantic.seen_module_docstring_boundary()
|
||||
&& value.is_string_literal_expr() =>
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
|
||||
}
|
||||
Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
|
||||
|
||||
// Allow __future__ imports until we see a non-__future__ import.
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
@@ -332,11 +364,11 @@ where
|
||||
}
|
||||
}
|
||||
Stmt::Import(_) => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
}
|
||||
_ => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING_BOUNDARY;
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
if !(self.semantic.seen_import_boundary()
|
||||
|| helpers::is_assignment_to_a_dunder(stmt)
|
||||
@@ -353,6 +385,16 @@ where
|
||||
// the node.
|
||||
let flags_snapshot = self.semantic.flags;
|
||||
|
||||
// Update the semantic model if it is in a docstring. This should be done after the
|
||||
// flags snapshot to ensure that it gets reset once the statement is analyzed.
|
||||
if self.docstring_state.is_expected() {
|
||||
if is_docstring_stmt(stmt) {
|
||||
self.semantic.flags |= SemanticModelFlags::DOCSTRING;
|
||||
}
|
||||
// Reset the state irrespective of whether the statement is a docstring or not.
|
||||
self.docstring_state = DocstringState::Other;
|
||||
}
|
||||
|
||||
// Step 1: Binding
|
||||
match stmt {
|
||||
Stmt::AugAssign(ast::StmtAugAssign {
|
||||
@@ -654,6 +696,8 @@ where
|
||||
self.semantic.set_globals(globals);
|
||||
}
|
||||
|
||||
// Set the docstring state before visiting the class body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.visit_body(body);
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
@@ -1288,6 +1332,16 @@ where
|
||||
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
Expr::NamedExpr(ast::ExprNamedExpr {
|
||||
target,
|
||||
value,
|
||||
range: _,
|
||||
}) => {
|
||||
self.visit_expr(value);
|
||||
|
||||
self.semantic.flags |= SemanticModelFlags::NAMED_EXPRESSION_ASSIGNMENT;
|
||||
self.visit_expr(target);
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
|
||||
@@ -1504,6 +1558,8 @@ impl<'a> Checker<'a> {
|
||||
unreachable!("Generator expression must contain at least one generator");
|
||||
};
|
||||
|
||||
let flags = self.semantic.flags;
|
||||
|
||||
// Generators are compiled as nested functions. (This may change with PEP 709.)
|
||||
// As such, the `iter` of the first generator is evaluated in the outer scope, while all
|
||||
// subsequent nodes are evaluated in the inner scope.
|
||||
@@ -1533,14 +1589,22 @@ impl<'a> Checker<'a> {
|
||||
// `x` is local to `foo`, and the `T` in `y=T` skips the class scope when resolving.
|
||||
self.visit_expr(&generator.iter);
|
||||
self.semantic.push_scope(ScopeKind::Generator);
|
||||
|
||||
self.semantic.flags = flags | SemanticModelFlags::COMPREHENSION_ASSIGNMENT;
|
||||
self.visit_expr(&generator.target);
|
||||
self.semantic.flags = flags;
|
||||
|
||||
for expr in &generator.ifs {
|
||||
self.visit_boolean_test(expr);
|
||||
}
|
||||
|
||||
for generator in iterator {
|
||||
self.visit_expr(&generator.iter);
|
||||
|
||||
self.semantic.flags = flags | SemanticModelFlags::COMPREHENSION_ASSIGNMENT;
|
||||
self.visit_expr(&generator.target);
|
||||
self.semantic.flags = flags;
|
||||
|
||||
for expr in &generator.ifs {
|
||||
self.visit_boolean_test(expr);
|
||||
}
|
||||
@@ -1739,11 +1803,21 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
// A binding within a `for` must be a loop variable, as in:
|
||||
// ```python
|
||||
// for x in range(10):
|
||||
// ...
|
||||
// ```
|
||||
if parent.is_for_stmt() {
|
||||
self.add_binding(id, expr.range(), BindingKind::LoopVar, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// A binding within a `with` must be an item, as in:
|
||||
// ```python
|
||||
// with open("file.txt") as fp:
|
||||
// ...
|
||||
// ```
|
||||
if parent.is_with_stmt() {
|
||||
self.add_binding(id, expr.range(), BindingKind::WithItemVar, flags);
|
||||
return;
|
||||
@@ -1799,17 +1873,26 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
// If the expression is the left-hand side of a walrus operator, then it's a named
|
||||
// expression assignment.
|
||||
if self
|
||||
.semantic
|
||||
.current_expressions()
|
||||
.filter_map(Expr::as_named_expr_expr)
|
||||
.any(|parent| parent.target.as_ref() == expr)
|
||||
{
|
||||
// expression assignment, as in:
|
||||
// ```python
|
||||
// if (x := 10) > 5:
|
||||
// ...
|
||||
// ```
|
||||
if self.semantic.in_named_expression_assignment() {
|
||||
self.add_binding(id, expr.range(), BindingKind::NamedExprAssignment, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the expression is part of a comprehension target, then it's a comprehension variable
|
||||
// assignment, as in:
|
||||
// ```python
|
||||
// [x for x in range(10)]
|
||||
// ```
|
||||
if self.semantic.in_comprehension_assignment() {
|
||||
self.add_binding(id, expr.range(), BindingKind::ComprehensionVar, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
self.add_binding(id, expr.range(), BindingKind::Assignment, flags);
|
||||
}
|
||||
|
||||
@@ -1925,6 +2008,8 @@ impl<'a> Checker<'a> {
|
||||
};
|
||||
|
||||
self.visit_parameters(parameters);
|
||||
// Set the docstring state before visiting the function body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.visit_body(body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,6 +1025,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
(Refurb, "129") => (RuleGroup::Preview, rules::refurb::rules::ReadlinesInFor),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
|
||||
@@ -248,6 +248,7 @@ impl Renamer {
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::BoundException
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::ComprehensionVar
|
||||
| BindingKind::WithItemVar
|
||||
| BindingKind::Global
|
||||
| BindingKind::Nonlocal(_)
|
||||
|
||||
@@ -118,8 +118,7 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
|
||||
let binding = semantic.binding(binding_id);
|
||||
|
||||
let Some(Expr::Call(call)) = analyze::typing::find_binding_value(&name.id, binding, semantic)
|
||||
else {
|
||||
let Some(Expr::Call(call)) = analyze::typing::find_binding_value(binding, semantic) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,13 +15,11 @@ PYI049.py:9:7: PYI049 Private TypedDict `_UnusedTypedDict2` is never used
|
||||
10 | bar: int
|
||||
|
|
||||
|
||||
PYI049.py:20:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used
|
||||
PYI049.py:21:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used
|
||||
|
|
||||
18 | bar: list[int]
|
||||
19 |
|
||||
20 | _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
||||
21 | _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
||||
| ^^^^^^^^^^^^^^^^^ PYI049
|
||||
21 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||
22 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -24,4 +24,13 @@ PYI049.pyi:34:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used
|
||||
35 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||
|
|
||||
|
||||
PYI049.pyi:43:11: PYI049 Private TypedDict `_UnusedTypeDict4` is never used
|
||||
|
|
||||
41 | # scope (unlike in `.py` files).
|
||||
42 | class _CustomClass3:
|
||||
43 | class _UnusedTypeDict4(TypedDict):
|
||||
| ^^^^^^^^^^^^^^^^ PYI049
|
||||
44 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -76,8 +76,7 @@ pub(crate) fn enumerate_for_loop(checker: &mut Checker, for_stmt: &ast::StmtFor)
|
||||
}
|
||||
|
||||
// Ensure that the index variable was initialized to 0.
|
||||
let Some(value) = typing::find_binding_value(&index.id, binding, checker.semantic())
|
||||
else {
|
||||
let Some(value) = typing::find_binding_value(binding, checker.semantic()) else {
|
||||
continue;
|
||||
};
|
||||
if !matches!(
|
||||
|
||||
@@ -419,23 +419,20 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test currently disabled as line endings are automatically converted to
|
||||
// platform-appropriate ones in CI/CD #[test_case(Path::new("
|
||||
// line_ending_crlf.py"))] #[test_case(Path::new("line_ending_lf.py"))]
|
||||
// fn source_code_style(path: &Path) -> Result<()> {
|
||||
// let snapshot = format!("{}", path.to_string_lossy());
|
||||
// let diagnostics = test_path(
|
||||
// Path::new("isort")
|
||||
// .join(path)
|
||||
// .as_path(),
|
||||
// &LinterSettings {
|
||||
// src: vec![test_resource_path("fixtures/isort")],
|
||||
// ..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
// },
|
||||
// )?;
|
||||
// crate::assert_messages!(snapshot, diagnostics);
|
||||
// Ok(())
|
||||
// }
|
||||
#[test_case(Path::new("line_ending_crlf.py"))]
|
||||
#[test_case(Path::new("line_ending_lf.py"))]
|
||||
fn source_code_style(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
crate::assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("separate_local_folder_imports.py"))]
|
||||
fn known_local_folder(path: &Path) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
line_ending_crlf.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | / from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
2 | |
|
||||
| |_^ I001
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
1 |-from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
1 |+from long_module_name import (
|
||||
2 |+ member_five,
|
||||
3 |+ member_four,
|
||||
4 |+ member_one,
|
||||
5 |+ member_three,
|
||||
6 |+ member_two,
|
||||
7 |+)
|
||||
2 8 |
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
line_ending_lf.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | / from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
2 | |
|
||||
| |_^ I001
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
1 |-from long_module_name import member_one, member_two, member_three, member_four, member_five
|
||||
1 |+from long_module_name import (
|
||||
2 |+ member_five,
|
||||
3 |+ member_four,
|
||||
4 |+ member_one,
|
||||
5 |+ member_three,
|
||||
6 |+ member_two,
|
||||
7 |+)
|
||||
2 8 |
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::ComprehensionVar
|
||||
| BindingKind::Global
|
||||
| BindingKind::Nonlocal(_) => Resolution::RelevantLocal,
|
||||
BindingKind::Import(import) if matches!(import.call_path(), ["pandas"]) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::find_assigned_value;
|
||||
use ruff_text_size::TextRange;
|
||||
@@ -98,22 +99,25 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
|
||||
range: iterable_range,
|
||||
..
|
||||
}) => {
|
||||
// If the variable is being appended to, don't suggest removing the cast:
|
||||
//
|
||||
// ```python
|
||||
// items = ["foo", "bar"]
|
||||
// for item in list(items):
|
||||
// items.append("baz")
|
||||
// ```
|
||||
//
|
||||
// Here, removing the `list()` cast would change the behavior of the code.
|
||||
if body.iter().any(|stmt| match_append(stmt, id)) {
|
||||
return;
|
||||
}
|
||||
let Some(value) = find_assigned_value(id, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
if matches!(value, Expr::Tuple(_) | Expr::List(_) | Expr::Set(_)) {
|
||||
// If the variable is being modified to, don't suggest removing the cast:
|
||||
//
|
||||
// ```python
|
||||
// items = ["foo", "bar"]
|
||||
// for item in list(items):
|
||||
// items.append("baz")
|
||||
// ```
|
||||
//
|
||||
// Here, removing the `list()` cast would change the behavior of the code.
|
||||
let mut visitor = MutationVisitor::new(id);
|
||||
visitor.visit_body(body);
|
||||
if visitor.is_mutated {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range);
|
||||
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -123,28 +127,6 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a statement is an `append` call to a given identifier.
|
||||
///
|
||||
/// For example, `foo.append(bar)` would return `true` if `id` is `foo`.
|
||||
fn match_append(stmt: &Stmt, id: &str) -> bool {
|
||||
let Some(ast::StmtExpr { value, .. }) = stmt.as_expr_stmt() else {
|
||||
return false;
|
||||
};
|
||||
let Some(ast::ExprCall { func, .. }) = value.as_call_expr() else {
|
||||
return false;
|
||||
};
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = func.as_attribute_expr() else {
|
||||
return false;
|
||||
};
|
||||
if attr != "append" {
|
||||
return false;
|
||||
}
|
||||
let Some(ast::ExprName { id: target_id, .. }) = value.as_name_expr() else {
|
||||
return false;
|
||||
};
|
||||
target_id == id
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove a `list` cast from an expression.
|
||||
fn remove_cast(list_range: TextRange, iterable_range: TextRange) -> Fix {
|
||||
Fix::safe_edits(
|
||||
@@ -152,3 +134,95 @@ fn remove_cast(list_range: TextRange, iterable_range: TextRange) -> Fix {
|
||||
[Edit::deletion(iterable_range.end(), list_range.end())],
|
||||
)
|
||||
}
|
||||
|
||||
/// A [`StatementVisitor`] that (conservatively) identifies mutations to a variable.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MutationVisitor<'a> {
|
||||
pub(crate) target: &'a str,
|
||||
pub(crate) is_mutated: bool,
|
||||
}
|
||||
|
||||
impl<'a> MutationVisitor<'a> {
|
||||
pub(crate) fn new(target: &'a str) -> Self {
|
||||
Self {
|
||||
target,
|
||||
is_mutated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> StatementVisitor<'b> for MutationVisitor<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
if match_mutation(stmt, self.target) {
|
||||
self.is_mutated = true;
|
||||
} else {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a statement is (probably) a modification to the list assigned to the given identifier.
|
||||
///
|
||||
/// For example, `foo.append(bar)` would return `true` if `id` is `foo`.
|
||||
fn match_mutation(stmt: &Stmt, id: &str) -> bool {
|
||||
match stmt {
|
||||
// Ex) `foo.append(bar)`
|
||||
Stmt::Expr(ast::StmtExpr { value, .. }) => {
|
||||
let Some(ast::ExprCall { func, .. }) = value.as_call_expr() else {
|
||||
return false;
|
||||
};
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = func.as_attribute_expr() else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(
|
||||
attr.as_str(),
|
||||
"append" | "insert" | "extend" | "remove" | "pop" | "clear" | "reverse" | "sort"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let Some(ast::ExprName { id: target_id, .. }) = value.as_name_expr() else {
|
||||
return false;
|
||||
};
|
||||
target_id == id
|
||||
}
|
||||
// Ex) `foo[0] = bar`
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => targets.iter().any(|target| {
|
||||
if let Some(ast::ExprSubscript { value: target, .. }) = target.as_subscript_expr() {
|
||||
if let Some(ast::ExprName { id: target_id, .. }) = target.as_name_expr() {
|
||||
return target_id == id;
|
||||
}
|
||||
}
|
||||
false
|
||||
}),
|
||||
// Ex) `foo += bar`
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
|
||||
if let Some(ast::ExprName { id: target_id, .. }) = target.as_name_expr() {
|
||||
target_id == id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// Ex) `foo[0]: int = bar`
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
|
||||
if let Some(ast::ExprSubscript { value: target, .. }) = target.as_subscript_expr() {
|
||||
if let Some(ast::ExprName { id: target_id, .. }) = target.as_name_expr() {
|
||||
return target_id == id;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
// Ex) `del foo[0]`
|
||||
Stmt::Delete(ast::StmtDelete { targets, .. }) => targets.iter().any(|target| {
|
||||
if let Some(ast::ExprSubscript { value: target, .. }) = target.as_subscript_expr() {
|
||||
if let Some(ast::ExprName { id: target_id, .. }) = target.as_name_expr() {
|
||||
return target_id == id;
|
||||
}
|
||||
}
|
||||
false
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ PERF101.py:34:10: PERF101 [*] Do not cast an iterable to `list` before iterating
|
||||
34 |+for i in {1, 2, 3}: # PERF101
|
||||
37 35 | pass
|
||||
38 36 |
|
||||
39 37 | for i in list(foo_dict): # Ok
|
||||
39 37 | for i in list(foo_dict): # OK
|
||||
|
||||
PERF101.py:57:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
@@ -192,7 +192,7 @@ PERF101.py:57:10: PERF101 [*] Do not cast an iterable to `list` before iterating
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Safe fix
|
||||
54 54 | for i in list(foo_list): # Ok
|
||||
54 54 | for i in list(foo_list): # OK
|
||||
55 55 | foo_list.append(i + 1)
|
||||
56 56 |
|
||||
57 |-for i in list(foo_list): # PERF101
|
||||
@@ -218,5 +218,7 @@ PERF101.py:69:10: PERF101 [*] Do not cast an iterable to `list` before iterating
|
||||
69 |-for i in list(nested_tuple): # PERF101
|
||||
69 |+for i in nested_tuple: # PERF101
|
||||
70 70 | pass
|
||||
71 71 |
|
||||
72 72 | for i in list(foo_list): # OK
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,15 @@ enum UnusedImportContext {
|
||||
/// If an import statement is used to check for the availability or existence
|
||||
/// of a module, consider using `importlib.util.find_spec` instead.
|
||||
///
|
||||
/// If an import statement is used to re-export a symbol as part of a module's
|
||||
/// public interface, consider using a "redundant" import alias, which
|
||||
/// instructs Ruff (and other tools) to respect the re-export, and avoid
|
||||
/// marking it as unused, as in:
|
||||
///
|
||||
/// ```python
|
||||
/// from module import member as member
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import numpy as np # unused import
|
||||
@@ -54,11 +63,12 @@ enum UnusedImportContext {
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.pyflakes.extend-generics`
|
||||
/// - `lint.ignore-init-module-imports`
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
||||
/// - [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
||||
/// - [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
|
||||
#[violation]
|
||||
pub struct UnusedImport {
|
||||
name: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -25,7 +26,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as the use of a `set` literal will
|
||||
/// error at runtime if the sequence contains unhashable elements (like lists
|
||||
/// or dictionaries).
|
||||
/// or dictionaries). While Ruff will attempt to infer the hashability of the
|
||||
/// elements, it may not always be able to do so.
|
||||
///
|
||||
/// ## References
|
||||
/// - [What’s New In Python 3.2](https://docs.python.org/3/whatsnew/3.2.html#optimizations)
|
||||
@@ -57,7 +59,40 @@ pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompa
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(right, Expr::List(_) | Expr::Tuple(_)) {
|
||||
let elts = match right {
|
||||
Expr::List(ast::ExprList { elts, .. }) => elts,
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// If `left`, or any of the elements in `right`, are known to _not_ be hashable, return.
|
||||
if std::iter::once(compare.left.as_ref())
|
||||
.chain(elts)
|
||||
.any(|expr| match expr {
|
||||
// Expressions that are known _not_ to be hashable.
|
||||
Expr::List(_)
|
||||
| Expr::Set(_)
|
||||
| Expr::Dict(_)
|
||||
| Expr::ListComp(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
| Expr::GeneratorExp(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::YieldFrom(_) => true,
|
||||
// Expressions that can be _inferred_ not to be hashable.
|
||||
Expr::Name(name) => {
|
||||
let Some(id) = checker.semantic().resolve_name(name) else {
|
||||
return false;
|
||||
};
|
||||
let binding = checker.semantic().binding(id);
|
||||
typing::is_list(binding, checker.semantic())
|
||||
|| typing::is_dict(binding, checker.semantic())
|
||||
|| typing::is_set(binding, checker.semantic())
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,10 @@ fn is_magic_value(literal_expr: LiteralExpressionRef, allowed_types: &[ConstantT
|
||||
!matches!(value.to_str(), "" | "__main__")
|
||||
}
|
||||
LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
|
||||
#[allow(clippy::float_cmp)]
|
||||
ast::Number::Float(value) => !(*value == 0.0 || *value == 1.0),
|
||||
ast::Number::Int(value) => !matches!(*value, Int::ZERO | Int::ONE),
|
||||
_ => true,
|
||||
ast::Number::Complex { .. } => true,
|
||||
},
|
||||
LiteralExpressionRef::BytesLiteral(_) => true,
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option<Dia
|
||||
BindingKind::Assignment => Kind::Assignment,
|
||||
BindingKind::TypeParam => Kind::TypeParam,
|
||||
BindingKind::LoopVar => Kind::LoopVar,
|
||||
BindingKind::ComprehensionVar => Kind::ComprenhensionVar,
|
||||
BindingKind::WithItemVar => Kind::WithItemVar,
|
||||
BindingKind::Global => Kind::Global,
|
||||
BindingKind::Nonlocal(_) => Kind::Nonlocal,
|
||||
@@ -88,6 +89,7 @@ enum Kind {
|
||||
Assignment,
|
||||
TypeParam,
|
||||
LoopVar,
|
||||
ComprenhensionVar,
|
||||
WithItemVar,
|
||||
Global,
|
||||
Nonlocal,
|
||||
@@ -105,6 +107,7 @@ impl fmt::Display for Kind {
|
||||
Kind::Assignment => f.write_str("Variable"),
|
||||
Kind::TypeParam => f.write_str("Type parameter"),
|
||||
Kind::LoopVar => f.write_str("Variable"),
|
||||
Kind::ComprenhensionVar => f.write_str("Variable"),
|
||||
Kind::WithItemVar => f.write_str("Variable"),
|
||||
Kind::Global => f.write_str("Global"),
|
||||
Kind::Nonlocal => f.write_str("Nonlocal"),
|
||||
|
||||
@@ -10,49 +10,67 @@ magic_value_comparison.py:5:4: PLR2004 Magic value used in comparison, consider
|
||||
6 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:38:12: PLR2004 Magic value used in comparison, consider replacing `2` with a constant variable
|
||||
magic_value_comparison.py:47:12: PLR2004 Magic value used in comparison, consider replacing `2` with a constant variable
|
||||
|
|
||||
36 | pass
|
||||
37 |
|
||||
38 | if argc != 2: # [magic-value-comparison]
|
||||
| ^ PLR2004
|
||||
39 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:41:12: PLR2004 Magic value used in comparison, consider replacing `-2` with a constant variable
|
||||
|
|
||||
39 | pass
|
||||
40 |
|
||||
41 | if argc != -2: # [magic-value-comparison]
|
||||
| ^^ PLR2004
|
||||
42 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:44:12: PLR2004 Magic value used in comparison, consider replacing `+2` with a constant variable
|
||||
|
|
||||
42 | pass
|
||||
43 |
|
||||
44 | if argc != +2: # [magic-value-comparison]
|
||||
| ^^ PLR2004
|
||||
45 | pass
|
||||
46 |
|
||||
47 | if argc != 2: # [magic-value-comparison]
|
||||
| ^ PLR2004
|
||||
48 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:65:21: PLR2004 Magic value used in comparison, consider replacing `3.141592653589793238` with a constant variable
|
||||
magic_value_comparison.py:50:12: PLR2004 Magic value used in comparison, consider replacing `-2` with a constant variable
|
||||
|
|
||||
63 | pi_estimation = 3.14
|
||||
64 |
|
||||
65 | if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
|
||||
48 | pass
|
||||
49 |
|
||||
50 | if argc != -2: # [magic-value-comparison]
|
||||
| ^^ PLR2004
|
||||
51 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:53:12: PLR2004 Magic value used in comparison, consider replacing `+2` with a constant variable
|
||||
|
|
||||
51 | pass
|
||||
52 |
|
||||
53 | if argc != +2: # [magic-value-comparison]
|
||||
| ^^ PLR2004
|
||||
54 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:56:12: PLR2004 Magic value used in comparison, consider replacing `-2.0` with a constant variable
|
||||
|
|
||||
54 | pass
|
||||
55 |
|
||||
56 | if argc != -2.0: # [magic-value-comparison]
|
||||
| ^^^^ PLR2004
|
||||
57 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:59:12: PLR2004 Magic value used in comparison, consider replacing `+2.0` with a constant variable
|
||||
|
|
||||
57 | pass
|
||||
58 |
|
||||
59 | if argc != +2.0: # [magic-value-comparison]
|
||||
| ^^^^ PLR2004
|
||||
60 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:80:21: PLR2004 Magic value used in comparison, consider replacing `3.141592653589793238` with a constant variable
|
||||
|
|
||||
78 | pi_estimation = 3.14
|
||||
79 |
|
||||
80 | if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PLR2004
|
||||
66 | pass
|
||||
81 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:71:21: PLR2004 Magic value used in comparison, consider replacing `0x3` with a constant variable
|
||||
magic_value_comparison.py:86:21: PLR2004 Magic value used in comparison, consider replacing `0x3` with a constant variable
|
||||
|
|
||||
69 | pass
|
||||
70 |
|
||||
71 | if pi_estimation == 0x3: # [magic-value-comparison]
|
||||
84 | pass
|
||||
85 |
|
||||
86 | if pi_estimation == 0x3: # [magic-value-comparison]
|
||||
| ^^^ PLR2004
|
||||
72 | pass
|
||||
87 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ literal_membership.py:4:6: PLR6201 [*] Use a `set` literal when testing for memb
|
||||
5 | | 1, 2, 3
|
||||
6 | | )
|
||||
| |_^ PLR6201
|
||||
7 |
|
||||
8 | # OK
|
||||
7 | fruits = ["cherry", "grapes"]
|
||||
8 | "cherry" in fruits
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
@@ -62,8 +62,29 @@ literal_membership.py:4:6: PLR6201 [*] Use a `set` literal when testing for memb
|
||||
5 5 | 1, 2, 3
|
||||
6 |-)
|
||||
6 |+}
|
||||
7 7 |
|
||||
8 8 | # OK
|
||||
9 9 | fruits = ["cherry", "grapes"]
|
||||
7 7 | fruits = ["cherry", "grapes"]
|
||||
8 8 | "cherry" in fruits
|
||||
9 9 | _ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in ("a", "b")}
|
||||
|
||||
literal_membership.py:9:70: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
7 | fruits = ["cherry", "grapes"]
|
||||
8 | "cherry" in fruits
|
||||
9 | _ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in ("a", "b")}
|
||||
| ^^^^^^^^^^ PLR6201
|
||||
10 |
|
||||
11 | # OK
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 | )
|
||||
7 7 | fruits = ["cherry", "grapes"]
|
||||
8 8 | "cherry" in fruits
|
||||
9 |-_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in ("a", "b")}
|
||||
9 |+_ = {key: value for key, value in {"a": 1, "b": 2}.items() if key in {"a", "b"}}
|
||||
10 10 |
|
||||
11 11 | # OK
|
||||
12 12 | fruits in [[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
|
||||
|
||||
@@ -1,31 +1,49 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
magic_value_comparison.py:59:22: PLR2004 Magic value used in comparison, consider replacing `"Hunter2"` with a constant variable
|
||||
magic_value_comparison.py:56:12: PLR2004 Magic value used in comparison, consider replacing `-2.0` with a constant variable
|
||||
|
|
||||
54 | pass
|
||||
55 |
|
||||
56 | if argc != -2.0: # [magic-value-comparison]
|
||||
| ^^^^ PLR2004
|
||||
57 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:59:12: PLR2004 Magic value used in comparison, consider replacing `+2.0` with a constant variable
|
||||
|
|
||||
57 | pass
|
||||
58 |
|
||||
59 | if input_password == "Hunter2": # correct
|
||||
| ^^^^^^^^^ PLR2004
|
||||
59 | if argc != +2.0: # [magic-value-comparison]
|
||||
| ^^^^ PLR2004
|
||||
60 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:65:21: PLR2004 Magic value used in comparison, consider replacing `3.141592653589793238` with a constant variable
|
||||
magic_value_comparison.py:74:22: PLR2004 Magic value used in comparison, consider replacing `"Hunter2"` with a constant variable
|
||||
|
|
||||
63 | pi_estimation = 3.14
|
||||
64 |
|
||||
65 | if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
|
||||
72 | pass
|
||||
73 |
|
||||
74 | if input_password == "Hunter2": # correct
|
||||
| ^^^^^^^^^ PLR2004
|
||||
75 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:80:21: PLR2004 Magic value used in comparison, consider replacing `3.141592653589793238` with a constant variable
|
||||
|
|
||||
78 | pi_estimation = 3.14
|
||||
79 |
|
||||
80 | if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PLR2004
|
||||
66 | pass
|
||||
81 | pass
|
||||
|
|
||||
|
||||
magic_value_comparison.py:77:18: PLR2004 Magic value used in comparison, consider replacing `b"something"` with a constant variable
|
||||
magic_value_comparison.py:92:18: PLR2004 Magic value used in comparison, consider replacing `b"something"` with a constant variable
|
||||
|
|
||||
75 | user_input = b"Hello, There!"
|
||||
76 |
|
||||
77 | if user_input == b"something": # correct
|
||||
90 | user_input = b"Hello, There!"
|
||||
91 |
|
||||
92 | if user_input == b"something": # correct
|
||||
| ^^^^^^^^^^^^ PLR2004
|
||||
78 | pass
|
||||
93 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ mod tests {
|
||||
#[test_case(Rule::ReadWholeFile, Path::new("FURB101.py"))]
|
||||
#[test_case(Rule::RepeatedAppend, Path::new("FURB113.py"))]
|
||||
#[test_case(Rule::ReimplementedOperator, Path::new("FURB118.py"))]
|
||||
#[test_case(Rule::ReadlinesInFor, Path::new("FURB129.py"))]
|
||||
#[test_case(Rule::DeleteFullSlice, Path::new("FURB131.py"))]
|
||||
#[test_case(Rule::CheckAndRemoveFromSet, Path::new("FURB132.py"))]
|
||||
#[test_case(Rule::IfExprMinMax, Path::new("FURB136.py"))]
|
||||
|
||||
@@ -9,6 +9,7 @@ pub(crate) use math_constant::*;
|
||||
pub(crate) use metaclass_abcmeta::*;
|
||||
pub(crate) use print_empty_string::*;
|
||||
pub(crate) use read_whole_file::*;
|
||||
pub(crate) use readlines_in_for::*;
|
||||
pub(crate) use redundant_log_base::*;
|
||||
pub(crate) use regex_flag_alias::*;
|
||||
pub(crate) use reimplemented_operator::*;
|
||||
@@ -30,6 +31,7 @@ mod math_constant;
|
||||
mod metaclass_abcmeta;
|
||||
mod print_empty_string;
|
||||
mod read_whole_file;
|
||||
mod readlines_in_for;
|
||||
mod redundant_log_base;
|
||||
mod regex_flag_alias;
|
||||
mod reimplemented_operator;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Comprehension, Expr, StmtFor};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_semantic::analyze::typing::is_io_base_expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `readlines()` when iterating over a file line-by-line.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Rather than iterating over all lines in a file by calling `readlines()`,
|
||||
/// it's more convenient and performant to iterate over the file object
|
||||
/// directly.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// with open("file.txt") as fp:
|
||||
/// for line in fp.readlines():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// with open("file.txt") as fp:
|
||||
/// for line in fp:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `io.IOBase.readlines`](https://docs.python.org/3/library/io.html#io.IOBase.readlines)
|
||||
#[violation]
|
||||
pub(crate) struct ReadlinesInFor;
|
||||
|
||||
impl AlwaysFixableViolation for ReadlinesInFor {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Instead of calling `readlines()`, iterate over file object directly")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove `readlines()`".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// FURB129
|
||||
pub(crate) fn readlines_in_for(checker: &mut Checker, for_stmt: &StmtFor) {
|
||||
readlines_in_iter(checker, for_stmt.iter.as_ref());
|
||||
}
|
||||
|
||||
/// FURB129
|
||||
pub(crate) fn readlines_in_comprehension(checker: &mut Checker, comprehension: &Comprehension) {
|
||||
readlines_in_iter(checker, &comprehension.iter);
|
||||
}
|
||||
|
||||
fn readlines_in_iter(checker: &mut Checker, iter_expr: &Expr) {
|
||||
let Expr::Call(expr_call) = iter_expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Expr::Attribute(expr_attr) = expr_call.func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if expr_attr.attr.as_str() != "readlines" || !expr_call.arguments.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether `fp` in `fp.readlines()` was bound to a file object.
|
||||
if let Expr::Name(name) = expr_attr.value.as_ref() {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_name(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
.is_some_and(|binding| typing::is_io_base(binding, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if !is_io_base_expr(expr_attr.value.as_ref(), checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(ReadlinesInFor, expr_call.range());
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(
|
||||
expr_call.range().add_start(expr_attr.value.range().len()),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB129.py:7:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
5 | # Errors
|
||||
6 | with open("FURB129.py") as f:
|
||||
7 | for _line in f.readlines():
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
8 | pass
|
||||
9 | a = [line.lower() for line in f.readlines()]
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
4 4 |
|
||||
5 5 | # Errors
|
||||
6 6 | with open("FURB129.py") as f:
|
||||
7 |- for _line in f.readlines():
|
||||
7 |+ for _line in f:
|
||||
8 8 | pass
|
||||
9 9 | a = [line.lower() for line in f.readlines()]
|
||||
10 10 | b = {line.upper() for line in f.readlines()}
|
||||
|
||||
FURB129.py:9:35: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
7 | for _line in f.readlines():
|
||||
8 | pass
|
||||
9 | a = [line.lower() for line in f.readlines()]
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
10 | b = {line.upper() for line in f.readlines()}
|
||||
11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 | with open("FURB129.py") as f:
|
||||
7 7 | for _line in f.readlines():
|
||||
8 8 | pass
|
||||
9 |- a = [line.lower() for line in f.readlines()]
|
||||
9 |+ a = [line.lower() for line in f]
|
||||
10 10 | b = {line.upper() for line in f.readlines()}
|
||||
11 11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
12 12 |
|
||||
|
||||
FURB129.py:10:35: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
8 | pass
|
||||
9 | a = [line.lower() for line in f.readlines()]
|
||||
10 | b = {line.upper() for line in f.readlines()}
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 | for _line in f.readlines():
|
||||
8 8 | pass
|
||||
9 9 | a = [line.lower() for line in f.readlines()]
|
||||
10 |- b = {line.upper() for line in f.readlines()}
|
||||
10 |+ b = {line.upper() for line in f}
|
||||
11 11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
12 12 |
|
||||
13 13 | with Path("FURB129.py").open() as f:
|
||||
|
||||
FURB129.py:11:49: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
9 | a = [line.lower() for line in f.readlines()]
|
||||
10 | b = {line.upper() for line in f.readlines()}
|
||||
11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
12 |
|
||||
13 | with Path("FURB129.py").open() as f:
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | pass
|
||||
9 9 | a = [line.lower() for line in f.readlines()]
|
||||
10 10 | b = {line.upper() for line in f.readlines()}
|
||||
11 |- c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
11 |+ c = {line.lower(): line.upper() for line in f}
|
||||
12 12 |
|
||||
13 13 | with Path("FURB129.py").open() as f:
|
||||
14 14 | for _line in f.readlines():
|
||||
|
||||
FURB129.py:14:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
13 | with Path("FURB129.py").open() as f:
|
||||
14 | for _line in f.readlines():
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
15 | pass
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
11 11 | c = {line.lower(): line.upper() for line in f.readlines()}
|
||||
12 12 |
|
||||
13 13 | with Path("FURB129.py").open() as f:
|
||||
14 |- for _line in f.readlines():
|
||||
14 |+ for _line in f:
|
||||
15 15 | pass
|
||||
16 16 |
|
||||
17 17 | for _line in open("FURB129.py").readlines():
|
||||
|
||||
FURB129.py:17:14: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
15 | pass
|
||||
16 |
|
||||
17 | for _line in open("FURB129.py").readlines():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB129
|
||||
18 | pass
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
14 14 | for _line in f.readlines():
|
||||
15 15 | pass
|
||||
16 16 |
|
||||
17 |-for _line in open("FURB129.py").readlines():
|
||||
17 |+for _line in open("FURB129.py"):
|
||||
18 18 | pass
|
||||
19 19 |
|
||||
20 20 | for _line in Path("FURB129.py").open().readlines():
|
||||
|
||||
FURB129.py:20:14: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | for _line in Path("FURB129.py").open().readlines():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB129
|
||||
21 | pass
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 | for _line in open("FURB129.py").readlines():
|
||||
18 18 | pass
|
||||
19 19 |
|
||||
20 |-for _line in Path("FURB129.py").open().readlines():
|
||||
20 |+for _line in Path("FURB129.py").open():
|
||||
21 21 | pass
|
||||
22 22 |
|
||||
23 23 |
|
||||
|
||||
FURB129.py:26:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
24 | def func():
|
||||
25 | f = Path("FURB129.py").open()
|
||||
26 | for _line in f.readlines():
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
27 | pass
|
||||
28 | f.close()
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
23 23 |
|
||||
24 24 | def func():
|
||||
25 25 | f = Path("FURB129.py").open()
|
||||
26 |- for _line in f.readlines():
|
||||
26 |+ for _line in f:
|
||||
27 27 | pass
|
||||
28 28 | f.close()
|
||||
29 29 |
|
||||
|
||||
FURB129.py:32:18: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
31 | def func(f: io.BytesIO):
|
||||
32 | for _line in f.readlines():
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
33 | pass
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
29 29 |
|
||||
30 30 |
|
||||
31 31 | def func(f: io.BytesIO):
|
||||
32 |- for _line in f.readlines():
|
||||
32 |+ for _line in f:
|
||||
33 33 | pass
|
||||
34 34 |
|
||||
35 35 |
|
||||
|
||||
FURB129.py:38:22: FURB129 [*] Instead of calling `readlines()`, iterate over file object directly
|
||||
|
|
||||
36 | def func():
|
||||
37 | with (open("FURB129.py") as f, foo as bar):
|
||||
38 | for _line in f.readlines():
|
||||
| ^^^^^^^^^^^^^ FURB129
|
||||
39 | pass
|
||||
40 | for _line in bar.readlines():
|
||||
|
|
||||
= help: Remove `readlines()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
35 35 |
|
||||
36 36 | def func():
|
||||
37 37 | with (open("FURB129.py") as f, foo as bar):
|
||||
38 |- for _line in f.readlines():
|
||||
38 |+ for _line in f:
|
||||
39 39 | pass
|
||||
40 40 | for _line in bar.readlines():
|
||||
41 41 | pass
|
||||
|
||||
|
||||
@@ -52,14 +52,15 @@ use ruff_text_size::Ranged;
|
||||
/// - [The Python Standard Library](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)
|
||||
#[violation]
|
||||
pub struct AsyncioDanglingTask {
|
||||
expr: String,
|
||||
method: Method,
|
||||
}
|
||||
|
||||
impl Violation for AsyncioDanglingTask {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let AsyncioDanglingTask { method } = self;
|
||||
format!("Store a reference to the return value of `asyncio.{method}`")
|
||||
let AsyncioDanglingTask { expr, method } = self;
|
||||
format!("Store a reference to the return value of `{expr}.{method}`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,23 +81,35 @@ pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Op
|
||||
})
|
||||
{
|
||||
return Some(Diagnostic::new(
|
||||
AsyncioDanglingTask { method },
|
||||
AsyncioDanglingTask {
|
||||
expr: "asyncio".to_string(),
|
||||
method,
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
// Ex) `loop = asyncio.get_running_loop(); loop.create_task(...)`
|
||||
// Ex) `loop = ...; loop.create_task(...)`
|
||||
if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() {
|
||||
if attr == "create_task" {
|
||||
if typing::resolve_assignment(value, semantic).is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["asyncio", "get_running_loop"])
|
||||
}) {
|
||||
return Some(Diagnostic::new(
|
||||
AsyncioDanglingTask {
|
||||
method: Method::CreateTask,
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
if let Expr::Name(name) = value.as_ref() {
|
||||
if typing::resolve_assignment(value, semantic).is_some_and(|call_path| {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
[
|
||||
"asyncio",
|
||||
"get_event_loop" | "get_running_loop" | "new_event_loop"
|
||||
]
|
||||
)
|
||||
}) {
|
||||
return Some(Diagnostic::new(
|
||||
AsyncioDanglingTask {
|
||||
expr: name.id.to_string(),
|
||||
method: Method::CreateTask,
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ RUF006.py:68:12: RUF006 Store a reference to the return value of `asyncio.create
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006
|
||||
|
|
||||
|
||||
RUF006.py:74:26: RUF006 Store a reference to the return value of `asyncio.create_task`
|
||||
RUF006.py:74:26: RUF006 Store a reference to the return value of `loop.create_task`
|
||||
|
|
||||
72 | def f():
|
||||
73 | loop = asyncio.get_running_loop()
|
||||
@@ -33,7 +33,7 @@ RUF006.py:74:26: RUF006 Store a reference to the return value of `asyncio.create
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006
|
||||
|
|
||||
|
||||
RUF006.py:97:5: RUF006 Store a reference to the return value of `asyncio.create_task`
|
||||
RUF006.py:97:5: RUF006 Store a reference to the return value of `loop.create_task`
|
||||
|
|
||||
95 | def f():
|
||||
96 | loop = asyncio.get_running_loop()
|
||||
@@ -41,4 +41,24 @@ RUF006.py:97:5: RUF006 Store a reference to the return value of `asyncio.create_
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006
|
||||
|
|
||||
|
||||
RUF006.py:170:5: RUF006 Store a reference to the return value of `loop.create_task`
|
||||
|
|
||||
168 | def f():
|
||||
169 | loop = asyncio.new_event_loop()
|
||||
170 | loop.create_task(main()) # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF006
|
||||
171 |
|
||||
172 | # Error
|
||||
|
|
||||
|
||||
RUF006.py:175:5: RUF006 Store a reference to the return value of `loop.create_task`
|
||||
|
|
||||
173 | def f():
|
||||
174 | loop = asyncio.get_event_loop()
|
||||
175 | loop.create_task(main()) # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF006
|
||||
176 |
|
||||
177 | # OK
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ pub fn derive_message_formats(_attr: TokenStream, item: TokenStream) -> TokenStr
|
||||
///
|
||||
/// Good:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```ignroe
|
||||
/// use ruff_macros::newtype_index;
|
||||
///
|
||||
/// #[newtype_index]
|
||||
/// #[derive(Ord, PartialOrd)]
|
||||
/// struct MyIndex;
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics" }
|
||||
|
||||
@@ -935,7 +935,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`StatementVisitor`] that collects all `return` statements in a function or method.
|
||||
/// A [`Visitor`] that collects all `return` statements in a function or method.
|
||||
#[derive(Default)]
|
||||
pub struct ReturnStatementVisitor<'a> {
|
||||
pub returns: Vec<&'a ast::StmtReturn>,
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
|
||||
@@ -1705,6 +1705,7 @@ class Foo:
|
||||
assert_round_trip!(r#"f"{ chr(65) = !s}""#);
|
||||
assert_round_trip!(r#"f"{ chr(65) = !r}""#);
|
||||
assert_round_trip!(r#"f"{ chr(65) = :#x}""#);
|
||||
assert_round_trip!(r#"f"{ ( chr(65) ) = }""#);
|
||||
assert_round_trip!(r#"f"{a=!r:0.05f}""#);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest= false
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_formatter = { path = "../ruff_formatter" }
|
||||
|
||||
@@ -4,4 +4,8 @@ ij_formatter_enabled = false
|
||||
|
||||
["range_formatting/*.py"]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
||||
|
||||
[docstring_tab_indentation.py]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
||||
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_tab_indentation.options.json
vendored
Normal file
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_tab_indentation.options.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"indent_style": "tab",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"indent_style": "tab",
|
||||
"indent_width": 8
|
||||
}
|
||||
]
|
||||
72
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_tab_indentation.py
vendored
Normal file
72
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_tab_indentation.py
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# Tests the behavior of the formatter when it comes to tabs inside docstrings
|
||||
# when using `indent_style="tab`
|
||||
|
||||
# The example below uses tabs exclusively. The formatter should preserve the tab indentation
|
||||
# of `arg1`.
|
||||
def tab_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with 2 tabs in front
|
||||
"""
|
||||
|
||||
# The `arg1` is intended with spaces. The formatter should not change the spaces to a tab
|
||||
# because it must assume that the spaces are used for alignment and not indentation.
|
||||
def space_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
def under_indented(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
def under_indented_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
def spaces_tabs_over_indent(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
# The docstring itself is indented with spaces but the argument is indented by a tab.
|
||||
# Keep the tab indentation of the argument, convert th docstring indent to tabs.
|
||||
def space_indented_docstring_containing_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg
|
||||
"""
|
||||
|
||||
|
||||
# The docstring uses tabs, spaces, tabs indentation.
|
||||
# Fallback to use space indentation
|
||||
def mixed_indentation(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The example shows an ascii art. The formatter should not change the spaces
|
||||
# to tabs because it breaks the ASCII art when inspecting the docstring with `inspect.cleandoc(ascii_art.__doc__)`
|
||||
# when using an indent width other than 8.
|
||||
def ascii_art():
|
||||
r"""
|
||||
Look at this beautiful tree.
|
||||
|
||||
a
|
||||
/ \
|
||||
b c
|
||||
/ \
|
||||
d e
|
||||
"""
|
||||
|
||||
|
||||
8
crates/ruff_python_formatter/resources/test/fixtures/ruff/notebook_docstring.options.json
vendored
Normal file
8
crates/ruff_python_formatter/resources/test/fixtures/ruff/notebook_docstring.options.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"source_type": "Ipynb"
|
||||
},
|
||||
{
|
||||
"source_type": "Python"
|
||||
}
|
||||
]
|
||||
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/notebook_docstring.py
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/notebook_docstring.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
This looks like a docstring but is not in a notebook because notebooks can't be imported as a module.
|
||||
Ruff should leave it as is
|
||||
""";
|
||||
|
||||
"another normal string"
|
||||
@@ -13,10 +13,11 @@ 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_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_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,
|
||||
};
|
||||
use crate::expression::OperatorPrecedence;
|
||||
use crate::prelude::*;
|
||||
@@ -287,7 +288,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();
|
||||
@@ -481,7 +482,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(())
|
||||
@@ -527,6 +528,12 @@ 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.
|
||||
///
|
||||
@@ -642,7 +649,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 by the lowest precedence operators (and it either splits
|
||||
/// In other words: It splits the line before 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
|
||||
@@ -678,59 +685,64 @@ 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 self.operators() {
|
||||
if operator_part.precedence() == lowest_precedence {
|
||||
let left = self.between_operators(last_operator, index);
|
||||
let right = self.after_operator(index);
|
||||
for (index, operator_part) in lowest_precedence_operators {
|
||||
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 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);
|
||||
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)?;
|
||||
}
|
||||
} else {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
|
||||
last_operator = Some(index);
|
||||
}
|
||||
|
||||
// Format the last right side
|
||||
@@ -745,9 +757,11 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -379,6 +379,42 @@ 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
|
||||
|
||||
@@ -248,6 +248,12 @@ pub enum QuoteStyle {
|
||||
Preserve,
|
||||
}
|
||||
|
||||
impl QuoteStyle {
|
||||
pub const fn is_preserve(self) -> bool {
|
||||
matches!(self, QuoteStyle::Preserve)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for QuoteStyle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
||||
@@ -2,8 +2,7 @@ use ruff_python_ast::BytesLiteral;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
|
||||
use crate::string::{Quoting, StringPart};
|
||||
use crate::string::{StringNormalizer, StringPart};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatBytesLiteral;
|
||||
@@ -12,14 +11,9 @@ impl FormatNodeRule<BytesLiteral> for FormatBytesLiteral {
|
||||
fn fmt_fields(&self, item: &BytesLiteral, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let locator = f.context().locator();
|
||||
|
||||
StringPart::from_source(item.range(), &locator)
|
||||
.normalize(
|
||||
Quoting::CanChange,
|
||||
&locator,
|
||||
f.options().quote_style(),
|
||||
f.context().docstring(),
|
||||
is_hex_codes_in_unicode_sequences_enabled(f.context()),
|
||||
)
|
||||
StringNormalizer::from_context(f.context())
|
||||
.with_preferred_quote_style(f.options().quote_style())
|
||||
.normalize(&StringPart::from_source(item.range(), &locator), &locator)
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ use ruff_python_ast::FString;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
|
||||
use crate::string::{Quoting, StringPart};
|
||||
use crate::string::{Quoting, StringNormalizer, StringPart};
|
||||
|
||||
/// Formats an f-string which is part of a larger f-string expression.
|
||||
///
|
||||
@@ -26,13 +25,12 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let locator = f.context().locator();
|
||||
|
||||
let result = StringPart::from_source(self.value.range(), &locator)
|
||||
let result = StringNormalizer::from_context(f.context())
|
||||
.with_quoting(self.quoting)
|
||||
.with_preferred_quote_style(f.options().quote_style())
|
||||
.normalize(
|
||||
self.quoting,
|
||||
&StringPart::from_source(self.value.range(), &locator),
|
||||
&locator,
|
||||
f.options().quote_style(),
|
||||
f.context().docstring(),
|
||||
is_hex_codes_in_unicode_sequences_enabled(f.context()),
|
||||
)
|
||||
.fmt(f);
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ use ruff_python_ast::StringLiteral;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
|
||||
use crate::string::{docstring, Quoting, StringPart};
|
||||
use crate::string::{docstring, Quoting, StringNormalizer, StringPart};
|
||||
use crate::QuoteStyle;
|
||||
|
||||
pub(crate) struct FormatStringLiteral<'a> {
|
||||
@@ -50,20 +49,22 @@ impl Format<PyFormatContext<'_>> for FormatStringLiteral<'_> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let locator = f.context().locator();
|
||||
|
||||
let quote_style = if self.layout.is_docstring() {
|
||||
// Per PEP 8 and PEP 257, always prefer double quotes for docstrings
|
||||
let quote_style = f.options().quote_style();
|
||||
let quote_style = if self.layout.is_docstring() && !quote_style.is_preserve() {
|
||||
// Per PEP 8 and PEP 257, always prefer double quotes for docstrings,
|
||||
// except when using quote-style=preserve
|
||||
QuoteStyle::Double
|
||||
} else {
|
||||
f.options().quote_style()
|
||||
quote_style
|
||||
};
|
||||
|
||||
let normalized = StringPart::from_source(self.value.range(), &locator).normalize(
|
||||
self.layout.quoting(),
|
||||
&locator,
|
||||
quote_style,
|
||||
f.context().docstring(),
|
||||
is_hex_codes_in_unicode_sequences_enabled(f.context()),
|
||||
);
|
||||
let normalized = StringNormalizer::from_context(f.context())
|
||||
.with_quoting(self.layout.quoting())
|
||||
.with_preferred_quote_style(quote_style)
|
||||
.normalize(
|
||||
&StringPart::from_source(self.value.range(), &locator),
|
||||
&locator,
|
||||
);
|
||||
|
||||
if self.layout.is_docstring() {
|
||||
docstring::format(&normalized, f)
|
||||
|
||||
@@ -214,9 +214,9 @@ impl<'ast> PreorderVisitor<'ast> for FindEnclosingNode<'_, 'ast> {
|
||||
// Don't pick potential docstrings as the closest enclosing node because `suite.rs` than fails to identify them as
|
||||
// docstrings and docstring formatting won't kick in.
|
||||
// Format the enclosing node instead and slice the formatted docstring from the result.
|
||||
let is_maybe_docstring = node
|
||||
.as_stmt_expr()
|
||||
.is_some_and(|stmt| DocstringStmt::is_docstring_statement(stmt));
|
||||
let is_maybe_docstring = node.as_stmt_expr().is_some_and(|stmt| {
|
||||
DocstringStmt::is_docstring_statement(stmt, self.context.options().source_type())
|
||||
});
|
||||
|
||||
if is_maybe_docstring {
|
||||
return TraversalSignal::Skip;
|
||||
|
||||
@@ -103,7 +103,9 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
}
|
||||
|
||||
SuiteKind::Function => {
|
||||
if let Some(docstring) = DocstringStmt::try_from_statement(first, self.kind) {
|
||||
if let Some(docstring) =
|
||||
DocstringStmt::try_from_statement(first, self.kind, source_type)
|
||||
{
|
||||
SuiteChildStatement::Docstring(docstring)
|
||||
} else {
|
||||
SuiteChildStatement::Other(first)
|
||||
@@ -111,7 +113,9 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
}
|
||||
|
||||
SuiteKind::Class => {
|
||||
if let Some(docstring) = DocstringStmt::try_from_statement(first, self.kind) {
|
||||
if let Some(docstring) =
|
||||
DocstringStmt::try_from_statement(first, self.kind, source_type)
|
||||
{
|
||||
if !comments.has_leading(first)
|
||||
&& lines_before(first.start(), source) > 1
|
||||
&& !source_type.is_stub()
|
||||
@@ -143,7 +147,9 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
}
|
||||
SuiteKind::TopLevel => {
|
||||
if is_format_module_docstring_enabled(f.context()) {
|
||||
if let Some(docstring) = DocstringStmt::try_from_statement(first, self.kind) {
|
||||
if let Some(docstring) =
|
||||
DocstringStmt::try_from_statement(first, self.kind, source_type)
|
||||
{
|
||||
SuiteChildStatement::Docstring(docstring)
|
||||
} else {
|
||||
SuiteChildStatement::Other(first)
|
||||
@@ -184,7 +190,8 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
true
|
||||
} else if is_module_docstring_newlines_enabled(f.context())
|
||||
&& self.kind == SuiteKind::TopLevel
|
||||
&& DocstringStmt::try_from_statement(first.statement(), self.kind).is_some()
|
||||
&& DocstringStmt::try_from_statement(first.statement(), self.kind, source_type)
|
||||
.is_some()
|
||||
{
|
||||
// Only in preview mode, insert a newline after a module level docstring, but treat
|
||||
// it as a docstring otherwise. See: https://github.com/psf/black/pull/3932.
|
||||
@@ -734,7 +741,16 @@ pub(crate) struct DocstringStmt<'a> {
|
||||
|
||||
impl<'a> DocstringStmt<'a> {
|
||||
/// Checks if the statement is a simple string that can be formatted as a docstring
|
||||
fn try_from_statement(stmt: &'a Stmt, suite_kind: SuiteKind) -> Option<DocstringStmt<'a>> {
|
||||
fn try_from_statement(
|
||||
stmt: &'a Stmt,
|
||||
suite_kind: SuiteKind,
|
||||
source_type: PySourceType,
|
||||
) -> Option<DocstringStmt<'a>> {
|
||||
// Notebooks don't have a concept of modules, therefore, don't recognise the first string as the module docstring.
|
||||
if source_type.is_ipynb() && suite_kind == SuiteKind::TopLevel {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||
return None;
|
||||
};
|
||||
@@ -752,7 +768,11 @@ impl<'a> DocstringStmt<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_docstring_statement(stmt: &StmtExpr) -> bool {
|
||||
pub(crate) fn is_docstring_statement(stmt: &StmtExpr, source_type: PySourceType) -> bool {
|
||||
if source_type.is_ipynb() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = stmt.value.as_ref() {
|
||||
!value.is_implicit_concatenated()
|
||||
} else {
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// "reStructuredText."
|
||||
#![allow(clippy::doc_markdown)]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::{borrow::Cow, collections::VecDeque};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_formatter::printer::SourceMapGeneration;
|
||||
use ruff_python_parser::ParseError;
|
||||
|
||||
use {once_cell::sync::Lazy, regex::Regex};
|
||||
use {
|
||||
ruff_formatter::{write, FormatOptions, IndentStyle, LineWidth, Printed},
|
||||
@@ -80,9 +82,7 @@ use super::{NormalizedString, QuoteChar};
|
||||
/// ```
|
||||
///
|
||||
/// Tabs are counted by padding them to the next multiple of 8 according to
|
||||
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs). When
|
||||
/// we see indentation that contains a tab or any other none ascii-space whitespace we rewrite the
|
||||
/// string.
|
||||
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs).
|
||||
///
|
||||
/// Additionally, if any line in the docstring has less indentation than the docstring
|
||||
/// (effectively a negative indentation wrt. to the current level), we pad all lines to the
|
||||
@@ -104,6 +104,10 @@ use super::{NormalizedString, QuoteChar};
|
||||
/// line c
|
||||
/// """
|
||||
/// ```
|
||||
/// The indentation is rewritten to all-spaces when using [`IndentStyle::Space`].
|
||||
/// The formatter preserves tab-indentations when using [`IndentStyle::Tab`], but doesn't convert
|
||||
/// `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;
|
||||
|
||||
@@ -176,19 +180,19 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
|
||||
// align it with the docstring statement. Conversely, if all lines are over-indented, we strip
|
||||
// the extra indentation. We call this stripped indentation since it's relative to the block
|
||||
// indent printer-made indentation.
|
||||
let stripped_indentation_length = lines
|
||||
let stripped_indentation = lines
|
||||
.clone()
|
||||
// We don't want to count whitespace-only lines as miss-indented
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(indentation_length)
|
||||
.min()
|
||||
.map(Indentation::from_str)
|
||||
.min_by_key(|indentation| indentation.width())
|
||||
.unwrap_or_default();
|
||||
|
||||
DocstringLinePrinter {
|
||||
f,
|
||||
action_queue: VecDeque::new(),
|
||||
offset,
|
||||
stripped_indentation_length,
|
||||
stripped_indentation,
|
||||
already_normalized,
|
||||
quote_char: normalized.quotes.quote_char,
|
||||
code_example: CodeExample::default(),
|
||||
@@ -240,9 +244,9 @@ struct DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
/// printed.
|
||||
offset: TextSize,
|
||||
|
||||
/// Indentation alignment (in columns) based on the least indented line in the
|
||||
/// Indentation alignment based on the least indented line in the
|
||||
/// docstring.
|
||||
stripped_indentation_length: usize,
|
||||
stripped_indentation: Indentation,
|
||||
|
||||
/// Whether the docstring is overall already considered normalized. When it
|
||||
/// is, the formatter can take a fast path.
|
||||
@@ -345,7 +349,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
};
|
||||
// This looks suspicious, but it's consistent with the whitespace
|
||||
// normalization that will occur anyway.
|
||||
let indent = " ".repeat(min_indent);
|
||||
let indent = " ".repeat(min_indent.width());
|
||||
for docline in formatted_lines {
|
||||
self.print_one(
|
||||
&docline.map(|line| std::format!("{indent}{line}")),
|
||||
@@ -355,7 +359,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
CodeExampleKind::Markdown(fenced) => {
|
||||
// This looks suspicious, but it's consistent with the whitespace
|
||||
// normalization that will occur anyway.
|
||||
let indent = " ".repeat(fenced.opening_fence_indent);
|
||||
let indent = " ".repeat(fenced.opening_fence_indent.width());
|
||||
for docline in formatted_lines {
|
||||
self.print_one(
|
||||
&docline.map(|line| std::format!("{indent}{line}")),
|
||||
@@ -387,12 +391,58 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
};
|
||||
}
|
||||
|
||||
let tab_or_non_ascii_space = trim_end
|
||||
.chars()
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.any(|c| c != ' ');
|
||||
let indent_offset = match self.f.options().indent_style() {
|
||||
// Normalize all indent to spaces.
|
||||
IndentStyle::Space => {
|
||||
let tab_or_non_ascii_space = trim_end
|
||||
.chars()
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.any(|c| c != ' ');
|
||||
|
||||
if tab_or_non_ascii_space {
|
||||
if tab_or_non_ascii_space {
|
||||
None
|
||||
} else {
|
||||
// It's guaranteed that the `indent` is all spaces because `tab_or_non_ascii_space` is
|
||||
// `false` (indent contains neither tabs nor non-space whitespace).
|
||||
let stripped_indentation_len = self.stripped_indentation.text_len();
|
||||
|
||||
// Take the string with the trailing whitespace removed, then also
|
||||
// skip the leading whitespace.
|
||||
Some(stripped_indentation_len)
|
||||
}
|
||||
}
|
||||
IndentStyle::Tab => {
|
||||
let line_indent = Indentation::from_str(trim_end);
|
||||
|
||||
let non_ascii_whitespace = trim_end
|
||||
.chars()
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.any(|c| !matches!(c, ' ' | '\t'));
|
||||
|
||||
let trimmed = line_indent.trim_start(self.stripped_indentation);
|
||||
|
||||
// Preserve tabs that are used for indentation, but only if the indent isn't
|
||||
// * a mix of tabs and spaces
|
||||
// * the `stripped_indentation` is a prefix of the line's indent
|
||||
// * the trimmed indent isn't spaces followed by tabs because that would result in a
|
||||
// mixed tab, spaces, tab indentation, resulting in instabilities.
|
||||
let preserve_indent = !non_ascii_whitespace
|
||||
&& trimmed.is_some_and(|trimmed| !trimmed.is_spaces_tabs());
|
||||
preserve_indent.then_some(self.stripped_indentation.text_len())
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(indent_offset) = indent_offset {
|
||||
// Take the string with the trailing whitespace removed, then also
|
||||
// skip the leading whitespace.
|
||||
if self.already_normalized {
|
||||
let trimmed_line_range =
|
||||
TextRange::at(line.offset, trim_end.text_len()).add_start(indent_offset);
|
||||
source_text_slice(trimmed_line_range).fmt(self.f)?;
|
||||
} else {
|
||||
text(&trim_end[indent_offset.to_usize()..]).fmt(self.f)?;
|
||||
}
|
||||
} else {
|
||||
// We strip the indentation that is shared with the docstring
|
||||
// statement, unless a line was indented less than the docstring
|
||||
// statement, in which case we strip only this much indentation to
|
||||
@@ -400,24 +450,11 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
// overindented, in which case we strip the additional whitespace
|
||||
// (see example in [`format_docstring`] doc comment). We then
|
||||
// prepend the in-docstring indentation to the string.
|
||||
let indent_len = indentation_length(trim_end) - self.stripped_indentation_length;
|
||||
let indent_len =
|
||||
Indentation::from_str(trim_end).width() - self.stripped_indentation.width();
|
||||
let in_docstring_indent = " ".repeat(indent_len) + trim_end.trim_start();
|
||||
text(&in_docstring_indent).fmt(self.f)?;
|
||||
} else {
|
||||
// It's guaranteed that the `indent` is all spaces because `tab_or_non_ascii_space` is
|
||||
// `false` (indent contains neither tabs nor non-space whitespace).
|
||||
|
||||
// Take the string with the trailing whitespace removed, then also
|
||||
// skip the leading whitespace.
|
||||
let trimmed_line_range = TextRange::at(line.offset, trim_end.text_len())
|
||||
.add_start(TextSize::try_from(self.stripped_indentation_length).unwrap());
|
||||
if self.already_normalized {
|
||||
source_text_slice(trimmed_line_range).fmt(self.f)?;
|
||||
} else {
|
||||
// All indents are ascii spaces, so the slicing is correct.
|
||||
text(&trim_end[self.stripped_indentation_length..]).fmt(self.f)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// We handled the case that the closing quotes are on their own line
|
||||
// above (the last line is empty except for whitespace). If they are on
|
||||
@@ -898,8 +935,7 @@ struct CodeExampleRst<'src> {
|
||||
/// The lines that have been seen so far that make up the block.
|
||||
lines: Vec<CodeExampleLine<'src>>,
|
||||
|
||||
/// The indent of the line "opening" this block measured via
|
||||
/// `indentation_length` (in columns).
|
||||
/// The indent of the line "opening" this block in columns.
|
||||
///
|
||||
/// It can either be the indent of a line ending with `::` (for a literal
|
||||
/// block) or the indent of a line starting with `.. ` (a directive).
|
||||
@@ -907,9 +943,9 @@ struct CodeExampleRst<'src> {
|
||||
/// The content body of a block needs to be indented more than the line
|
||||
/// opening the block, so we use this indentation to look for indentation
|
||||
/// that is "more than" it.
|
||||
opening_indent: usize,
|
||||
opening_indent: Indentation,
|
||||
|
||||
/// The minimum indent of the block measured via `indentation_length`.
|
||||
/// The minimum indent of the block in columns.
|
||||
///
|
||||
/// This is `None` until the first such line is seen. If no such line is
|
||||
/// found, then we consider it an invalid block and bail out of trying to
|
||||
@@ -926,7 +962,7 @@ struct CodeExampleRst<'src> {
|
||||
/// When the code snippet has been extracted, it is re-built before being
|
||||
/// reformatted. The minimum indent is stripped from each line when it is
|
||||
/// re-built.
|
||||
min_indent: Option<usize>,
|
||||
min_indent: Option<Indentation>,
|
||||
|
||||
/// Whether this is a directive block or not. When not a directive, this is
|
||||
/// a literal block. The main difference between them is that they start
|
||||
@@ -975,7 +1011,7 @@ impl<'src> CodeExampleRst<'src> {
|
||||
}
|
||||
Some(CodeExampleRst {
|
||||
lines: vec![],
|
||||
opening_indent: indentation_length(opening_indent),
|
||||
opening_indent: Indentation::from_str(opening_indent),
|
||||
min_indent: None,
|
||||
is_directive: false,
|
||||
})
|
||||
@@ -1013,7 +1049,7 @@ impl<'src> CodeExampleRst<'src> {
|
||||
}
|
||||
Some(CodeExampleRst {
|
||||
lines: vec![],
|
||||
opening_indent: indentation_length(original.line),
|
||||
opening_indent: Indentation::from_str(original.line),
|
||||
min_indent: None,
|
||||
is_directive: true,
|
||||
})
|
||||
@@ -1033,7 +1069,7 @@ impl<'src> CodeExampleRst<'src> {
|
||||
line.code = if line.original.line.trim().is_empty() {
|
||||
""
|
||||
} else {
|
||||
indentation_trim(min_indent, line.original.line)
|
||||
min_indent.trim_start_str(line.original.line)
|
||||
};
|
||||
}
|
||||
&self.lines
|
||||
@@ -1070,7 +1106,9 @@ impl<'src> CodeExampleRst<'src> {
|
||||
// an empty line followed by an unindented non-empty line.
|
||||
if let Some(next) = original.next {
|
||||
let (next_indent, next_rest) = indent_with_suffix(next);
|
||||
if !next_rest.is_empty() && indentation_length(next_indent) <= self.opening_indent {
|
||||
if !next_rest.is_empty()
|
||||
&& Indentation::from_str(next_indent) <= self.opening_indent
|
||||
{
|
||||
self.push_format_action(queue);
|
||||
return None;
|
||||
}
|
||||
@@ -1082,7 +1120,7 @@ impl<'src> CodeExampleRst<'src> {
|
||||
queue.push_back(CodeExampleAddAction::Kept);
|
||||
return Some(self);
|
||||
}
|
||||
let indent_len = indentation_length(indent);
|
||||
let indent_len = Indentation::from_str(indent);
|
||||
if indent_len <= self.opening_indent {
|
||||
// If we find an unindented non-empty line at the same (or less)
|
||||
// indentation of the opening line at this point, then we know it
|
||||
@@ -1144,7 +1182,7 @@ impl<'src> CodeExampleRst<'src> {
|
||||
queue.push_back(CodeExampleAddAction::Print { original });
|
||||
return Some(self);
|
||||
}
|
||||
let min_indent = indentation_length(indent);
|
||||
let min_indent = Indentation::from_str(indent);
|
||||
// At this point, we found a non-empty line. The only thing we require
|
||||
// is that its indentation is strictly greater than the indentation of
|
||||
// the line containing the `::`. Otherwise, we treat this as an invalid
|
||||
@@ -1218,12 +1256,11 @@ struct CodeExampleMarkdown<'src> {
|
||||
/// The lines that have been seen so far that make up the block.
|
||||
lines: Vec<CodeExampleLine<'src>>,
|
||||
|
||||
/// The indent of the line "opening" fence of this block measured via
|
||||
/// `indentation_length` (in columns).
|
||||
/// The indent of the line "opening" fence of this block in columns.
|
||||
///
|
||||
/// This indentation is trimmed from the indentation of every line in the
|
||||
/// body of the code block,
|
||||
opening_fence_indent: usize,
|
||||
opening_fence_indent: Indentation,
|
||||
|
||||
/// The kind of fence, backticks or tildes, used for this block. We need to
|
||||
/// keep track of which kind was used to open the block in order to look
|
||||
@@ -1292,7 +1329,7 @@ impl<'src> CodeExampleMarkdown<'src> {
|
||||
};
|
||||
Some(CodeExampleMarkdown {
|
||||
lines: vec![],
|
||||
opening_fence_indent: indentation_length(opening_fence_indent),
|
||||
opening_fence_indent: Indentation::from_str(opening_fence_indent),
|
||||
fence_kind,
|
||||
fence_len,
|
||||
})
|
||||
@@ -1325,7 +1362,7 @@ impl<'src> CodeExampleMarkdown<'src> {
|
||||
// its indent normalized. And, at the time of writing, a subsequent
|
||||
// formatting run undoes this indentation, thus violating idempotency.
|
||||
if !original.line.trim_whitespace().is_empty()
|
||||
&& indentation_length(original.line) < self.opening_fence_indent
|
||||
&& Indentation::from_str(original.line) < self.opening_fence_indent
|
||||
{
|
||||
queue.push_back(self.into_reset_action());
|
||||
queue.push_back(CodeExampleAddAction::Print { original });
|
||||
@@ -1371,7 +1408,7 @@ impl<'src> CodeExampleMarkdown<'src> {
|
||||
// Unlike reStructuredText blocks, for Markdown fenced code blocks, the
|
||||
// indentation that we want to strip from each line is known when the
|
||||
// block is opened. So we can strip it as we collect lines.
|
||||
let code = indentation_trim(self.opening_fence_indent, original.line);
|
||||
let code = self.opening_fence_indent.trim_start_str(original.line);
|
||||
self.lines.push(CodeExampleLine { original, code });
|
||||
}
|
||||
|
||||
@@ -1486,7 +1523,6 @@ enum CodeExampleAddAction<'src> {
|
||||
/// results in that code example becoming invalid. In this case,
|
||||
/// we don't want to treat it as a code example, but instead write
|
||||
/// back the lines to the docstring unchanged.
|
||||
#[allow(dead_code)] // FIXME: remove when reStructuredText support is added
|
||||
Reset {
|
||||
/// The lines of code that we collected but should be printed back to
|
||||
/// the docstring as-is and not formatted.
|
||||
@@ -1537,53 +1573,241 @@ fn needs_chaperone_space(normalized: &NormalizedString, trim_end: &str) -> bool
|
||||
|| trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
|
||||
}
|
||||
|
||||
/// Returns the indentation's visual width in columns/spaces.
|
||||
///
|
||||
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
|
||||
/// to the next multiple of 8. This is effectively a port of
|
||||
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
|
||||
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61).
|
||||
fn indentation_length(line: &str) -> usize {
|
||||
let mut indentation = 0usize;
|
||||
for char in line.chars() {
|
||||
if char == '\t' {
|
||||
// Pad to the next multiple of tab_width
|
||||
indentation += 8 - (indentation.rem_euclid(8));
|
||||
} else if char.is_whitespace() {
|
||||
indentation += char.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
indentation
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Indentation {
|
||||
/// Space only indentation or an empty indentation.
|
||||
///
|
||||
/// The value is the number of spaces.
|
||||
Spaces(usize),
|
||||
|
||||
/// Tabs only indentation.
|
||||
Tabs(usize),
|
||||
|
||||
/// Indentation that uses tabs followed by spaces.
|
||||
/// Also known as smart tabs where tabs are used for indents, and spaces for alignment.
|
||||
TabSpaces { tabs: usize, spaces: usize },
|
||||
|
||||
/// Indentation that uses spaces followed by tabs.
|
||||
SpacesTabs { spaces: usize, tabs: usize },
|
||||
|
||||
/// Mixed indentation of tabs and spaces.
|
||||
Mixed {
|
||||
/// The visual width of the indentation in columns.
|
||||
width: usize,
|
||||
|
||||
/// The length of the indentation in bytes
|
||||
len: TextSize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Trims at most `indent_len` indentation from the beginning of `line`.
|
||||
///
|
||||
/// This treats indentation in precisely the same way as `indentation_length`.
|
||||
/// As such, it is expected that `indent_len` is computed from
|
||||
/// `indentation_length`. This is useful when one needs to trim some minimum
|
||||
/// level of indentation from a code snippet collected from a docstring before
|
||||
/// attempting to reformat it.
|
||||
fn indentation_trim(indent_len: usize, line: &str) -> &str {
|
||||
let mut seen_indent_len = 0;
|
||||
let mut trimmed = line;
|
||||
for char in line.chars() {
|
||||
if seen_indent_len >= indent_len {
|
||||
return trimmed;
|
||||
impl Indentation {
|
||||
const TAB_INDENT_WIDTH: usize = 8;
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
let mut iter = s.chars().peekable();
|
||||
|
||||
let spaces = iter.peeking_take_while(|c| *c == ' ').count();
|
||||
let tabs = iter.peeking_take_while(|c| *c == '\t').count();
|
||||
|
||||
if tabs == 0 {
|
||||
// No indent, or spaces only indent
|
||||
return Self::Spaces(spaces);
|
||||
}
|
||||
if char == '\t' {
|
||||
// Pad to the next multiple of tab_width
|
||||
seen_indent_len += 8 - (seen_indent_len.rem_euclid(8));
|
||||
trimmed = &trimmed[1..];
|
||||
} else if char.is_whitespace() {
|
||||
seen_indent_len += char.len_utf8();
|
||||
trimmed = &trimmed[char.len_utf8()..];
|
||||
} else {
|
||||
break;
|
||||
|
||||
let align_spaces = iter.peeking_take_while(|c| *c == ' ').count();
|
||||
|
||||
if spaces == 0 {
|
||||
if align_spaces == 0 {
|
||||
return Self::Tabs(tabs);
|
||||
}
|
||||
|
||||
// At this point it's either a smart tab (tabs followed by spaces) or a wild mix of tabs and spaces.
|
||||
if iter.peek().copied() != Some('\t') {
|
||||
return Self::TabSpaces {
|
||||
tabs,
|
||||
spaces: align_spaces,
|
||||
};
|
||||
}
|
||||
} else if align_spaces == 0 {
|
||||
return Self::SpacesTabs { spaces, tabs };
|
||||
}
|
||||
|
||||
// Sequence of spaces.. tabs, spaces, tabs...
|
||||
let mut width = spaces + tabs * Self::TAB_INDENT_WIDTH + align_spaces;
|
||||
// SAFETY: Safe because Ruff doesn't support files larger than 4GB.
|
||||
let mut len = TextSize::try_from(spaces + tabs + align_spaces).unwrap();
|
||||
|
||||
for char in iter {
|
||||
if char == '\t' {
|
||||
// Pad to the next multiple of tab_width
|
||||
width += Self::TAB_INDENT_WIDTH - (width.rem_euclid(Self::TAB_INDENT_WIDTH));
|
||||
len += '\t'.text_len();
|
||||
} else if char.is_whitespace() {
|
||||
width += char.len_utf8();
|
||||
len += char.text_len();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Mixed tabs and spaces
|
||||
Self::Mixed { width, len }
|
||||
}
|
||||
|
||||
/// Returns the indentation's visual width in columns/spaces.
|
||||
///
|
||||
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
|
||||
/// to the next multiple of 8. This is effectively a port of
|
||||
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
|
||||
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61).
|
||||
const fn width(self) -> usize {
|
||||
match self {
|
||||
Self::Spaces(count) => count,
|
||||
Self::Tabs(count) => count * Self::TAB_INDENT_WIDTH,
|
||||
Self::TabSpaces { tabs, spaces } => tabs * Self::TAB_INDENT_WIDTH + spaces,
|
||||
Self::SpacesTabs { spaces, tabs } => {
|
||||
let mut indent = spaces;
|
||||
indent += Self::TAB_INDENT_WIDTH - indent.rem_euclid(Self::TAB_INDENT_WIDTH);
|
||||
indent + (tabs - 1) * Self::TAB_INDENT_WIDTH
|
||||
}
|
||||
Self::Mixed { width, .. } => width,
|
||||
}
|
||||
}
|
||||
trimmed
|
||||
|
||||
/// Returns the length of the indentation in bytes.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the indentation is longer than 4GB.
|
||||
fn text_len(self) -> TextSize {
|
||||
let len = match self {
|
||||
Self::Spaces(count) => count,
|
||||
Self::Tabs(count) => count,
|
||||
Self::TabSpaces { tabs, spaces } => tabs + spaces,
|
||||
Self::SpacesTabs { spaces, tabs } => spaces + tabs,
|
||||
Self::Mixed { len, .. } => return len,
|
||||
};
|
||||
|
||||
TextSize::try_from(len).unwrap()
|
||||
}
|
||||
|
||||
/// Trims the indent of `rhs` by `self`.
|
||||
///
|
||||
/// Returns `None` if `self` is not a prefix of `rhs` or either `self` or `rhs` use mixed indentation.
|
||||
fn trim_start(self, rhs: Self) -> Option<Self> {
|
||||
let (left_tabs, left_spaces) = match self {
|
||||
Self::Spaces(spaces) => (0usize, spaces),
|
||||
Self::Tabs(tabs) => (tabs, 0usize),
|
||||
Self::TabSpaces { tabs, spaces } => (tabs, spaces),
|
||||
// Handle spaces here because it is the only indent where the spaces come before the tabs.
|
||||
Self::SpacesTabs {
|
||||
spaces: left_spaces,
|
||||
tabs: left_tabs,
|
||||
} => {
|
||||
return match rhs {
|
||||
Self::Spaces(right_spaces) => {
|
||||
left_spaces.checked_sub(right_spaces).map(|spaces| {
|
||||
if spaces == 0 {
|
||||
Self::Tabs(left_tabs)
|
||||
} else {
|
||||
Self::SpacesTabs {
|
||||
tabs: left_tabs,
|
||||
spaces,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Self::SpacesTabs {
|
||||
spaces: right_spaces,
|
||||
tabs: right_tabs,
|
||||
} => left_spaces.checked_sub(right_spaces).and_then(|spaces| {
|
||||
let tabs = left_tabs.checked_sub(right_tabs)?;
|
||||
|
||||
Some(if spaces == 0 {
|
||||
if tabs == 0 {
|
||||
Self::Spaces(0)
|
||||
} else {
|
||||
Self::Tabs(tabs)
|
||||
}
|
||||
} else {
|
||||
Self::SpacesTabs { spaces, tabs }
|
||||
})
|
||||
}),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Self::Mixed { .. } => return None,
|
||||
};
|
||||
|
||||
let (right_tabs, right_spaces) = match rhs {
|
||||
Self::Spaces(spaces) => (0usize, spaces),
|
||||
Self::Tabs(tabs) => (tabs, 0usize),
|
||||
Self::TabSpaces { tabs, spaces } => (tabs, spaces),
|
||||
Self::SpacesTabs { .. } | Self::Mixed { .. } => return None,
|
||||
};
|
||||
|
||||
let tabs = left_tabs.checked_sub(right_tabs)?;
|
||||
let spaces = left_spaces.checked_sub(right_spaces)?;
|
||||
|
||||
Some(if tabs == 0 {
|
||||
Self::Spaces(spaces)
|
||||
} else if spaces == 0 {
|
||||
Self::Tabs(tabs)
|
||||
} else {
|
||||
Self::TabSpaces { tabs, spaces }
|
||||
})
|
||||
}
|
||||
|
||||
/// Trims at most `indent_len` indentation from the beginning of `line`.
|
||||
///
|
||||
/// This is useful when one needs to trim some minimum
|
||||
/// level of indentation from a code snippet collected from a docstring before
|
||||
/// attempting to reformat it.
|
||||
fn trim_start_str(self, line: &str) -> &str {
|
||||
let mut seen_indent_len = 0;
|
||||
let mut trimmed = line;
|
||||
let indent_len = self.width();
|
||||
|
||||
for char in line.chars() {
|
||||
if seen_indent_len >= indent_len {
|
||||
return trimmed;
|
||||
}
|
||||
if char == '\t' {
|
||||
// Pad to the next multiple of tab_width
|
||||
seen_indent_len +=
|
||||
Self::TAB_INDENT_WIDTH - (seen_indent_len.rem_euclid(Self::TAB_INDENT_WIDTH));
|
||||
trimmed = &trimmed[1..];
|
||||
} else if char.is_whitespace() {
|
||||
seen_indent_len += char.len_utf8();
|
||||
trimmed = &trimmed[char.len_utf8()..];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
trimmed
|
||||
}
|
||||
|
||||
const fn is_spaces_tabs(self) -> bool {
|
||||
matches!(self, Self::SpacesTabs { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Indentation {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.width().cmp(&other.width()))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Indentation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.width() == other.width()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Indentation {
|
||||
fn default() -> Self {
|
||||
Self::Spaces(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the indentation of the given line and everything following it.
|
||||
@@ -1613,14 +1837,13 @@ fn is_rst_option(line: &str) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::indentation_length;
|
||||
use crate::string::docstring::Indentation;
|
||||
|
||||
#[test]
|
||||
fn test_indentation_like_black() {
|
||||
assert_eq!(indentation_length("\t \t \t"), 24);
|
||||
assert_eq!(indentation_length("\t \t"), 24);
|
||||
assert_eq!(indentation_length("\t\t\t"), 24);
|
||||
assert_eq!(indentation_length(" "), 4);
|
||||
assert_eq!(Indentation::from_str("\t \t \t").width(), 24);
|
||||
assert_eq!(Indentation::from_str("\t \t").width(), 24);
|
||||
assert_eq!(Indentation::from_str("\t\t\t").width(), 24);
|
||||
assert_eq!(Indentation::from_str(" ").width(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ 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;
|
||||
|
||||
pub(crate) mod docstring;
|
||||
@@ -291,22 +292,54 @@ impl StringPart {
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the strings preferred quotes and normalizes its content.
|
||||
///
|
||||
/// The parent docstring quote style should be set when formatting a code
|
||||
/// snippet within the docstring. The quote style should correspond to the
|
||||
/// style of quotes used by said docstring. Normalization will ensure the
|
||||
/// quoting styles don't conflict.
|
||||
pub(crate) fn normalize<'a>(
|
||||
self,
|
||||
quoting: Quoting,
|
||||
locator: &'a Locator,
|
||||
configured_style: QuoteStyle,
|
||||
parent_docstring_quote_char: Option<QuoteChar>,
|
||||
normalize_hex: bool,
|
||||
) -> NormalizedString<'a> {
|
||||
/// Returns the prefix of the string part.
|
||||
pub(crate) const fn prefix(&self) -> StringPrefix {
|
||||
self.prefix
|
||||
}
|
||||
|
||||
/// Returns the surrounding quotes of the string part.
|
||||
pub(crate) const fn quotes(&self) -> StringQuotes {
|
||||
self.quotes
|
||||
}
|
||||
|
||||
/// Returns the range of the string's content in the source (minus prefix and quotes).
|
||||
pub(crate) const fn content_range(&self) -> TextRange {
|
||||
self.content_range
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
let preferred_style = if self.quotes.triple {
|
||||
// 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.
|
||||
@@ -352,37 +385,49 @@ impl StringPart {
|
||||
// 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) = parent_docstring_quote_char {
|
||||
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 {
|
||||
configured_style
|
||||
self.preferred_quote_style
|
||||
};
|
||||
|
||||
let raw_content = &locator.slice(self.content_range);
|
||||
|
||||
let quotes = match quoting {
|
||||
Quoting::Preserve => self.quotes,
|
||||
match self.quoting {
|
||||
Quoting::Preserve => string.quotes(),
|
||||
Quoting::CanChange => {
|
||||
if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) {
|
||||
if self.prefix.is_raw_string() {
|
||||
choose_quotes_raw(raw_content, self.quotes, preferred_quote)
|
||||
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(raw_content, self.quotes, preferred_quote)
|
||||
choose_quotes_impl(raw_content, string.quotes(), preferred_quote)
|
||||
}
|
||||
} else {
|
||||
self.quotes
|
||||
string.quotes()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let normalized = normalize_string(raw_content, quotes, self.prefix, normalize_hex);
|
||||
/// 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: self.prefix,
|
||||
content_range: self.content_range,
|
||||
prefix: string.prefix(),
|
||||
content_range: string.content_range(),
|
||||
text: normalized,
|
||||
quotes,
|
||||
}
|
||||
@@ -509,7 +554,7 @@ impl Format<PyFormatContext<'_>> for StringPrefix {
|
||||
/// 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_raw(
|
||||
fn choose_quotes_for_raw_string(
|
||||
input: &str,
|
||||
quotes: StringQuotes,
|
||||
preferred_quote: QuoteChar,
|
||||
@@ -568,7 +613,11 @@ fn choose_quotes_raw(
|
||||
/// 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(input: &str, quotes: StringQuotes, preferred_quote: QuoteChar) -> StringQuotes {
|
||||
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;
|
||||
@@ -777,7 +826,7 @@ impl TryFrom<char> for QuoteChar {
|
||||
/// with the provided [`StringQuotes`] style.
|
||||
///
|
||||
/// Returns the normalized string and whether it contains new lines.
|
||||
fn normalize_string(
|
||||
pub(crate) fn normalize_string(
|
||||
input: &str,
|
||||
quotes: StringQuotes,
|
||||
prefix: StringPrefix,
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
---
|
||||
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,7 +76,11 @@
|
||||
@@ -72,14 +76,18 @@
|
||||
body,
|
||||
parameters.children[-1], # )2
|
||||
]
|
||||
@@ -204,7 +204,19 @@ instruction()#comment with bad spacing
|
||||
+ ] # type: ignore
|
||||
if (
|
||||
self._proc is not None
|
||||
# has the child process finished?
|
||||
- # 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
|
||||
@@ -115,7 +123,9 @@
|
||||
arg3=True,
|
||||
)
|
||||
@@ -228,14 +240,23 @@ instruction()#comment with bad spacing
|
||||
)
|
||||
|
||||
|
||||
@@ -158,7 +171,10 @@
|
||||
@@ -151,14 +164,17 @@
|
||||
[
|
||||
CONFIG_FILE,
|
||||
]
|
||||
- + SHARED_CONFIG_FILES
|
||||
- + USER_CONFIG_FILES
|
||||
+ + SHARED_CONFIG_FILES
|
||||
+ + USER_CONFIG_FILES
|
||||
) # type: Final
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -330,11 +351,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
|
||||
@@ -411,8 +432,8 @@ CONFIG_FILES = (
|
||||
[
|
||||
CONFIG_FILE,
|
||||
]
|
||||
+ SHARED_CONFIG_FILES
|
||||
+ USER_CONFIG_FILES
|
||||
+ SHARED_CONFIG_FILES
|
||||
+ USER_CONFIG_FILES
|
||||
) # type: Final
|
||||
|
||||
|
||||
@@ -420,7 +441,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,6 +141,23 @@ 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 @@
|
||||
)
|
||||
|
||||
@@ -243,11 +260,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,6 +146,40 @@ 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
|
||||
@@ -244,21 +278,21 @@ if (
|
||||
# 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")
|
||||
```
|
||||
|
||||
@@ -193,6 +193,26 @@ 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"
|
||||
@@ -222,12 +242,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,9 +162,7 @@
|
||||
@@ -161,21 +162,19 @@
|
||||
8 STORE_ATTR 0 (x)
|
||||
10 LOAD_CONST 0 (None)
|
||||
12 RETURN_VALUE
|
||||
@@ -238,6 +258,29 @@ 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
|
||||
@@ -267,8 +310,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
|
||||
@@ -286,7 +329,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:
|
||||
@@ -367,7 +410,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(
|
||||
@@ -411,17 +454,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,6 +193,26 @@ 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"
|
||||
@@ -222,12 +242,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,9 +162,7 @@
|
||||
@@ -161,21 +162,19 @@
|
||||
8 STORE_ATTR 0 (x)
|
||||
10 LOAD_CONST 0 (None)
|
||||
12 RETURN_VALUE
|
||||
@@ -238,6 +258,29 @@ 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
|
||||
@@ -267,8 +310,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
|
||||
@@ -286,7 +329,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:
|
||||
@@ -367,7 +410,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(
|
||||
@@ -411,17 +454,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,6 +275,131 @@ 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
|
||||
@@ -559,95 +684,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,6 +110,15 @@ elif unformatted:
|
||||
},
|
||||
)
|
||||
|
||||
@@ -19,7 +18,7 @@
|
||||
"-la",
|
||||
]
|
||||
# fmt: on
|
||||
- + path,
|
||||
+ + path,
|
||||
check=True,
|
||||
)
|
||||
|
||||
@@ -82,6 +81,6 @@
|
||||
if x:
|
||||
return x
|
||||
@@ -143,7 +152,7 @@ run(
|
||||
"-la",
|
||||
]
|
||||
# fmt: on
|
||||
+ path,
|
||||
+ path,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -21,15 +21,17 @@ else:
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,7 +1,7 @@
|
||||
@@ -1,8 +1,8 @@
|
||||
a, b, c = 3, 4, 5
|
||||
if (
|
||||
a == 3
|
||||
- and b != 9 # fmt: skip
|
||||
+ and b != 9 # fmt: skip
|
||||
and c is not None
|
||||
- and c is not None
|
||||
+ and b != 9 # fmt: skip
|
||||
+ and c is not None
|
||||
):
|
||||
print("I'm good!")
|
||||
else:
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
@@ -38,8 +40,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:
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
---
|
||||
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,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
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,7 +304,52 @@ long_unmergable_string_with_pragma = (
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -165,13 +165,9 @@
|
||||
@@ -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 @@
|
||||
|
||||
triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched."""
|
||||
|
||||
@@ -320,6 +365,50 @@ 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
|
||||
@@ -367,11 +456,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(
|
||||
@@ -450,7 +539,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" % (
|
||||
@@ -462,12 +551,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."
|
||||
@@ -500,24 +589,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,
|
||||
)
|
||||
@@ -572,10 +661,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,7 +95,96 @@ def f(
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -63,7 +63,7 @@
|
||||
@@ -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
|
||||
|
||||
|
||||
# remove unnecessary paren
|
||||
@@ -110,14 +199,12 @@ def f(
|
||||
i: int,
|
||||
- x: (
|
||||
- Loooooooooooooooooooooooong
|
||||
- | Looooooooooooooooong
|
||||
- | Looooooooooooooooooooong
|
||||
+ x: Loooooooooooooooooooooooong
|
||||
| Looooooooooooooooong
|
||||
| Looooooooooooooooooooong
|
||||
- | Looooooong
|
||||
- ),
|
||||
+ x: Loooooooooooooooooooooooong
|
||||
+ | Looooooooooooooooong
|
||||
+ | Looooooooooooooooooooong
|
||||
+ | Looooooong,
|
||||
+ | Looooooong,
|
||||
*,
|
||||
s: str,
|
||||
) -> None:
|
||||
@@ -138,17 +225,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
|
||||
@@ -157,9 +244,9 @@ z: int
|
||||
|
||||
z: (
|
||||
Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
) = 7
|
||||
z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
@@ -168,32 +255,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
|
||||
@@ -210,9 +297,9 @@ def foo(i: (int,)) -> None: ...
|
||||
def foo(
|
||||
i: int,
|
||||
x: Loooooooooooooooooooooooong
|
||||
| Looooooooooooooooong
|
||||
| Looooooooooooooooooooong
|
||||
| Looooooong,
|
||||
| Looooooooooooooooong
|
||||
| Looooooooooooooooooooong
|
||||
| Looooooong,
|
||||
*,
|
||||
s: str,
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
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,12 +13,14 @@ importA;()<<0**0#
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -2,5 +2,5 @@
|
||||
@@ -1,6 +1,6 @@
|
||||
importA
|
||||
(
|
||||
()
|
||||
<< 0
|
||||
- << 0
|
||||
- ** 0
|
||||
+ **0
|
||||
+ << 0
|
||||
+ **0
|
||||
) #
|
||||
```
|
||||
|
||||
@@ -28,8 +30,8 @@ importA;()<<0**0#
|
||||
importA
|
||||
(
|
||||
()
|
||||
<< 0
|
||||
**0
|
||||
<< 0
|
||||
**0
|
||||
) #
|
||||
```
|
||||
|
||||
|
||||
@@ -85,21 +85,18 @@ 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
|
||||
- * 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
|
||||
* and_a_very_long_function_call()
|
||||
* and_another_long_func()
|
||||
/ 100000.0
|
||||
- )
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
@@ -140,15 +137,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,22 +622,29 @@ 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,8 +201,7 @@
|
||||
@@ -271,36 +201,23 @@
|
||||
)
|
||||
|
||||
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",
|
||||
@@ -281,26 +210,14 @@
|
||||
)
|
||||
+ % (
|
||||
+ "really really really really really",
|
||||
+ "old",
|
||||
+ "way to format strings!",
|
||||
+ "Use f-strings instead!",
|
||||
+ )
|
||||
)
|
||||
|
||||
-fstring = (
|
||||
@@ -688,33 +695,37 @@ 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, "
|
||||
+ "With a reallly generic name and with a really really long string that is, at some point down the line, "
|
||||
+ added
|
||||
- + 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.",
|
||||
+ "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.",
|
||||
"and a second argument",
|
||||
and_a_third,
|
||||
)
|
||||
@@ -772,7 +783,7 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
@@ -397,7 +306,7 @@
|
||||
@@ -397,61 +306,38 @@
|
||||
func_with_bad_parens(
|
||||
x,
|
||||
y,
|
||||
@@ -781,14 +792,21 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
|
||||
z,
|
||||
)
|
||||
|
||||
@@ -408,50 +317,27 @@
|
||||
+ CONCATENATED
|
||||
+ "using the '+' operator."
|
||||
)
|
||||
annotated_variable: Final = (
|
||||
"This is a large "
|
||||
- + 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."
|
||||
-)
|
||||
+ + 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"
|
||||
@@ -910,15 +928,16 @@ 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
|
||||
+ (" return [\n")
|
||||
+ + (
|
||||
+ ", \n".join(
|
||||
+ " (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
|
||||
+ for attrname, visit_name in names
|
||||
+ )
|
||||
)
|
||||
- )
|
||||
- + "\n ]\n"
|
||||
+ + ("\n ]\n")
|
||||
+ (" return [\n")
|
||||
+ + (
|
||||
+ ", \n".join(
|
||||
+ " (%r, self.%s, visitor.%s)" % (attrname, attrname, visit_name)
|
||||
+ for attrname, visit_name in names
|
||||
+ )
|
||||
+ )
|
||||
+ + ("\n ]\n")
|
||||
)
|
||||
|
||||
|
||||
@@ -1028,11 +1047,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
|
||||
@@ -1178,7 +1197,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" % (
|
||||
@@ -1190,12 +1209,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."
|
||||
@@ -1228,24 +1247,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,
|
||||
)
|
||||
@@ -1300,10 +1319,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"] = (
|
||||
@@ -1372,13 +1391,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")
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -99,18 +99,19 @@ 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!!!"
|
||||
@@ -131,20 +132,21 @@ 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"
|
||||
+ % "This long string should be split at some point right close to or around hereeeeeee"
|
||||
!= result
|
||||
- != 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"
|
||||
@@ -193,14 +195,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!!!"
|
||||
@@ -216,16 +218,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
|
||||
+ )
|
||||
)
|
||||
|
||||
|
||||
@@ -832,7 +832,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
|
||||
|
||||
some_commented_string = ( # This comment stays at the top.
|
||||
"This string is long but not so long that it needs hahahah toooooo be so greatttt"
|
||||
@@ -279,36 +280,25 @@
|
||||
@@ -279,37 +280,26 @@
|
||||
)
|
||||
|
||||
lpar_and_rpar_have_comments = func_call( # LPAR Comment
|
||||
@@ -852,32 +852,33 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
|
||||
- f" {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
|
||||
-)
|
||||
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
|
||||
+
|
||||
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
|
||||
|
||||
-cmd_fstring = (
|
||||
- "sudo -E deluge-console info --detailed --sort-reverse=time_added"
|
||||
- f" {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
|
||||
-)
|
||||
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
|
||||
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'"
|
||||
|
||||
-cmd_fstring = (
|
||||
- "sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is"
|
||||
- f" None else ID}} | perl -nE 'print if /^{field}:/'"
|
||||
-)
|
||||
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'"
|
||||
|
||||
+fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}."
|
||||
+
|
||||
|
||||
fstring = (
|
||||
- "This string really doesn't need to be an {{fstring}}, but this one most"
|
||||
- f" certainly, absolutely {does}."
|
||||
+ f"We have to remember to escape {braces}." " Like {these}." f" But not {this}."
|
||||
)
|
||||
-
|
||||
-fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}."
|
||||
|
||||
-fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}."
|
||||
-
|
||||
|
||||
class A:
|
||||
@@ -364,10 +354,7 @@
|
||||
class B:
|
||||
@@ -364,11 +354,8 @@
|
||||
def foo():
|
||||
if not hasattr(module, name):
|
||||
raise ValueError(
|
||||
@@ -885,10 +886,12 @@ 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):
|
||||
@@ -927,10 +930,19 @@ 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,
|
||||
@@ -1036,14 +1048,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."
|
||||
@@ -1323,14 +1335,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")
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1341,17 +1353,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),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1401,9 +1413,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
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1554,7 +1566,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())
|
||||
)
|
||||
|
||||
|
||||
@@ -1592,7 +1604,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)
|
||||
)
|
||||
|
||||
|
||||
@@ -1605,7 +1617,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
|
||||
|
||||
|
||||
@@ -1725,11 +1737,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."
|
||||
|
||||
@@ -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.""")
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
---
|
||||
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,6 +129,15 @@ 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
|
||||
@@ -213,7 +222,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,6 +127,24 @@ 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(
|
||||
@@ -198,7 +216,7 @@ def foo() -> (
|
||||
|
||||
def foo() -> (
|
||||
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
):
|
||||
return 2
|
||||
|
||||
@@ -217,7 +235,7 @@ def foo(
|
||||
c: int,
|
||||
) -> (
|
||||
intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
|
||||
):
|
||||
return 2
|
||||
|
||||
|
||||
@@ -41,12 +41,14 @@ assert (
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -2,7 +2,7 @@
|
||||
@@ -1,8 +1,8 @@
|
||||
importA
|
||||
(
|
||||
()
|
||||
<< 0
|
||||
- << 0
|
||||
- ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||
+ **101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||
+ << 0
|
||||
+ **101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||
) #
|
||||
|
||||
assert sort_by_dependency(
|
||||
@@ -70,8 +72,8 @@ assert (
|
||||
importA
|
||||
(
|
||||
()
|
||||
<< 0
|
||||
**101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||
<< 0
|
||||
**101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||
) #
|
||||
|
||||
assert sort_by_dependency(
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
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,7 +56,12 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
|
||||
|
||||
|
||||
# Example from https://github.com/psf/black/issues/3229
|
||||
@@ -43,8 +41,6 @@
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
# Regression test for https://github.com/psf/black/issues/3414.
|
||||
@@ -112,7 +117,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.
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_tab_indentation.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# Tests the behavior of the formatter when it comes to tabs inside docstrings
|
||||
# when using `indent_style="tab`
|
||||
|
||||
# The example below uses tabs exclusively. The formatter should preserve the tab indentation
|
||||
# of `arg1`.
|
||||
def tab_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with 2 tabs in front
|
||||
"""
|
||||
|
||||
# The `arg1` is intended with spaces. The formatter should not change the spaces to a tab
|
||||
# because it must assume that the spaces are used for alignment and not indentation.
|
||||
def space_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
def under_indented(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
def under_indented_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
def spaces_tabs_over_indent(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
# The docstring itself is indented with spaces but the argument is indented by a tab.
|
||||
# Keep the tab indentation of the argument, convert th docstring indent to tabs.
|
||||
def space_indented_docstring_containing_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg
|
||||
"""
|
||||
|
||||
|
||||
# The docstring uses tabs, spaces, tabs indentation.
|
||||
# Fallback to use space indentation
|
||||
def mixed_indentation(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The example shows an ascii art. The formatter should not change the spaces
|
||||
# to tabs because it breaks the ASCII art when inspecting the docstring with `inspect.cleandoc(ascii_art.__doc__)`
|
||||
# when using an indent width other than 8.
|
||||
def ascii_art():
|
||||
r"""
|
||||
Look at this beautiful tree.
|
||||
|
||||
a
|
||||
/ \
|
||||
b c
|
||||
/ \
|
||||
d e
|
||||
"""
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Outputs
|
||||
### Output 1
|
||||
```
|
||||
indent-style = tab
|
||||
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 = Py38
|
||||
source_type = Python
|
||||
```
|
||||
|
||||
```python
|
||||
# Tests the behavior of the formatter when it comes to tabs inside docstrings
|
||||
# when using `indent_style="tab`
|
||||
|
||||
# The example below uses tabs exclusively. The formatter should preserve the tab indentation
|
||||
# of `arg1`.
|
||||
def tab_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with 2 tabs in front
|
||||
"""
|
||||
|
||||
|
||||
# The `arg1` is intended with spaces. The formatter should not change the spaces to a tab
|
||||
# because it must assume that the spaces are used for alignment and not indentation.
|
||||
def space_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
def under_indented(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
|
||||
def under_indented_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
|
||||
def spaces_tabs_over_indent(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The docstring itself is indented with spaces but the argument is indented by a tab.
|
||||
# Keep the tab indentation of the argument, convert th docstring indent to tabs.
|
||||
def space_indented_docstring_containing_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg
|
||||
"""
|
||||
|
||||
|
||||
# The docstring uses tabs, spaces, tabs indentation.
|
||||
# Fallback to use space indentation
|
||||
def mixed_indentation(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The example shows an ascii art. The formatter should not change the spaces
|
||||
# to tabs because it breaks the ASCII art when inspecting the docstring with `inspect.cleandoc(ascii_art.__doc__)`
|
||||
# when using an indent width other than 8.
|
||||
def ascii_art():
|
||||
r"""
|
||||
Look at this beautiful tree.
|
||||
|
||||
a
|
||||
/ \
|
||||
b c
|
||||
/ \
|
||||
d e
|
||||
"""
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = tab
|
||||
line-width = 88
|
||||
indent-width = 8
|
||||
quote-style = Double
|
||||
line-ending = LineFeed
|
||||
magic-trailing-comma = Respect
|
||||
docstring-code = Disabled
|
||||
docstring-code-line-width = "dynamic"
|
||||
preview = Disabled
|
||||
target_version = Py38
|
||||
source_type = Python
|
||||
```
|
||||
|
||||
```python
|
||||
# Tests the behavior of the formatter when it comes to tabs inside docstrings
|
||||
# when using `indent_style="tab`
|
||||
|
||||
# The example below uses tabs exclusively. The formatter should preserve the tab indentation
|
||||
# of `arg1`.
|
||||
def tab_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with 2 tabs in front
|
||||
"""
|
||||
|
||||
|
||||
# The `arg1` is intended with spaces. The formatter should not change the spaces to a tab
|
||||
# because it must assume that the spaces are used for alignment and not indentation.
|
||||
def space_argument(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
def under_indented(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
|
||||
def under_indented_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
arg2: Not properly indented
|
||||
"""
|
||||
|
||||
|
||||
def spaces_tabs_over_indent(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The docstring itself is indented with spaces but the argument is indented by a tab.
|
||||
# Keep the tab indentation of the argument, convert th docstring indent to tabs.
|
||||
def space_indented_docstring_containing_tabs(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg
|
||||
"""
|
||||
|
||||
|
||||
# The docstring uses tabs, spaces, tabs indentation.
|
||||
# Fallback to use space indentation
|
||||
def mixed_indentation(arg1: str) -> None:
|
||||
"""
|
||||
Arguments:
|
||||
arg1: super duper arg with a tab and a space in front
|
||||
"""
|
||||
|
||||
|
||||
# The example shows an ascii art. The formatter should not change the spaces
|
||||
# to tabs because it breaks the ASCII art when inspecting the docstring with `inspect.cleandoc(ascii_art.__doc__)`
|
||||
# when using an indent width other than 8.
|
||||
def ascii_art():
|
||||
r"""
|
||||
Look at this beautiful tree.
|
||||
|
||||
a
|
||||
/ \
|
||||
b c
|
||||
/ \
|
||||
d e
|
||||
"""
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user