Compare commits
33 Commits
v0.1.9
...
zb/fixable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3980c1113 | ||
|
|
4f6e03ced8 | ||
|
|
29513398d2 | ||
|
|
fa78d2d97c | ||
|
|
8b70240fa2 | ||
|
|
e85e0d45f3 | ||
|
|
9b43203575 | ||
|
|
bae3fa435d | ||
|
|
6e65601055 | ||
|
|
9d6444138b | ||
|
|
6d0c9c4e95 | ||
|
|
20def33fb7 | ||
|
|
506ffade6c | ||
|
|
5040fb8cec | ||
|
|
09ac0f9e72 | ||
|
|
34d7584ca3 | ||
|
|
097d0a4322 | ||
|
|
9a672ec112 | ||
|
|
7a109164a6 | ||
|
|
74dba3ee59 | ||
|
|
cdea7d71a3 | ||
|
|
bb86d359d4 | ||
|
|
9cc257ee7d | ||
|
|
a06723da2b | ||
|
|
fa2c37b411 | ||
|
|
3cc719bd74 | ||
|
|
d835b28d01 | ||
|
|
1e7bc1dffe | ||
|
|
e241c1c5df | ||
|
|
b0ae1199e8 | ||
|
|
a9ceef5b5d | ||
|
|
a3e06e5a9d | ||
|
|
3c2b800d26 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -5,6 +5,10 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels: ["internal"]
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -95,9 +95,9 @@ jobs:
|
||||
rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Clippy"
|
||||
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
- name: "Clippy (wasm)"
|
||||
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
|
||||
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||
|
||||
cargo-test-linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
14
.github/workflows/release.yaml
vendored
14
.github/workflows/release.yaml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel - x86_64"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
- name: "Build wheels - universal2"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
args: --release --target universal2-apple-darwin --out dist
|
||||
args: --release --locked --target universal2-apple-darwin --out dist
|
||||
- name: "Test wheel - universal2"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel"
|
||||
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
|
||||
shell: bash
|
||||
@@ -210,7 +210,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel"
|
||||
if: ${{ startsWith(matrix.target, 'x86_64') }}
|
||||
run: |
|
||||
@@ -269,7 +269,7 @@ jobs:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: auto
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
if: matrix.platform.arch != 'ppc64'
|
||||
name: Test wheel
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel"
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
uses: addnab/docker-run-action@v3
|
||||
@@ -379,7 +379,7 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist
|
||||
args: --release --locked --out dist
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Test wheel
|
||||
|
||||
@@ -326,16 +326,18 @@ We use an experimental in-house tool for managing releases.
|
||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||
1. Highlight any breaking changes in `BREAKING_CHANGES.md`
|
||||
1. Run `cargo check`. This should update the lock file with new versions.
|
||||
1. Create a pull request with the changelog and version updates
|
||||
1. Merge the PR
|
||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||
main has your merged PR as last commit
|
||||
1. Run the [release workflow](https://github.com/astral-sh/ruff/actions/workflows/release.yaml) with:
|
||||
- The new version number (without starting `v`)
|
||||
- The commit hash of the merged release pull request on `main`
|
||||
1. The release workflow will do the following:
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or
|
||||
uploaded anything, you can restart after pushing a fix.
|
||||
1. Upload to PyPI.
|
||||
1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only
|
||||
after building the wheels and uploading to PyPI, since we can't delete or modify the tag ([#4468](https://github.com/charliermarsh/ruff/issues/4468)).
|
||||
after building the wheels and uploading to PyPI, since we can't delete or modify the tag ([#4468](https://github.com/astral-sh/ruff/issues/4468)).
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
||||
downstream jobs manually if needed.
|
||||
@@ -344,7 +346,10 @@ We use an experimental in-house tool for managing releases.
|
||||
1. Copy the changelog for the release into the GitHub release
|
||||
- See previous releases for formatting of section headers
|
||||
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
||||
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||
1. One can determine if an update is needed when
|
||||
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
||||
1. Once run successfully, you should follow the link in the output to create a PR.
|
||||
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
|
||||
|
||||
## Ecosystem CI
|
||||
|
||||
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -123,9 +123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -234,9 +234,9 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||
|
||||
[[package]]
|
||||
name = "cachedir"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e236bf5873ea57ec2877445297f4da008916bfae51567131acfc54a073d694f3"
|
||||
checksum = "4703f3937077db8fa35bee3c8789343c1aec2585f0146f09d658d4ccc0e8d873"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
@@ -434,11 +434,10 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.4"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
dependencies = [
|
||||
"is-terminal",
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -736,9 +735,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
@@ -809,7 +808,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -818,7 +817,7 @@ dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"ruff_linter",
|
||||
@@ -998,17 +997,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.20"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
|
||||
checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"regex-automata 0.4.3",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -1605,6 +1603,18 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c29f9c43de378b4e4e0cd7dbcce0e5cfb80443de8c05620368b2948bc936a1"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep508_rs"
|
||||
version = "0.2.1"
|
||||
@@ -1612,7 +1622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0713d7bb861ca2b7d4c50a38e1f31a4b63a2e2df35ef1e5855cc29e108453e2"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.3.12",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1794,9 +1804,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1808,7 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46d4a5e69187f23a29f8aa0ea57491d104ba541bc55f76552c2a74962aa20e04"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.3.12",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"toml",
|
||||
@@ -2063,7 +2073,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
@@ -2199,7 +2209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2222,7 +2232,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"pretty_assertions",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
@@ -2452,7 +2462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2528,7 +2538,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
@@ -2731,9 +2741,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -2958,9 +2968,9 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "3.2.1"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8f1e820b7f1d95a0cdbf97a5df9de10e1be731983ab943e56703ac1b8e9d425"
|
||||
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
|
||||
dependencies = [
|
||||
"test-case-macros",
|
||||
]
|
||||
@@ -2993,18 +3003,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.50"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.50"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3093,9 +3103,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3105,18 +3115,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -12,15 +12,15 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
anyhow = { version = "1.0.76" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
colored = { version = "2.1.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
ignore = { version = "0.4.20" }
|
||||
ignore = { version = "0.4.21" }
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.1" }
|
||||
itertools = { version = "0.11.0" }
|
||||
@@ -29,7 +29,7 @@ log = { version = "0.4.17" }
|
||||
memchr = { version = "2.6.4" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.70" }
|
||||
proc-macro2 = { version = "1.0.71" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
@@ -43,9 +43,9 @@ static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.50" }
|
||||
toml = { version = "0.8.2" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.51" }
|
||||
toml = { version = "0.8.8" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
||||
@@ -23,7 +23,7 @@ configparser = { version = "3.0.3" }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_benchmark::criterion::{
|
||||
criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
|
||||
};
|
||||
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_linter::linter::lint_only;
|
||||
use ruff_linter::linter::{lint_only, ParseSource};
|
||||
use ruff_linter::rule_selector::PreviewOptions;
|
||||
use ruff_linter::settings::rule_table::RuleTable;
|
||||
use ruff_linter::settings::types::PreviewMode;
|
||||
@@ -10,6 +10,7 @@ use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{registry::Rule, RuleSelector};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::{lexer, parse_program_tokens, Mode};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
@@ -53,7 +54,13 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
BenchmarkId::from_parameter(case.name()),
|
||||
&case,
|
||||
|b, case| {
|
||||
let kind = SourceKind::Python(case.code().to_string());
|
||||
// Tokenize the source.
|
||||
let tokens = lexer::lex(case.code(), Mode::Module).collect::<Vec<_>>();
|
||||
|
||||
// Parse the source.
|
||||
let ast =
|
||||
parse_program_tokens(tokens.clone(), case.code(), case.name(), false).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
let path = case.path();
|
||||
let result = lint_only(
|
||||
@@ -61,8 +68,12 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
None,
|
||||
settings,
|
||||
flags::Noqa::Enabled,
|
||||
&kind,
|
||||
&SourceKind::Python(case.code().to_string()),
|
||||
PySourceType::from(path.as_path()),
|
||||
ParseSource::Precomputed {
|
||||
tokens: &tokens,
|
||||
ast: &ast,
|
||||
},
|
||||
);
|
||||
|
||||
// Assert that file contains no parse errors
|
||||
|
||||
@@ -33,7 +33,7 @@ anyhow = { workspace = true }
|
||||
argfile = { version = "0.1.6" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { workspace = true }
|
||||
cachedir = { version = "0.3.0" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
|
||||
@@ -12,7 +12,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
||||
use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
@@ -303,12 +303,28 @@ pub(crate) fn lint_path(
|
||||
(result, fixed)
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
|
||||
let result = lint_only(
|
||||
path,
|
||||
package,
|
||||
settings,
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
}
|
||||
} else {
|
||||
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
|
||||
let result = lint_only(
|
||||
path,
|
||||
package,
|
||||
settings,
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
};
|
||||
@@ -444,6 +460,7 @@ pub(crate) fn lint_stdin(
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let fixed = FxHashMap::default();
|
||||
|
||||
@@ -462,6 +479,7 @@ pub(crate) fn lint_stdin(
|
||||
noqa,
|
||||
&source_kind,
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
|
||||
@@ -52,7 +52,7 @@ path-absolutize = { workspace = true, features = [
|
||||
"use_unix_paths_on_wasm",
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.8.1" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
regex = { workspace = true }
|
||||
|
||||
@@ -212,3 +212,20 @@ def func(x: int):
|
||||
raise ValueError
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def overloaded(i: int) -> "int":
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def overloaded(i: "str") -> "str":
|
||||
...
|
||||
|
||||
|
||||
def overloaded(i):
|
||||
return i
|
||||
|
||||
9
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_1.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_1.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import a
|
||||
|
||||
"""Some other docstring."""
|
||||
|
||||
import b
|
||||
|
||||
"""Some other docstring."""
|
||||
|
||||
import c
|
||||
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F404_1.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F404_1.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Docstring"""
|
||||
|
||||
"""Non-docstring"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_24.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_24.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Test for accessing class members within a generator."""
|
||||
|
||||
|
||||
class Class:
|
||||
items = []
|
||||
|
||||
if len(replacements := {item[1] for item in items}) > 1:
|
||||
pass
|
||||
@@ -1,30 +1,28 @@
|
||||
# These should change
|
||||
x = u"Hello"
|
||||
u"Hello"
|
||||
|
||||
u'world'
|
||||
x = u"Hello" # UP025
|
||||
|
||||
print(u"Hello")
|
||||
u'world' # UP025
|
||||
|
||||
print(u'world')
|
||||
print(u"Hello") # UP025
|
||||
|
||||
print(u'world') # UP025
|
||||
|
||||
import foo
|
||||
|
||||
foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
|
||||
# These should stay quoted they way they are
|
||||
# Retain quotes when fixing.
|
||||
x = u'hello' # UP025
|
||||
x = u"""hello""" # UP025
|
||||
x = u'''hello''' # UP025
|
||||
x = u'Hello "World"' # UP025
|
||||
|
||||
x = u'hello'
|
||||
x = u"""hello"""
|
||||
x = u'''hello'''
|
||||
x = u'Hello "World"'
|
||||
|
||||
# These should not change
|
||||
u = "Hello"
|
||||
|
||||
u = u
|
||||
u = "Hello" # OK
|
||||
u = u # OK
|
||||
|
||||
def hello():
|
||||
return"Hello"
|
||||
return"Hello" # OK
|
||||
|
||||
f"foo"u"bar"
|
||||
f"foo" u"bar"
|
||||
f"foo"u"bar" # OK
|
||||
f"foo" u"bar" # OK
|
||||
|
||||
@@ -243,3 +243,12 @@ raise ValueError(
|
||||
).format(a, b)
|
||||
|
||||
("{}" "{{{}}}").format(a, b)
|
||||
|
||||
# The dictionary should be parenthesized.
|
||||
"{}".format({0: 1}[0])
|
||||
|
||||
# The dictionary should be parenthesized.
|
||||
"{}".format({0: 1}.bar)
|
||||
|
||||
# The dictionary should be parenthesized.
|
||||
"{}".format({0: 1}())
|
||||
|
||||
@@ -152,3 +152,13 @@ async def f(x: bool):
|
||||
t = asyncio.create_task(asyncio.sleep(1))
|
||||
else:
|
||||
t = None
|
||||
|
||||
|
||||
# OK
|
||||
async def f(x: bool):
|
||||
global T
|
||||
|
||||
if x:
|
||||
T = asyncio.create_task(asyncio.sleep(1))
|
||||
else:
|
||||
T = None
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/ruff/RUF020.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/ruff/RUF020.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import Never, NoReturn, Union
|
||||
|
||||
Union[Never, int]
|
||||
Union[NoReturn, int]
|
||||
Never | int
|
||||
NoReturn | int
|
||||
Union[Union[Never, int], Union[NoReturn, int]]
|
||||
Union[NoReturn, int, float]
|
||||
@@ -81,6 +81,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::DuplicateUnionMember,
|
||||
Rule::RedundantLiteralUnion,
|
||||
Rule::UnnecessaryTypeUnion,
|
||||
Rule::NeverUnion,
|
||||
]) {
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// traverse nested unions.
|
||||
@@ -100,6 +101,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::NeverUnion) {
|
||||
ruff::rules::never_union(checker, expr);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::SysVersionSlice3,
|
||||
Rule::SysVersion2,
|
||||
@@ -1154,6 +1159,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::NeverUnion) {
|
||||
ruff::rules::never_union(checker, expr);
|
||||
}
|
||||
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// traverse nested unions.
|
||||
if !checker.semantic.in_nested_union() {
|
||||
|
||||
@@ -287,7 +287,18 @@ where
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
Stmt::Expr(ast::StmtExpr { value, .. })
|
||||
if !self
|
||||
.semantic
|
||||
.flags
|
||||
.intersects(SemanticModelFlags::MODULE_DOCSTRING)
|
||||
&& value.is_string_literal_expr() =>
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
}
|
||||
Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
|
||||
// Allow __future__ imports until we see a non-__future__ import.
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
if names
|
||||
@@ -301,9 +312,11 @@ where
|
||||
}
|
||||
}
|
||||
Stmt::Import(_) => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
}
|
||||
_ => {
|
||||
self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING;
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
if !(self.semantic.seen_import_boundary()
|
||||
|| helpers::is_assignment_to_a_dunder(stmt)
|
||||
@@ -1435,11 +1448,8 @@ where
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
/// Visit a [`Module`]. Returns `true` if the module contains a module-level docstring.
|
||||
fn visit_module(&mut self, python_ast: &'a Suite) -> bool {
|
||||
fn visit_module(&mut self, python_ast: &'a Suite) {
|
||||
analyze::module(python_ast, self);
|
||||
|
||||
let docstring = docstrings::extraction::docstring_from(python_ast);
|
||||
docstring.is_some()
|
||||
}
|
||||
|
||||
/// Visit a list of [`Comprehension`] nodes, assumed to be the comprehensions that compose a
|
||||
@@ -1745,10 +1755,13 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the expression is the left-hand side of a walrus operator, then it's a named
|
||||
// expression assignment.
|
||||
if self
|
||||
.semantic
|
||||
.current_expressions()
|
||||
.any(Expr::is_named_expr_expr)
|
||||
.filter_map(Expr::as_named_expr_expr)
|
||||
.any(|parent| parent.target.as_ref() == expr)
|
||||
{
|
||||
self.add_binding(id, expr.range(), BindingKind::NamedExprAssignment, flags);
|
||||
return;
|
||||
@@ -2003,14 +2016,8 @@ pub(crate) fn check_ast(
|
||||
);
|
||||
checker.bind_builtins();
|
||||
|
||||
// Check for module docstring.
|
||||
let python_ast = if checker.visit_module(python_ast) {
|
||||
&python_ast[1..]
|
||||
} else {
|
||||
python_ast
|
||||
};
|
||||
|
||||
// Iterate over the AST.
|
||||
checker.visit_module(python_ast);
|
||||
checker.visit_body(python_ast);
|
||||
|
||||
// Visit any deferred syntax nodes. Take care to visit in order, such that we avoid adding
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::TokenKind;
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn check_logical_lines(
|
||||
indent_size,
|
||||
) {
|
||||
if settings.rules.enabled(kind.rule()) {
|
||||
context.push(kind, range);
|
||||
context.push_diagnostic(Diagnostic::new(kind, range));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +123,6 @@ impl<'a> LogicalLinesContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push<K: Into<DiagnosticKind>>(&mut self, kind: K, range: TextRange) {
|
||||
let kind = kind.into();
|
||||
if self.settings.rules.enabled(kind.rule()) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
range,
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
|
||||
if self.settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -901,6 +901,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
|
||||
(Ruff, "018") => (RuleGroup::Preview, rules::ruff::rules::AssignmentInAssert),
|
||||
(Ruff, "019") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryKeyCheck),
|
||||
(Ruff, "020") => (RuleGroup::Preview, rules::ruff::rules::NeverUnion),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use rustc_hash::FxHashMap;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_ast::{PySourceType, Suite};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
@@ -73,7 +73,6 @@ pub struct FixerResult<'a> {
|
||||
pub fn check_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
tokens: Vec<LexResult>,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
@@ -82,6 +81,7 @@ pub fn check_path(
|
||||
noqa: flags::Noqa,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
tokens: TokenSource,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
@@ -144,12 +144,8 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_imports());
|
||||
if use_ast || use_imports || use_doc_lines {
|
||||
match ruff_python_parser::parse_program_tokens(
|
||||
tokens,
|
||||
source_kind.source_code(),
|
||||
&path.to_string_lossy(),
|
||||
source_type.is_ipynb(),
|
||||
) {
|
||||
// Parse, if the AST wasn't pre-provided provided.
|
||||
match tokens.into_ast_source(source_kind, source_type, path) {
|
||||
Ok(python_ast) => {
|
||||
let cell_offsets = source_kind.as_ipy_notebook().map(Notebook::cell_offsets);
|
||||
if use_ast {
|
||||
@@ -325,7 +321,6 @@ pub fn add_noqa_to_path(
|
||||
} = check_path(
|
||||
path,
|
||||
package,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -334,6 +329,7 @@ pub fn add_noqa_to_path(
|
||||
flags::Noqa::Disabled,
|
||||
source_kind,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
@@ -365,10 +361,10 @@ pub fn lint_only(
|
||||
noqa: flags::Noqa,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
data: ParseSource,
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(source_kind.source_code(), source_type.as_mode());
|
||||
let tokens = data.into_token_source(source_kind, source_type);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(source_kind.source_code());
|
||||
@@ -391,7 +387,6 @@ pub fn lint_only(
|
||||
let result = check_path(
|
||||
path,
|
||||
package,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -400,6 +395,7 @@ pub fn lint_only(
|
||||
noqa,
|
||||
source_kind,
|
||||
source_type,
|
||||
tokens,
|
||||
);
|
||||
|
||||
result.map(|(diagnostics, imports)| {
|
||||
@@ -487,7 +483,6 @@ pub fn lint_fix<'a>(
|
||||
let result = check_path(
|
||||
path,
|
||||
package,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -496,6 +491,7 @@ pub fn lint_fix<'a>(
|
||||
noqa,
|
||||
&transformed,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
@@ -632,6 +628,95 @@ This indicates a bug in Ruff. If you could open an issue at:
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ParseSource<'a> {
|
||||
/// Extract the tokens and AST from the given source code.
|
||||
None,
|
||||
/// Use the precomputed tokens and AST.
|
||||
Precomputed {
|
||||
tokens: &'a [LexResult],
|
||||
ast: &'a Suite,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> ParseSource<'a> {
|
||||
/// Convert to a [`TokenSource`], tokenizing if necessary.
|
||||
fn into_token_source(
|
||||
self,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> TokenSource<'a> {
|
||||
match self {
|
||||
Self::None => TokenSource::Tokens(ruff_python_parser::tokenize(
|
||||
source_kind.source_code(),
|
||||
source_type.as_mode(),
|
||||
)),
|
||||
Self::Precomputed { tokens, ast } => TokenSource::Precomputed { tokens, ast },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TokenSource<'a> {
|
||||
/// Use the precomputed tokens to generate the AST.
|
||||
Tokens(Vec<LexResult>),
|
||||
/// Use the precomputed tokens and AST.
|
||||
Precomputed {
|
||||
tokens: &'a [LexResult],
|
||||
ast: &'a Suite,
|
||||
},
|
||||
}
|
||||
|
||||
impl Deref for TokenSource<'_> {
|
||||
type Target = [LexResult];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Tokens(tokens) => tokens,
|
||||
Self::Precomputed { tokens, .. } => tokens,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TokenSource<'a> {
|
||||
/// Convert to an [`AstSource`], parsing if necessary.
|
||||
fn into_ast_source(
|
||||
self,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
path: &Path,
|
||||
) -> Result<AstSource<'a>, ParseError> {
|
||||
match self {
|
||||
Self::Tokens(tokens) => Ok(AstSource::Ast(ruff_python_parser::parse_program_tokens(
|
||||
tokens,
|
||||
source_kind.source_code(),
|
||||
&path.to_string_lossy(),
|
||||
source_type.is_ipynb(),
|
||||
)?)),
|
||||
Self::Precomputed { ast, .. } => Ok(AstSource::Precomputed(ast)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AstSource<'a> {
|
||||
/// Extract the AST from the given source code.
|
||||
Ast(Suite),
|
||||
/// Use the precomputed AST.
|
||||
Precomputed(&'a Suite),
|
||||
}
|
||||
|
||||
impl Deref for AstSource<'_> {
|
||||
type Target = Suite;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Ast(ast) => ast,
|
||||
Self::Precomputed(ast) => ast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
@@ -243,6 +243,17 @@ pub struct PreviewOptions {
|
||||
pub require_explicit: bool,
|
||||
}
|
||||
|
||||
impl PreviewOptions {
|
||||
/// Return a copy with the same preview mode setting but require explicit disabled.
|
||||
#[must_use]
|
||||
pub fn without_require_explicit(&self) -> Self {
|
||||
Self {
|
||||
mode: self.mode,
|
||||
require_explicit: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use itertools::Itertools;
|
||||
|
||||
@@ -482,7 +482,6 @@ impl Violation for AnyType {
|
||||
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
|
||||
}
|
||||
}
|
||||
|
||||
fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
let mut visitor = ReturnStatementVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
@@ -537,17 +536,41 @@ fn check_dynamically_typed<F>(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
)
|
||||
/// Return `true` if a function appears to be a stub.
|
||||
fn is_stub_function(function_def: &ast::StmtFunctionDef, checker: &Checker) -> bool {
|
||||
/// Returns `true` if a function has an empty body.
|
||||
fn is_empty_body(function_def: &ast::StmtFunctionDef) -> bool {
|
||||
function_def.body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
// Ignore functions with empty bodies in...
|
||||
if is_empty_body(function_def) {
|
||||
// Stub definitions (.pyi files)...
|
||||
if checker.source_type.is_stub() {
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
|
||||
// Abstract methods...
|
||||
if visibility::is_abstract(&function_def.decorator_list, checker.semantic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Overload definitions...
|
||||
if visibility::is_overload(&function_def.decorator_list, checker.semantic()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Generate flake8-annotation checks for a given `Definition`.
|
||||
@@ -738,9 +761,7 @@ pub(crate) fn definition(
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||
let return_type = if visibility::is_abstract(decorator_list, checker.semantic())
|
||||
&& is_empty_body(body)
|
||||
{
|
||||
let return_type = if is_stub_function(function, checker) {
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
@@ -771,9 +792,7 @@ pub(crate) fn definition(
|
||||
}
|
||||
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||
let return_type = if visibility::is_abstract(decorator_list, checker.semantic())
|
||||
&& is_empty_body(body)
|
||||
{
|
||||
let return_type = if is_stub_function(function, checker) {
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
@@ -843,25 +862,22 @@ pub(crate) fn definition(
|
||||
match visibility {
|
||||
visibility::Visibility::Public => {
|
||||
if checker.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction) {
|
||||
let return_type =
|
||||
if visibility::is_abstract(decorator_list, checker.semantic())
|
||||
&& is_empty_body(body)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.settings.target_version,
|
||||
)
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
})
|
||||
};
|
||||
let return_type = if is_stub_function(function, checker) {
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.settings.target_version,
|
||||
)
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
})
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
@@ -885,25 +901,22 @@ pub(crate) fn definition(
|
||||
}
|
||||
visibility::Visibility::Private => {
|
||||
if checker.enabled(Rule::MissingReturnTypePrivateFunction) {
|
||||
let return_type =
|
||||
if visibility::is_abstract(decorator_list, checker.semantic())
|
||||
&& is_empty_body(body)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.settings.target_version,
|
||||
)
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
})
|
||||
};
|
||||
let return_type = if is_stub_function(function, checker) {
|
||||
None
|
||||
} else {
|
||||
auto_return_type(function)
|
||||
.and_then(|return_type| {
|
||||
return_type.into_expression(
|
||||
checker.importer(),
|
||||
function.parameters.start(),
|
||||
checker.semantic(),
|
||||
checker.settings.target_version,
|
||||
)
|
||||
})
|
||||
.map(|(return_type, edits)| {
|
||||
(checker.generator().expr(&return_type), edits)
|
||||
})
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
/// Traverse a "union" type annotation, applying `func` to each union member.
|
||||
/// Supports traversal of `Union` and `|` union expressions.
|
||||
/// The function is called with each expression in the union (excluding declarations of nested unions)
|
||||
/// and the parent expression (if any).
|
||||
pub(super) fn traverse_union<'a, F>(
|
||||
func: &mut F,
|
||||
semantic: &SemanticModel,
|
||||
expr: &'a Expr,
|
||||
parent: Option<&'a Expr>,
|
||||
) where
|
||||
F: FnMut(&'a Expr, Option<&'a Expr>),
|
||||
{
|
||||
// Ex) x | y
|
||||
if let Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
left,
|
||||
right,
|
||||
range: _,
|
||||
}) = expr
|
||||
{
|
||||
// The union data structure usually looks like this:
|
||||
// a | b | c -> (a | b) | c
|
||||
//
|
||||
// However, parenthesized expressions can coerce it into any structure:
|
||||
// a | (b | c)
|
||||
//
|
||||
// So we have to traverse both branches in order (left, then right), to report members
|
||||
// in the order they appear in the source code.
|
||||
|
||||
// Traverse the left then right arms
|
||||
traverse_union(func, semantic, left, Some(expr));
|
||||
traverse_union(func, semantic, right, Some(expr));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) Union[x, y]
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
if semantic.match_typing_expr(value, "Union") {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
// Traverse each element of the tuple within the union recursively to handle cases
|
||||
// such as `Union[..., Union[...]]
|
||||
elts.iter()
|
||||
.for_each(|elt| traverse_union(func, semantic, elt, Some(expr)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, call the function on expression
|
||||
func(expr, parent);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Rules from [flake8-pyi](https://pypi.org/project/flake8-pyi/).
|
||||
mod helpers;
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::rules::flake8_pyi::helpers::traverse_union;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate union members.
|
||||
///
|
||||
@@ -55,7 +56,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr)
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||
let mut check_for_duplicate_members = |expr: &'a Expr, parent: Option<&'a Expr>| {
|
||||
let mut check_for_duplicate_members = |expr: &'a Expr, parent: &'a Expr| {
|
||||
// If we've already seen this union member, raise a violation.
|
||||
if !seen_nodes.insert(expr.into()) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
@@ -68,7 +69,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr)
|
||||
// parent without the duplicate.
|
||||
|
||||
// If the parent node is not a `BinOp` we will not perform a fix
|
||||
if let Some(parent @ Expr::BinOp(ast::ExprBinOp { left, right, .. })) = parent {
|
||||
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = parent {
|
||||
// Replace the parent with its non-duplicate child.
|
||||
let child = if expr == left.as_ref() { right } else { left };
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
@@ -81,12 +82,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr)
|
||||
};
|
||||
|
||||
// Traverse the union, collect all diagnostic members
|
||||
traverse_union(
|
||||
&mut check_for_duplicate_members,
|
||||
checker.semantic(),
|
||||
expr,
|
||||
None,
|
||||
);
|
||||
traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
|
||||
// Add all diagnostics to the checker
|
||||
checker.diagnostics.append(&mut diagnostics);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
use crate::{checkers::ast::Checker, rules::flake8_pyi::helpers::traverse_union};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of redundant `Literal` types and builtin super
|
||||
@@ -64,7 +66,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &mut Checker, union: &'a Expr
|
||||
|
||||
// Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types
|
||||
// to `builtin_types_in_union`.
|
||||
let mut func = |expr: &'a Expr, _| {
|
||||
let mut func = |expr: &'a Expr, _parent: &'a Expr| {
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
if checker.semantic().match_typing_expr(value, "Literal") {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
@@ -82,7 +84,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &mut Checker, union: &'a Expr
|
||||
builtin_types_in_union.insert(builtin_type);
|
||||
};
|
||||
|
||||
traverse_union(&mut func, checker.semantic(), union, None);
|
||||
traverse_union(&mut func, checker.semantic(), union);
|
||||
|
||||
for typing_literal_expr in typing_literal_exprs {
|
||||
let Some(literal_type) = match_literal_type(typing_literal_expr) else {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use ruff_python_ast::{Expr, Parameters};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Parameters};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_pyi::helpers::traverse_union;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for union annotations that contain redundant numeric types (e.g.,
|
||||
@@ -90,7 +89,7 @@ fn check_annotation(checker: &mut Checker, annotation: &Expr) {
|
||||
let mut has_complex = false;
|
||||
let mut has_int = false;
|
||||
|
||||
let mut func = |expr: &Expr, _parent: Option<&Expr>| {
|
||||
let mut func = |expr: &Expr, _parent: &Expr| {
|
||||
let Some(call_path) = checker.semantic().resolve_call_path(expr) else {
|
||||
return;
|
||||
};
|
||||
@@ -103,7 +102,7 @@ fn check_annotation(checker: &mut Checker, annotation: &Expr) {
|
||||
}
|
||||
};
|
||||
|
||||
traverse_union(&mut func, checker.semantic(), annotation, None);
|
||||
traverse_union(&mut func, checker.semantic(), annotation);
|
||||
|
||||
if has_complex {
|
||||
if has_float {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use ast::{ExprSubscript, Operator};
|
||||
use ast::Operator;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use crate::rules::flake8_pyi::helpers::traverse_union;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of multiple literal types in a union.
|
||||
///
|
||||
@@ -62,7 +61,7 @@ fn concatenate_bin_ors(exprs: Vec<&Expr>) -> Expr {
|
||||
})
|
||||
}
|
||||
|
||||
fn make_union(subscript: &ExprSubscript, exprs: Vec<&Expr>) -> Expr {
|
||||
fn make_union(subscript: &ast::ExprSubscript, exprs: Vec<&Expr>) -> Expr {
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value: subscript.value.clone(),
|
||||
slice: Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
@@ -108,7 +107,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
|
||||
let mut total_literals = 0;
|
||||
|
||||
// Split members into `literal_exprs` if they are a `Literal` annotation and `other_exprs` otherwise
|
||||
let mut collect_literal_expr = |expr: &'a Expr, _| {
|
||||
let mut collect_literal_expr = |expr: &'a Expr, _parent: &'a Expr| {
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
if checker.semantic().match_typing_expr(value, "Literal") {
|
||||
total_literals += 1;
|
||||
@@ -137,7 +136,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
|
||||
};
|
||||
|
||||
// Traverse the union, collect all members, split out the literals from the rest.
|
||||
traverse_union(&mut collect_literal_expr, checker.semantic(), expr, None);
|
||||
traverse_union(&mut collect_literal_expr, checker.semantic(), expr);
|
||||
|
||||
let union_subscript = expr.as_subscript_expr();
|
||||
if union_subscript.is_some_and(|subscript| {
|
||||
|
||||
@@ -2,9 +2,10 @@ use ast::{ExprContext, Operator};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::{checkers::ast::Checker, rules::flake8_pyi::helpers::traverse_union};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of multiple `type`s in a union.
|
||||
@@ -82,7 +83,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||
let mut type_exprs = Vec::new();
|
||||
let mut other_exprs = Vec::new();
|
||||
|
||||
let mut collect_type_exprs = |expr: &'a Expr, _| {
|
||||
let mut collect_type_exprs = |expr: &'a Expr, _parent: &'a Expr| {
|
||||
let subscript = expr.as_subscript_expr();
|
||||
|
||||
if subscript.is_none() {
|
||||
@@ -101,7 +102,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||
}
|
||||
};
|
||||
|
||||
traverse_union(&mut collect_type_exprs, checker.semantic(), union, None);
|
||||
traverse_union(&mut collect_type_exprs, checker.semantic(), union);
|
||||
|
||||
if type_exprs.len() > 1 {
|
||||
let type_members: Vec<String> = type_exprs
|
||||
|
||||
@@ -35,7 +35,8 @@ mod tests {
|
||||
#[test_case(Rule::LineTooLong, Path::new("E501_3.py"))]
|
||||
#[test_case(Rule::MixedSpacesAndTabs, Path::new("E101.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E40.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_1.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.ipynb"))]
|
||||
#[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))]
|
||||
#[test_case(Rule::MultipleStatementsOnOneLineColon, Path::new("E70.py"))]
|
||||
@@ -65,7 +66,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Rule::IsLiteral, Path::new("constant_literals.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))]
|
||||
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E402.py:25:1: E402 Module level import not at top of file
|
||||
E402_0.py:25:1: E402 Module level import not at top of file
|
||||
|
|
||||
23 | sys.path.insert(0, "some/path")
|
||||
24 |
|
||||
@@ -11,7 +11,7 @@ E402.py:25:1: E402 Module level import not at top of file
|
||||
27 | import matplotlib
|
||||
|
|
||||
|
||||
E402.py:27:1: E402 Module level import not at top of file
|
||||
E402_0.py:27:1: E402 Module level import not at top of file
|
||||
|
|
||||
25 | import f
|
||||
26 |
|
||||
@@ -21,7 +21,7 @@ E402.py:27:1: E402 Module level import not at top of file
|
||||
29 | matplotlib.use("Agg")
|
||||
|
|
||||
|
||||
E402.py:31:1: E402 Module level import not at top of file
|
||||
E402_0.py:31:1: E402 Module level import not at top of file
|
||||
|
|
||||
29 | matplotlib.use("Agg")
|
||||
30 |
|
||||
@@ -31,7 +31,7 @@ E402.py:31:1: E402 Module level import not at top of file
|
||||
33 | __some__magic = 1
|
||||
|
|
||||
|
||||
E402.py:35:1: E402 Module level import not at top of file
|
||||
E402_0.py:35:1: E402 Module level import not at top of file
|
||||
|
|
||||
33 | __some__magic = 1
|
||||
34 |
|
||||
@@ -39,7 +39,7 @@ E402.py:35:1: E402 Module level import not at top of file
|
||||
| ^^^^^^^^ E402
|
||||
|
|
||||
|
||||
E402.py:45:1: E402 Module level import not at top of file
|
||||
E402_0.py:45:1: E402 Module level import not at top of file
|
||||
|
|
||||
43 | import j
|
||||
44 |
|
||||
@@ -47,7 +47,7 @@ E402.py:45:1: E402 Module level import not at top of file
|
||||
| ^^^^^^^^ E402
|
||||
|
|
||||
|
||||
E402.py:45:11: E402 Module level import not at top of file
|
||||
E402_0.py:45:11: E402 Module level import not at top of file
|
||||
|
|
||||
43 | import j
|
||||
44 |
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E402_1.py:5:1: E402 Module level import not at top of file
|
||||
|
|
||||
3 | """Some other docstring."""
|
||||
4 |
|
||||
5 | import b
|
||||
| ^^^^^^^^ E402
|
||||
6 |
|
||||
7 | """Some other docstring."""
|
||||
|
|
||||
|
||||
E402_1.py:9:1: E402 Module level import not at top of file
|
||||
|
|
||||
7 | """Some other docstring."""
|
||||
8 |
|
||||
9 | import c
|
||||
| ^^^^^^^^ E402
|
||||
|
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E402.py:35:1: E402 Module level import not at top of file
|
||||
E402_0.py:35:1: E402 Module level import not at top of file
|
||||
|
|
||||
33 | __some__magic = 1
|
||||
34 |
|
||||
@@ -9,7 +9,7 @@ E402.py:35:1: E402 Module level import not at top of file
|
||||
| ^^^^^^^^ E402
|
||||
|
|
||||
|
||||
E402.py:45:1: E402 Module level import not at top of file
|
||||
E402_0.py:45:1: E402 Module level import not at top of file
|
||||
|
|
||||
43 | import j
|
||||
44 |
|
||||
@@ -17,7 +17,7 @@ E402.py:45:1: E402 Module level import not at top of file
|
||||
| ^^^^^^^^ E402
|
||||
|
|
||||
|
||||
E402.py:45:11: E402 Module level import not at top of file
|
||||
E402_0.py:45:11: E402 Module level import not at top of file
|
||||
|
|
||||
43 | import j
|
||||
44 |
|
||||
@@ -23,7 +23,7 @@ mod tests {
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::linter::{check_path, LinterResult};
|
||||
use crate::linter::{check_path, LinterResult, TokenSource};
|
||||
use crate::registry::{AsRule, Linter, Rule};
|
||||
use crate::rules::pyflakes;
|
||||
use crate::settings::types::PreviewMode;
|
||||
@@ -55,7 +55,8 @@ mod tests {
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_20.py"))]
|
||||
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
|
||||
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
|
||||
#[test_case(Rule::LateFutureImport, Path::new("F404.py"))]
|
||||
#[test_case(Rule::LateFutureImport, Path::new("F404_0.py"))]
|
||||
#[test_case(Rule::LateFutureImport, Path::new("F404_1.py"))]
|
||||
#[test_case(Rule::UndefinedLocalWithImportStarUsage, Path::new("F405.py"))]
|
||||
#[test_case(Rule::UndefinedLocalWithNestedImportStarUsage, Path::new("F406.py"))]
|
||||
#[test_case(Rule::FutureFeatureNotDefined, Path::new("F407.py"))]
|
||||
@@ -142,6 +143,7 @@ mod tests {
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_21.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_22.ipynb"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_23.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_24.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
|
||||
@@ -558,7 +560,6 @@ mod tests {
|
||||
} = check_path(
|
||||
Path::new("<filename>"),
|
||||
None,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -567,6 +568,7 @@ mod tests {
|
||||
flags::Noqa::Enabled,
|
||||
&source_kind,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
diagnostics.sort_by_key(Ranged::start);
|
||||
let actual = diagnostics
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F404.py:6:1: F404 `from __future__` imports must occur at the beginning of the file
|
||||
F404_0.py:6:1: F404 `from __future__` imports must occur at the beginning of the file
|
||||
|
|
||||
4 | from collections import namedtuple
|
||||
5 |
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F404_1.py:5:1: F404 `from __future__` imports must occur at the beginning of the file
|
||||
|
|
||||
3 | """Non-docstring"""
|
||||
4 |
|
||||
5 | from __future__ import absolute_import
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F404
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -7,7 +7,6 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the known pre-Python 2.5 ternary syntax.
|
||||
@@ -123,11 +122,9 @@ pub(crate) fn and_or_ternary(checker: &mut Checker, bool_op: &ExprBoolOp) {
|
||||
},
|
||||
bool_op.range,
|
||||
);
|
||||
if checker.enabled(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
ternary,
|
||||
bool_op.range,
|
||||
)));
|
||||
}
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
ternary,
|
||||
bool_op.range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -137,10 +137,9 @@ enum FormatContext {
|
||||
Accessed,
|
||||
}
|
||||
|
||||
/// Given an [`Expr`], format it for use in a formatted expression within an f-string.
|
||||
fn formatted_expr<'a>(expr: &Expr, context: FormatContext, locator: &Locator<'a>) -> Cow<'a, str> {
|
||||
let text = locator.slice(expr);
|
||||
let parenthesize = match (context, expr) {
|
||||
/// Returns `true` if the expression should be parenthesized when used in an f-string.
|
||||
fn parenthesize(expr: &Expr, text: &str, context: FormatContext) -> bool {
|
||||
match (context, expr) {
|
||||
// E.g., `x + y` should be parenthesized in `f"{(x + y)[0]}"`.
|
||||
(
|
||||
FormatContext::Accessed,
|
||||
@@ -173,9 +172,44 @@ fn formatted_expr<'a>(expr: &Expr, context: FormatContext, locator: &Locator<'a>
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_),
|
||||
) => true,
|
||||
(_, Expr::Subscript(ast::ExprSubscript { value, .. })) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::GeneratorExp(_)
|
||||
| Expr::Dict(_)
|
||||
| Expr::Set(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
)
|
||||
}
|
||||
(_, Expr::Attribute(ast::ExprAttribute { value, .. })) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::GeneratorExp(_)
|
||||
| Expr::Dict(_)
|
||||
| Expr::Set(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
)
|
||||
}
|
||||
(_, Expr::Call(ast::ExprCall { func, .. })) => {
|
||||
matches!(
|
||||
func.as_ref(),
|
||||
Expr::GeneratorExp(_)
|
||||
| Expr::Dict(_)
|
||||
| Expr::Set(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if parenthesize && !text.starts_with('(') && !text.ends_with(')') {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an [`Expr`], format it for use in a formatted expression within an f-string.
|
||||
fn formatted_expr<'a>(expr: &Expr, context: FormatContext, locator: &Locator<'a>) -> Cow<'a, str> {
|
||||
let text = locator.slice(expr);
|
||||
if parenthesize(expr, text, context) && !(text.starts_with('(') && text.ends_with(')')) {
|
||||
Cow::Owned(format!("({text})"))
|
||||
} else {
|
||||
Cow::Borrowed(text)
|
||||
|
||||
@@ -1,284 +1,302 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP025.py:2:5: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:1:1: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
1 | # These should change
|
||||
2 | x = u"Hello"
|
||||
| ^^^^^^^^ UP025
|
||||
3 |
|
||||
4 | u'world'
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # These should change
|
||||
2 |-x = u"Hello"
|
||||
2 |+x = "Hello"
|
||||
3 3 |
|
||||
4 4 | u'world'
|
||||
5 5 |
|
||||
|
||||
UP025.py:4:1: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
2 | x = u"Hello"
|
||||
3 |
|
||||
4 | u'world'
|
||||
1 | u"Hello"
|
||||
| ^^^^^^^^ UP025
|
||||
5 |
|
||||
6 | print(u"Hello")
|
||||
2 |
|
||||
3 | x = u"Hello" # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # These should change
|
||||
2 2 | x = u"Hello"
|
||||
3 3 |
|
||||
4 |-u'world'
|
||||
4 |+'world'
|
||||
5 5 |
|
||||
6 6 | print(u"Hello")
|
||||
7 7 |
|
||||
1 |-u"Hello"
|
||||
1 |+"Hello"
|
||||
2 2 |
|
||||
3 3 | x = u"Hello" # UP025
|
||||
4 4 |
|
||||
|
||||
UP025.py:6:7: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:3:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
4 | u'world'
|
||||
5 |
|
||||
6 | print(u"Hello")
|
||||
1 | u"Hello"
|
||||
2 |
|
||||
3 | x = u"Hello" # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
4 |
|
||||
5 | u'world' # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | u"Hello"
|
||||
2 2 |
|
||||
3 |-x = u"Hello" # UP025
|
||||
3 |+x = "Hello" # UP025
|
||||
4 4 |
|
||||
5 5 | u'world' # UP025
|
||||
6 6 |
|
||||
|
||||
UP025.py:5:1: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
3 | x = u"Hello" # UP025
|
||||
4 |
|
||||
5 | u'world' # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
6 |
|
||||
7 | print(u"Hello") # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 |
|
||||
3 3 | x = u"Hello" # UP025
|
||||
4 4 |
|
||||
5 |-u'world' # UP025
|
||||
5 |+'world' # UP025
|
||||
6 6 |
|
||||
7 7 | print(u"Hello") # UP025
|
||||
8 8 |
|
||||
|
||||
UP025.py:7:7: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
5 | u'world' # UP025
|
||||
6 |
|
||||
7 | print(u"Hello") # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
7 |
|
||||
8 | print(u'world')
|
||||
8 |
|
||||
9 | print(u'world') # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 | u'world'
|
||||
5 5 |
|
||||
6 |-print(u"Hello")
|
||||
6 |+print("Hello")
|
||||
7 7 |
|
||||
8 8 | print(u'world')
|
||||
9 9 |
|
||||
4 4 |
|
||||
5 5 | u'world' # UP025
|
||||
6 6 |
|
||||
7 |-print(u"Hello") # UP025
|
||||
7 |+print("Hello") # UP025
|
||||
8 8 |
|
||||
9 9 | print(u'world') # UP025
|
||||
10 10 |
|
||||
|
||||
UP025.py:8:7: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:9:7: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
6 | print(u"Hello")
|
||||
7 |
|
||||
8 | print(u'world')
|
||||
7 | print(u"Hello") # UP025
|
||||
8 |
|
||||
9 | print(u'world') # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
9 |
|
||||
10 | import foo
|
||||
10 |
|
||||
11 | import foo
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | print(u"Hello")
|
||||
7 7 |
|
||||
8 |-print(u'world')
|
||||
8 |+print('world')
|
||||
9 9 |
|
||||
10 10 | import foo
|
||||
11 11 |
|
||||
6 6 |
|
||||
7 7 | print(u"Hello") # UP025
|
||||
8 8 |
|
||||
9 |-print(u'world') # UP025
|
||||
9 |+print('world') # UP025
|
||||
10 10 |
|
||||
11 11 | import foo
|
||||
12 12 |
|
||||
|
||||
UP025.py:12:5: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:13:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
10 | import foo
|
||||
11 |
|
||||
12 | foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
11 | import foo
|
||||
12 |
|
||||
13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
13 |
|
||||
14 | # These should stay quoted they way they are
|
||||
14 |
|
||||
15 | # Retain quotes when fixing.
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | import foo
|
||||
11 11 |
|
||||
12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
12 |+foo("Hello", U"world", a=u"Hello", b=u"world")
|
||||
13 13 |
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
10 10 |
|
||||
11 11 | import foo
|
||||
12 12 |
|
||||
13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
13 |+foo("Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
|
||||
UP025.py:12:15: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:13:15: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
10 | import foo
|
||||
11 |
|
||||
12 | foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
11 | import foo
|
||||
12 |
|
||||
13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
13 |
|
||||
14 | # These should stay quoted they way they are
|
||||
14 |
|
||||
15 | # Retain quotes when fixing.
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | import foo
|
||||
11 11 |
|
||||
12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
12 |+foo(u"Hello", "world", a=u"Hello", b=u"world")
|
||||
13 13 |
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
10 10 |
|
||||
11 11 | import foo
|
||||
12 12 |
|
||||
13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
13 |+foo(u"Hello", "world", a=u"Hello", b=u"world") # UP025
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
|
||||
UP025.py:12:27: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:13:27: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
10 | import foo
|
||||
11 |
|
||||
12 | foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
11 | import foo
|
||||
12 |
|
||||
13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
13 |
|
||||
14 | # These should stay quoted they way they are
|
||||
14 |
|
||||
15 | # Retain quotes when fixing.
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | import foo
|
||||
11 11 |
|
||||
12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
12 |+foo(u"Hello", U"world", a="Hello", b=u"world")
|
||||
13 13 |
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
10 10 |
|
||||
11 11 | import foo
|
||||
12 12 |
|
||||
13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
13 |+foo(u"Hello", U"world", a="Hello", b=u"world") # UP025
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
|
||||
UP025.py:12:39: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:13:39: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
10 | import foo
|
||||
11 |
|
||||
12 | foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
11 | import foo
|
||||
12 |
|
||||
13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
13 |
|
||||
14 | # These should stay quoted they way they are
|
||||
14 |
|
||||
15 | # Retain quotes when fixing.
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | import foo
|
||||
11 11 |
|
||||
12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world")
|
||||
12 |+foo(u"Hello", U"world", a=u"Hello", b="world")
|
||||
13 13 |
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
10 10 |
|
||||
11 11 | import foo
|
||||
12 12 |
|
||||
13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
13 |+foo(u"Hello", U"world", a=u"Hello", b="world") # UP025
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
|
||||
UP025.py:16:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
14 | # These should stay quoted they way they are
|
||||
15 |
|
||||
16 | x = u'hello'
|
||||
15 | # Retain quotes when fixing.
|
||||
16 | x = u'hello' # UP025
|
||||
| ^^^^^^^^ UP025
|
||||
17 | x = u"""hello"""
|
||||
18 | x = u'''hello'''
|
||||
17 | x = u"""hello""" # UP025
|
||||
18 | x = u'''hello''' # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 |
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
16 |-x = u'hello'
|
||||
16 |+x = 'hello'
|
||||
17 17 | x = u"""hello"""
|
||||
18 18 | x = u'''hello'''
|
||||
19 19 | x = u'Hello "World"'
|
||||
13 13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 |-x = u'hello' # UP025
|
||||
16 |+x = 'hello' # UP025
|
||||
17 17 | x = u"""hello""" # UP025
|
||||
18 18 | x = u'''hello''' # UP025
|
||||
19 19 | x = u'Hello "World"' # UP025
|
||||
|
||||
UP025.py:17:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
16 | x = u'hello'
|
||||
17 | x = u"""hello"""
|
||||
15 | # Retain quotes when fixing.
|
||||
16 | x = u'hello' # UP025
|
||||
17 | x = u"""hello""" # UP025
|
||||
| ^^^^^^^^^^^^ UP025
|
||||
18 | x = u'''hello'''
|
||||
19 | x = u'Hello "World"'
|
||||
18 | x = u'''hello''' # UP025
|
||||
19 | x = u'Hello "World"' # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | # These should stay quoted they way they are
|
||||
15 15 |
|
||||
16 16 | x = u'hello'
|
||||
17 |-x = u"""hello"""
|
||||
17 |+x = """hello"""
|
||||
18 18 | x = u'''hello'''
|
||||
19 19 | x = u'Hello "World"'
|
||||
14 14 |
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
17 |-x = u"""hello""" # UP025
|
||||
17 |+x = """hello""" # UP025
|
||||
18 18 | x = u'''hello''' # UP025
|
||||
19 19 | x = u'Hello "World"' # UP025
|
||||
20 20 |
|
||||
|
||||
UP025.py:18:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
16 | x = u'hello'
|
||||
17 | x = u"""hello"""
|
||||
18 | x = u'''hello'''
|
||||
16 | x = u'hello' # UP025
|
||||
17 | x = u"""hello""" # UP025
|
||||
18 | x = u'''hello''' # UP025
|
||||
| ^^^^^^^^^^^^ UP025
|
||||
19 | x = u'Hello "World"'
|
||||
19 | x = u'Hello "World"' # UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 |
|
||||
16 16 | x = u'hello'
|
||||
17 17 | x = u"""hello"""
|
||||
18 |-x = u'''hello'''
|
||||
18 |+x = '''hello'''
|
||||
19 19 | x = u'Hello "World"'
|
||||
15 15 | # Retain quotes when fixing.
|
||||
16 16 | x = u'hello' # UP025
|
||||
17 17 | x = u"""hello""" # UP025
|
||||
18 |-x = u'''hello''' # UP025
|
||||
18 |+x = '''hello''' # UP025
|
||||
19 19 | x = u'Hello "World"' # UP025
|
||||
20 20 |
|
||||
21 21 | # These should not change
|
||||
21 21 | u = "Hello" # OK
|
||||
|
||||
UP025.py:19:5: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
17 | x = u"""hello"""
|
||||
18 | x = u'''hello'''
|
||||
19 | x = u'Hello "World"'
|
||||
17 | x = u"""hello""" # UP025
|
||||
18 | x = u'''hello''' # UP025
|
||||
19 | x = u'Hello "World"' # UP025
|
||||
| ^^^^^^^^^^^^^^^^ UP025
|
||||
20 |
|
||||
21 | # These should not change
|
||||
21 | u = "Hello" # OK
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 | x = u'hello'
|
||||
17 17 | x = u"""hello"""
|
||||
18 18 | x = u'''hello'''
|
||||
19 |-x = u'Hello "World"'
|
||||
19 |+x = 'Hello "World"'
|
||||
16 16 | x = u'hello' # UP025
|
||||
17 17 | x = u"""hello""" # UP025
|
||||
18 18 | x = u'''hello''' # UP025
|
||||
19 |-x = u'Hello "World"' # UP025
|
||||
19 |+x = 'Hello "World"' # UP025
|
||||
20 20 |
|
||||
21 21 | # These should not change
|
||||
22 22 | u = "Hello"
|
||||
21 21 | u = "Hello" # OK
|
||||
22 22 | u = u # OK
|
||||
|
||||
UP025.py:29:7: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:27:7: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
27 | return"Hello"
|
||||
28 |
|
||||
29 | f"foo"u"bar"
|
||||
25 | return"Hello" # OK
|
||||
26 |
|
||||
27 | f"foo"u"bar" # OK
|
||||
| ^^^^^^ UP025
|
||||
30 | f"foo" u"bar"
|
||||
28 | f"foo" u"bar" # OK
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | def hello():
|
||||
27 27 | return"Hello"
|
||||
28 28 |
|
||||
29 |-f"foo"u"bar"
|
||||
29 |+f"foo""bar"
|
||||
30 30 | f"foo" u"bar"
|
||||
24 24 | def hello():
|
||||
25 25 | return"Hello" # OK
|
||||
26 26 |
|
||||
27 |-f"foo"u"bar" # OK
|
||||
27 |+f"foo""bar" # OK
|
||||
28 28 | f"foo" u"bar" # OK
|
||||
|
||||
UP025.py:30:8: UP025 [*] Remove unicode literals from strings
|
||||
UP025.py:28:8: UP025 [*] Remove unicode literals from strings
|
||||
|
|
||||
29 | f"foo"u"bar"
|
||||
30 | f"foo" u"bar"
|
||||
27 | f"foo"u"bar" # OK
|
||||
28 | f"foo" u"bar" # OK
|
||||
| ^^^^^^ UP025
|
||||
|
|
||||
= help: Remove unicode prefix
|
||||
|
||||
ℹ Safe fix
|
||||
27 27 | return"Hello"
|
||||
28 28 |
|
||||
29 29 | f"foo"u"bar"
|
||||
30 |-f"foo" u"bar"
|
||||
30 |+f"foo" "bar"
|
||||
25 25 | return"Hello" # OK
|
||||
26 26 |
|
||||
27 27 | f"foo"u"bar" # OK
|
||||
28 |-f"foo" u"bar" # OK
|
||||
28 |+f"foo" "bar" # OK
|
||||
|
||||
|
||||
|
||||
@@ -1141,6 +1141,7 @@ UP032_0.py:240:1: UP032 [*] Use f-string instead of `format` call
|
||||
243 |+)
|
||||
244 244 |
|
||||
245 245 | ("{}" "{{{}}}").format(a, b)
|
||||
246 246 |
|
||||
|
||||
UP032_0.py:245:1: UP032 [*] Use f-string instead of `format` call
|
||||
|
|
||||
@@ -1148,6 +1149,8 @@ UP032_0.py:245:1: UP032 [*] Use f-string instead of `format` call
|
||||
244 |
|
||||
245 | ("{}" "{{{}}}").format(a, b)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP032
|
||||
246 |
|
||||
247 | # The dictionary should be parenthesized.
|
||||
|
|
||||
= help: Convert to f-string
|
||||
|
||||
@@ -1157,5 +1160,63 @@ UP032_0.py:245:1: UP032 [*] Use f-string instead of `format` call
|
||||
244 244 |
|
||||
245 |-("{}" "{{{}}}").format(a, b)
|
||||
245 |+(f"{a}" f"{{{b}}}")
|
||||
246 246 |
|
||||
247 247 | # The dictionary should be parenthesized.
|
||||
248 248 | "{}".format({0: 1}[0])
|
||||
|
||||
UP032_0.py:248:1: UP032 [*] Use f-string instead of `format` call
|
||||
|
|
||||
247 | # The dictionary should be parenthesized.
|
||||
248 | "{}".format({0: 1}[0])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ UP032
|
||||
249 |
|
||||
250 | # The dictionary should be parenthesized.
|
||||
|
|
||||
= help: Convert to f-string
|
||||
|
||||
ℹ Safe fix
|
||||
245 245 | ("{}" "{{{}}}").format(a, b)
|
||||
246 246 |
|
||||
247 247 | # The dictionary should be parenthesized.
|
||||
248 |-"{}".format({0: 1}[0])
|
||||
248 |+f"{({0: 1}[0])}"
|
||||
249 249 |
|
||||
250 250 | # The dictionary should be parenthesized.
|
||||
251 251 | "{}".format({0: 1}.bar)
|
||||
|
||||
UP032_0.py:251:1: UP032 [*] Use f-string instead of `format` call
|
||||
|
|
||||
250 | # The dictionary should be parenthesized.
|
||||
251 | "{}".format({0: 1}.bar)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ UP032
|
||||
252 |
|
||||
253 | # The dictionary should be parenthesized.
|
||||
|
|
||||
= help: Convert to f-string
|
||||
|
||||
ℹ Safe fix
|
||||
248 248 | "{}".format({0: 1}[0])
|
||||
249 249 |
|
||||
250 250 | # The dictionary should be parenthesized.
|
||||
251 |-"{}".format({0: 1}.bar)
|
||||
251 |+f"{({0: 1}.bar)}"
|
||||
252 252 |
|
||||
253 253 | # The dictionary should be parenthesized.
|
||||
254 254 | "{}".format({0: 1}())
|
||||
|
||||
UP032_0.py:254:1: UP032 [*] Use f-string instead of `format` call
|
||||
|
|
||||
253 | # The dictionary should be parenthesized.
|
||||
254 | "{}".format({0: 1}())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ UP032
|
||||
|
|
||||
= help: Convert to f-string
|
||||
|
||||
ℹ Safe fix
|
||||
251 251 | "{}".format({0: 1}.bar)
|
||||
252 252 |
|
||||
253 253 | # The dictionary should be parenthesized.
|
||||
254 |-"{}".format({0: 1}())
|
||||
254 |+f"{({0: 1}())}"
|
||||
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ mod tests {
|
||||
#[test_case(Rule::QuadraticListSummation, Path::new("RUF017_0.py"))]
|
||||
#[test_case(Rule::AssignmentInAssert, Path::new("RUF018.py"))]
|
||||
#[test_case(Rule::UnnecessaryKeyCheck, Path::new("RUF019.py"))]
|
||||
#[test_case(Rule::NeverUnion, Path::new("RUF020.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -112,11 +112,15 @@ pub(crate) fn asyncio_dangling_binding(
|
||||
for binding_id in scope.binding_ids() {
|
||||
// If the binding itself is used, or it's not an assignment, skip it.
|
||||
let binding = semantic.binding(binding_id);
|
||||
if binding.is_used() || !binding.kind.is_assignment() {
|
||||
if binding.is_used()
|
||||
|| binding.is_global()
|
||||
|| binding.is_nonlocal()
|
||||
|| !binding.kind.is_assignment()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, any dangling tasks, including those that are shadowed, as in:
|
||||
// Otherwise, flag any dangling tasks, including those that are shadowed, as in:
|
||||
// ```python
|
||||
// if x > 0:
|
||||
// task = asyncio.create_task(make_request())
|
||||
@@ -127,7 +131,11 @@ pub(crate) fn asyncio_dangling_binding(
|
||||
std::iter::successors(Some(binding_id), |id| semantic.shadowed_binding(*id))
|
||||
{
|
||||
let binding = semantic.binding(binding_id);
|
||||
if binding.is_used() || !binding.kind.is_assignment() {
|
||||
if binding.is_used()
|
||||
|| binding.is_global()
|
||||
|| binding.is_nonlocal()
|
||||
|| !binding.kind.is_assignment()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ pub(crate) use invalid_index_type::*;
|
||||
pub(crate) use invalid_pyproject_toml::*;
|
||||
pub(crate) use mutable_class_default::*;
|
||||
pub(crate) use mutable_dataclass_default::*;
|
||||
pub(crate) use never_union::*;
|
||||
pub(crate) use pairwise_over_zipped::*;
|
||||
pub(crate) use quadratic_list_summation::*;
|
||||
pub(crate) use static_key_dict_comprehension::*;
|
||||
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||
pub(crate) use unnecessary_key_check::*;
|
||||
@@ -30,6 +32,7 @@ mod invalid_index_type;
|
||||
mod invalid_pyproject_toml;
|
||||
mod mutable_class_default;
|
||||
mod mutable_dataclass_default;
|
||||
mod never_union;
|
||||
mod pairwise_over_zipped;
|
||||
mod static_key_dict_comprehension;
|
||||
mod unnecessary_iterable_allocation_for_first_element;
|
||||
@@ -44,6 +47,5 @@ pub(crate) enum Context {
|
||||
Docstring,
|
||||
Comment,
|
||||
}
|
||||
pub(crate) use quadratic_list_summation::*;
|
||||
|
||||
mod quadratic_list_summation;
|
||||
|
||||
212
crates/ruff_linter/src/rules/ruff/rules/never_union.rs
Normal file
212
crates/ruff_linter/src/rules/ruff/rules/never_union.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `typing.NoReturn` and `typing.Never` in union types.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `typing.NoReturn` and `typing.Never` are special types, used to indicate
|
||||
/// that a function never returns, or that a type has no values.
|
||||
///
|
||||
/// Including `typing.NoReturn` or `typing.Never` in a union type is redundant,
|
||||
/// as, e.g., `typing.Never | T` is equivalent to `T`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Never
|
||||
///
|
||||
///
|
||||
/// def func() -> Never | int:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def func() -> int:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - [Python documentation: `typing.Never`](https://docs.python.org/3/library/typing.html#typing.Never)
|
||||
/// - [Python documentation: `typing.NoReturn`](https://docs.python.org/3/library/typing.html#typing.NoReturn)
|
||||
#[violation]
|
||||
pub struct NeverUnion {
|
||||
never_like: NeverLike,
|
||||
union_like: UnionLike,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for NeverUnion {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self {
|
||||
never_like,
|
||||
union_like,
|
||||
} = self;
|
||||
match union_like {
|
||||
UnionLike::BinOp => {
|
||||
format!("`{never_like} | T` is equivalent to `T`")
|
||||
}
|
||||
UnionLike::TypingUnion => {
|
||||
format!("`Union[{never_like}, T]` is equivalent to `T`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
let Self { never_like, .. } = self;
|
||||
format!("Remove `{never_like}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF020
|
||||
pub(crate) fn never_union(checker: &mut Checker, expr: &Expr) {
|
||||
match expr {
|
||||
// Ex) `typing.NoReturn | int`
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
left,
|
||||
right,
|
||||
range: _,
|
||||
}) => {
|
||||
// Analyze the left-hand side of the `|` operator.
|
||||
if let Some(never_like) = NeverLike::from_expr(left, checker.semantic()) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NeverUnion {
|
||||
never_like,
|
||||
union_like: UnionLike::BinOp,
|
||||
},
|
||||
left.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
checker.locator().slice(right.as_ref()).to_string(),
|
||||
expr.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
// Analyze the right-hand side of the `|` operator.
|
||||
if let Some(never_like) = NeverLike::from_expr(right, checker.semantic()) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NeverUnion {
|
||||
never_like,
|
||||
union_like: UnionLike::BinOp,
|
||||
},
|
||||
right.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
checker.locator().slice(left.as_ref()).to_string(),
|
||||
expr.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) `typing.Union[typing.NoReturn, int]`
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) if checker.semantic().match_typing_expr(value, "Union") => {
|
||||
let Expr::Tuple(ast::ExprTuple {
|
||||
elts,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) = slice.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Analyze each element of the `Union`.
|
||||
for elt in elts {
|
||||
if let Some(never_like) = NeverLike::from_expr(elt, checker.semantic()) {
|
||||
// Collect the other elements of the `Union`.
|
||||
let rest = elts
|
||||
.iter()
|
||||
.filter(|other| *other != elt)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Ignore, e.g., `typing.Union[typing.NoReturn]`.
|
||||
if rest.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NeverUnion {
|
||||
never_like,
|
||||
union_like: UnionLike::TypingUnion,
|
||||
},
|
||||
elt.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
if let [only] = rest.as_slice() {
|
||||
// Ex) `typing.Union[typing.NoReturn, int]` -> `int`
|
||||
checker.locator().slice(only).to_string()
|
||||
} else {
|
||||
// Ex) `typing.Union[typing.NoReturn, int, str]` -> `typing.Union[int, str]`
|
||||
checker
|
||||
.generator()
|
||||
.expr(&Expr::Subscript(ast::ExprSubscript {
|
||||
value: value.clone(),
|
||||
slice: Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
elts: rest,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
}))
|
||||
},
|
||||
expr.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum UnionLike {
|
||||
/// E.g., `typing.Union[int, str]`
|
||||
TypingUnion,
|
||||
/// E.g., `int | str`
|
||||
BinOp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum NeverLike {
|
||||
/// E.g., `typing.NoReturn`
|
||||
NoReturn,
|
||||
/// E.g., `typing.Never`
|
||||
Never,
|
||||
}
|
||||
|
||||
impl NeverLike {
|
||||
fn from_expr(expr: &Expr, semantic: &ruff_python_semantic::SemanticModel) -> Option<Self> {
|
||||
let call_path = semantic.resolve_call_path(expr)?;
|
||||
if semantic.match_typing_call_path(&call_path, "NoReturn") {
|
||||
Some(NeverLike::NoReturn)
|
||||
} else if semantic.match_typing_call_path(&call_path, "Never") {
|
||||
Some(NeverLike::Never)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NeverLike {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NeverLike::NoReturn => f.write_str("NoReturn"),
|
||||
NeverLike::Never => f.write_str("Never"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF020.py:3:7: RUF020 [*] `Union[Never, T]` is equivalent to `T`
|
||||
|
|
||||
1 | from typing import Never, NoReturn, Union
|
||||
2 |
|
||||
3 | Union[Never, int]
|
||||
| ^^^^^ RUF020
|
||||
4 | Union[NoReturn, int]
|
||||
5 | Never | int
|
||||
|
|
||||
= help: Remove `Never`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Never, NoReturn, Union
|
||||
2 2 |
|
||||
3 |-Union[Never, int]
|
||||
3 |+int
|
||||
4 4 | Union[NoReturn, int]
|
||||
5 5 | Never | int
|
||||
6 6 | NoReturn | int
|
||||
|
||||
RUF020.py:4:7: RUF020 [*] `Union[NoReturn, T]` is equivalent to `T`
|
||||
|
|
||||
3 | Union[Never, int]
|
||||
4 | Union[NoReturn, int]
|
||||
| ^^^^^^^^ RUF020
|
||||
5 | Never | int
|
||||
6 | NoReturn | int
|
||||
|
|
||||
= help: Remove `NoReturn`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | from typing import Never, NoReturn, Union
|
||||
2 2 |
|
||||
3 3 | Union[Never, int]
|
||||
4 |-Union[NoReturn, int]
|
||||
4 |+int
|
||||
5 5 | Never | int
|
||||
6 6 | NoReturn | int
|
||||
7 7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
|
||||
RUF020.py:5:1: RUF020 [*] `Never | T` is equivalent to `T`
|
||||
|
|
||||
3 | Union[Never, int]
|
||||
4 | Union[NoReturn, int]
|
||||
5 | Never | int
|
||||
| ^^^^^ RUF020
|
||||
6 | NoReturn | int
|
||||
7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
|
|
||||
= help: Remove `Never`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 |
|
||||
3 3 | Union[Never, int]
|
||||
4 4 | Union[NoReturn, int]
|
||||
5 |-Never | int
|
||||
5 |+int
|
||||
6 6 | NoReturn | int
|
||||
7 7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
8 8 | Union[NoReturn, int, float]
|
||||
|
||||
RUF020.py:6:1: RUF020 [*] `NoReturn | T` is equivalent to `T`
|
||||
|
|
||||
4 | Union[NoReturn, int]
|
||||
5 | Never | int
|
||||
6 | NoReturn | int
|
||||
| ^^^^^^^^ RUF020
|
||||
7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
8 | Union[NoReturn, int, float]
|
||||
|
|
||||
= help: Remove `NoReturn`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | Union[Never, int]
|
||||
4 4 | Union[NoReturn, int]
|
||||
5 5 | Never | int
|
||||
6 |-NoReturn | int
|
||||
6 |+int
|
||||
7 7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
8 8 | Union[NoReturn, int, float]
|
||||
|
||||
RUF020.py:7:13: RUF020 [*] `Union[Never, T]` is equivalent to `T`
|
||||
|
|
||||
5 | Never | int
|
||||
6 | NoReturn | int
|
||||
7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
| ^^^^^ RUF020
|
||||
8 | Union[NoReturn, int, float]
|
||||
|
|
||||
= help: Remove `Never`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | Union[NoReturn, int]
|
||||
5 5 | Never | int
|
||||
6 6 | NoReturn | int
|
||||
7 |-Union[Union[Never, int], Union[NoReturn, int]]
|
||||
7 |+Union[int, Union[NoReturn, int]]
|
||||
8 8 | Union[NoReturn, int, float]
|
||||
|
||||
RUF020.py:7:32: RUF020 [*] `Union[NoReturn, T]` is equivalent to `T`
|
||||
|
|
||||
5 | Never | int
|
||||
6 | NoReturn | int
|
||||
7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
| ^^^^^^^^ RUF020
|
||||
8 | Union[NoReturn, int, float]
|
||||
|
|
||||
= help: Remove `NoReturn`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | Union[NoReturn, int]
|
||||
5 5 | Never | int
|
||||
6 6 | NoReturn | int
|
||||
7 |-Union[Union[Never, int], Union[NoReturn, int]]
|
||||
7 |+Union[Union[Never, int], int]
|
||||
8 8 | Union[NoReturn, int, float]
|
||||
|
||||
RUF020.py:8:7: RUF020 [*] `Union[NoReturn, T]` is equivalent to `T`
|
||||
|
|
||||
6 | NoReturn | int
|
||||
7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
8 | Union[NoReturn, int, float]
|
||||
| ^^^^^^^^ RUF020
|
||||
|
|
||||
= help: Remove `NoReturn`
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | Never | int
|
||||
6 6 | NoReturn | int
|
||||
7 7 | Union[Union[Never, int], Union[NoReturn, int]]
|
||||
8 |-Union[NoReturn, int, float]
|
||||
8 |+Union[int, float]
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::directives;
|
||||
use crate::fix::{fix_file, FixResult};
|
||||
use crate::linter::{check_path, LinterResult};
|
||||
use crate::linter::{check_path, LinterResult, TokenSource};
|
||||
use crate::message::{Emitter, EmitterContext, Message, TextEmitter};
|
||||
use crate::packaging::detect_package_root;
|
||||
use crate::registry::AsRule;
|
||||
@@ -129,7 +129,6 @@ pub(crate) fn test_contents<'a>(
|
||||
path,
|
||||
path.parent()
|
||||
.and_then(|parent| detect_package_root(parent, &settings.namespace_packages)),
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -138,6 +137,7 @@ pub(crate) fn test_contents<'a>(
|
||||
flags::Noqa::Enabled,
|
||||
source_kind,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
|
||||
let source_has_errors = error.is_some();
|
||||
@@ -195,7 +195,6 @@ pub(crate) fn test_contents<'a>(
|
||||
} = check_path(
|
||||
path,
|
||||
None,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -204,6 +203,7 @@ pub(crate) fn test_contents<'a>(
|
||||
flags::Noqa::Enabled,
|
||||
&transformed,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
|
||||
if let Some(fixed_error) = fixed_error {
|
||||
|
||||
@@ -1505,6 +1505,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the expression as a `typing.Optional`-style optional.
|
||||
pub fn typing_optional(elt: Expr, binding: String) -> Expr {
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
@@ -1518,18 +1519,19 @@ pub fn typing_optional(elt: Expr, binding: String) -> Expr {
|
||||
})
|
||||
}
|
||||
|
||||
/// Format the expressions as a `typing.Union`-style union.
|
||||
pub fn typing_union(elts: &[Expr], binding: String) -> Expr {
|
||||
fn tuple(elts: &[Expr]) -> Expr {
|
||||
fn tuple(elts: &[Expr], binding: String) -> Expr {
|
||||
match elts {
|
||||
[] => Expr::Tuple(ast::ExprTuple {
|
||||
elts: vec![],
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
}),
|
||||
[Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts),
|
||||
[Expr::Tuple(ast::ExprTuple { elts, .. })] => typing_union(elts, binding),
|
||||
[elt] => elt.clone(),
|
||||
[rest @ .., elt] => Expr::BinOp(ast::ExprBinOp {
|
||||
left: Box::new(tuple(rest)),
|
||||
left: Box::new(tuple(rest, binding)),
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(elt.clone()),
|
||||
range: TextRange::default(),
|
||||
@@ -1539,11 +1541,11 @@ pub fn typing_union(elts: &[Expr], binding: String) -> Expr {
|
||||
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
id: binding,
|
||||
id: binding.clone(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
})),
|
||||
slice: Box::new(tuple(elts)),
|
||||
slice: Box::new(tuple(elts, binding)),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
})
|
||||
|
||||
@@ -253,4 +253,23 @@ if True:
|
||||
# empty line(s) at the end of the file due to nested function
|
||||
if True:
|
||||
def nested_trailing_function():
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
def overload1(): ... # trailing comment
|
||||
def overload1(a: int): ...
|
||||
|
||||
def overload2(): ... # trailing comment
|
||||
|
||||
def overload2(a: int): ...
|
||||
|
||||
def overload3():
|
||||
...
|
||||
# trailing comment
|
||||
def overload3(a: int): ...
|
||||
|
||||
def overload4():
|
||||
...
|
||||
# trailing comment
|
||||
|
||||
def overload4(a: int): ...
|
||||
|
||||
157
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/long_type_annotations.py
vendored
Normal file
157
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/long_type_annotations.py
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
x1: A[b] | EventHandler | EventSpec | list[EventHandler | EventSpec] | Other | More | AndMore | None = None
|
||||
|
||||
x2: "VeryLongClassNameWithAwkwardGenericSubtype[int] |" "VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
|
||||
x6: VeryLongClassNameWithAwkwardGenericSubtype[
|
||||
integeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer,
|
||||
VeryLongClassNameWithAwkwardGenericSubtype,
|
||||
str
|
||||
] = True
|
||||
|
||||
|
||||
x7: CustomTrainingJob | CustomContainerTrainingJob | CustomPythonPackageTrainingJob
|
||||
x8: (
|
||||
None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
x9: None | (
|
||||
datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
|
||||
x10: (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbb,
|
||||
Subscript
|
||||
| None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset,
|
||||
],
|
||||
bbb[other],
|
||||
) = None
|
||||
|
||||
x11: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x12: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
|
||||
x13: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x14: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x15: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x16: None | Literal[
|
||||
"split",
|
||||
"a bit longer",
|
||||
"records",
|
||||
"index",
|
||||
"table",
|
||||
"columns",
|
||||
"values",
|
||||
] = None
|
||||
|
||||
x17: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
]
|
||||
|
||||
|
||||
class Test:
|
||||
safe_age: Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
applied_fixes: int # the number of fixes that this user applied. Used for ranking the users with the most applied fixes.
|
||||
string_annotation: "Test" # a long comment after a quoted, runtime-only type annotation
|
||||
|
||||
|
||||
##########
|
||||
# Comments
|
||||
|
||||
leading: (
|
||||
# Leading comment
|
||||
None | dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_with_value: (
|
||||
# Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
leading_open_parentheses: ( # Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_open_parentheses_with_value: ( # Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
trailing: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
)
|
||||
|
||||
trailing_with_value: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
) = None
|
||||
|
||||
trailing_own_line: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
)
|
||||
|
||||
trailing_own_line_with_value: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
) = None
|
||||
|
||||
nested_comment: None | [
|
||||
# a list of strings
|
||||
str
|
||||
] = None
|
||||
9
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.options.json
vendored
Normal file
9
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.options.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"target_version": "py38"
|
||||
},
|
||||
{
|
||||
"target_version": "py39",
|
||||
"preview": "enabled"
|
||||
}
|
||||
]
|
||||
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.options.json
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.options.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"target_version": "py39",
|
||||
"preview": "enabled"
|
||||
}
|
||||
]
|
||||
88
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py
vendored
Normal file
88
crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parenthesizing the with because it can make all with items fit by just breaking
|
||||
# around parentheses. We don't implement this optimisation because it makes it difficult to see where
|
||||
# the different context managers start and end.
|
||||
with cmd, xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd, another, and_more as x:
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses (can_omit_optional_parentheses)
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method(): pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parentheses here because it can make the entire with
|
||||
# header fit without requiring parentheses to do so.
|
||||
# We don't implement this optimisation because it very difficult to see where
|
||||
# the different context managers start or end.
|
||||
with cmd, xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd, another, and_more as x:
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method(): pass
|
||||
|
||||
# Parenthesize the with items if it makes them fit. Breaking the binary expression isn't
|
||||
# necessary because the entire items fit just into the 88 character limit.
|
||||
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes this binary expression but also preserves the parentheses of the first with-item.
|
||||
# It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses
|
||||
# like in this case.
|
||||
with (
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
if True:
|
||||
with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext():
|
||||
pass
|
||||
|
||||
with (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c):
|
||||
pass
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Expr {
|
||||
///
|
||||
/// This mimics Black's [`_maybe_split_omitting_optional_parens`](https://github.com/psf/black/blob/d1248ca9beaf0ba526d265f4108836d89cf551b7/src/black/linegen.py#L746-L820)
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool {
|
||||
pub(crate) fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool {
|
||||
let mut visitor = CanOmitOptionalParenthesesVisitor::new(context);
|
||||
visitor.visit_subexpression(expr);
|
||||
|
||||
@@ -538,8 +538,8 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
|
||||
false
|
||||
} else if visitor.max_precedence_count > 1 {
|
||||
false
|
||||
} else if visitor.max_precedence == OperatorPrecedence::None && expr.is_lambda_expr() {
|
||||
// Micha: This seems to exclusively apply for lambda expressions where the body ends in a subscript.
|
||||
} else if visitor.max_precedence == OperatorPrecedence::None {
|
||||
// Micha: This seems to apply for lambda expressions where the body ends in a subscript.
|
||||
// Subscripts are excluded by default because breaking them looks odd, but it seems to be fine for lambda expression.
|
||||
//
|
||||
// ```python
|
||||
@@ -566,10 +566,19 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
|
||||
// ]
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// Another case are method chains:
|
||||
// ```python
|
||||
// xxxxxxxx.some_kind_of_method(
|
||||
// some_argument=[
|
||||
// "first",
|
||||
// "second",
|
||||
// "third",
|
||||
// ]
|
||||
// ).another_method(a)
|
||||
// ```
|
||||
true
|
||||
} else if visitor.max_precedence == OperatorPrecedence::Attribute
|
||||
&& (expr.is_lambda_expr() || expr.is_named_expr_expr())
|
||||
{
|
||||
} else if visitor.max_precedence == OperatorPrecedence::Attribute {
|
||||
// A single method call inside a named expression (`:=`) or as the body of a lambda function:
|
||||
// ```python
|
||||
// kwargs["open_with"] = lambda path, _: fsspec.open(
|
||||
@@ -1195,3 +1204,75 @@ impl From<Operator> for OperatorPrecedence {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `expr` is an expression that can be split into multiple lines.
|
||||
///
|
||||
/// Returns `false` for expressions that are guaranteed to never split.
|
||||
pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) -> bool {
|
||||
match expr {
|
||||
// Single token expressions. They never have any split points.
|
||||
Expr::NamedExpr(_)
|
||||
| Expr::Name(_)
|
||||
| Expr::NumberLiteral(_)
|
||||
| Expr::BooleanLiteral(_)
|
||||
| Expr::NoneLiteral(_)
|
||||
| Expr::EllipsisLiteral(_)
|
||||
| Expr::Slice(_)
|
||||
| Expr::IpyEscapeCommand(_) => false,
|
||||
|
||||
// Expressions that insert split points when parenthesized.
|
||||
Expr::Compare(_)
|
||||
| Expr::BinOp(_)
|
||||
| Expr::BoolOp(_)
|
||||
| Expr::IfExp(_)
|
||||
| Expr::GeneratorExp(_)
|
||||
| Expr::Subscript(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::ListComp(_)
|
||||
| Expr::SetComp(_)
|
||||
| Expr::DictComp(_)
|
||||
| Expr::YieldFrom(_) => true,
|
||||
|
||||
// Sequence types can split if they contain at least one element.
|
||||
Expr::Tuple(tuple) => !tuple.elts.is_empty(),
|
||||
Expr::Dict(dict) => !dict.values.is_empty(),
|
||||
Expr::Set(set) => !set.elts.is_empty(),
|
||||
Expr::List(list) => !list.elts.is_empty(),
|
||||
|
||||
Expr::UnaryOp(unary) => is_splittable_expression(unary.operand.as_ref(), context),
|
||||
Expr::Yield(ast::ExprYield { value, .. }) => value.is_some(),
|
||||
|
||||
Expr::Call(ast::ExprCall {
|
||||
arguments, func, ..
|
||||
}) => {
|
||||
!arguments.is_empty()
|
||||
|| is_expression_parenthesized(
|
||||
func.as_ref().into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
)
|
||||
}
|
||||
|
||||
// String like literals can expand if they are implicit concatenated.
|
||||
Expr::FString(fstring) => fstring.value.is_implicit_concatenated(),
|
||||
Expr::StringLiteral(string) => string.value.is_implicit_concatenated(),
|
||||
Expr::BytesLiteral(bytes) => bytes.value.is_implicit_concatenated(),
|
||||
|
||||
// Expressions that have no split points per se, but they contain nested sub expressions that might expand.
|
||||
Expr::Lambda(ast::ExprLambda {
|
||||
body: expression, ..
|
||||
})
|
||||
| Expr::Starred(ast::ExprStarred {
|
||||
value: expression, ..
|
||||
})
|
||||
| Expr::Attribute(ast::ExprAttribute {
|
||||
value: expression, ..
|
||||
}) => {
|
||||
is_expression_parenthesized(
|
||||
expression.into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
) || is_splittable_expression(expression.as_ref(), context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use ruff_formatter::write;
|
||||
|
||||
use ruff_python_ast::WithItem;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
@@ -8,6 +7,7 @@ use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, parenthesized, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_wrap_multiple_context_managers_in_parens_enabled;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatWithItem;
|
||||
@@ -23,26 +23,49 @@ impl FormatNodeRule<WithItem> for FormatWithItem {
|
||||
let comments = f.context().comments().clone();
|
||||
let trailing_as_comments = comments.dangling(item);
|
||||
|
||||
// Prefer keeping parentheses for already parenthesized expressions over
|
||||
// parenthesizing other nodes.
|
||||
let parenthesize = if is_expression_parenthesized(
|
||||
let is_parenthesized = is_expression_parenthesized(
|
||||
context_expr.into(),
|
||||
f.context().comments().ranges(),
|
||||
f.context().source(),
|
||||
) {
|
||||
Parenthesize::IfBreaks
|
||||
} else {
|
||||
Parenthesize::IfRequired
|
||||
};
|
||||
);
|
||||
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
context_expr,
|
||||
item,
|
||||
parenthesize
|
||||
)]
|
||||
)?;
|
||||
// Remove the parentheses of the `with_items` if the with statement adds parentheses
|
||||
if f.context().node_level().is_parenthesized()
|
||||
&& is_wrap_multiple_context_managers_in_parens_enabled(f.context())
|
||||
{
|
||||
if is_parenthesized {
|
||||
// ...except if the with item is parenthesized, then use this with item as a preferred breaking point
|
||||
// or when it has comments, then parenthesize it to prevent comments from moving.
|
||||
maybe_parenthesize_expression(
|
||||
context_expr,
|
||||
item,
|
||||
Parenthesize::IfBreaksOrIfRequired,
|
||||
)
|
||||
.fmt(f)?;
|
||||
} else {
|
||||
context_expr
|
||||
.format()
|
||||
.with_options(Parentheses::Never)
|
||||
.fmt(f)?;
|
||||
}
|
||||
} else {
|
||||
// Prefer keeping parentheses for already parenthesized expressions over
|
||||
// parenthesizing other nodes.
|
||||
let parenthesize = if is_parenthesized {
|
||||
Parenthesize::IfBreaks
|
||||
} else {
|
||||
Parenthesize::IfRequired
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
context_expr,
|
||||
item,
|
||||
parenthesize
|
||||
)]
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(optional_vars) = optional_vars {
|
||||
write!(f, [space(), token("as"), space()])?;
|
||||
|
||||
@@ -25,6 +25,11 @@ pub(crate) const fn is_prefer_splitting_right_hand_side_of_assignments_enabled(
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`parenthesize_long_type_hints`](https://github.com/astral-sh/ruff/issues/8894) preview style is enabled.
|
||||
pub(crate) const fn is_parenthesize_long_type_hints_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`no_blank_line_before_class_docstring`] preview style is enabled.
|
||||
///
|
||||
/// [`no_blank_line_before_class_docstring`]: https://github.com/astral-sh/ruff/issues/8888
|
||||
@@ -33,3 +38,22 @@ pub(crate) const fn is_no_blank_line_before_class_docstring_enabled(
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`wrap_multiple_context_managers_in_parens`](https://github.com/astral-sh/ruff/issues/8889) preview style is enabled.
|
||||
///
|
||||
/// Unlike Black, we re-use the same preview style feature flag for [`improved_async_statements_handling`](https://github.com/astral-sh/ruff/issues/8890)
|
||||
pub(crate) const fn is_wrap_multiple_context_managers_in_parens_enabled(
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`module_docstring_newlines`](https://github.com/astral-sh/ruff/issues/7995) preview style is enabled.
|
||||
pub(crate) const fn is_module_docstring_newlines_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`dummy_implementations`](https://github.com/astral-sh/ruff/issues/8357) preview style is enabled.
|
||||
pub(crate) const fn is_dummy_implementations_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::comments::{
|
||||
leading_alternate_branch_comments, trailing_comments, SourceComment, SuppressionKind,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_dummy_implementations_enabled;
|
||||
use crate::statement::suite::{contains_only_an_ellipsis, SuiteKind};
|
||||
use crate::verbatim::write_suppressed_clause_header;
|
||||
|
||||
@@ -391,9 +392,13 @@ pub(crate) fn clause_body<'a>(
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatClauseBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
// In stable, stubs are only collapsed in stub files, in preview this is consistently
|
||||
// applied everywhere
|
||||
if (f.options().source_type().is_stub() || f.options().preview().is_enabled())
|
||||
// In stable, stubs are only collapsed in stub files, in preview stubs in functions
|
||||
// or classes are collapsed too
|
||||
let should_collapse_stub = f.options().source_type().is_stub()
|
||||
|| (is_dummy_implementations_enabled(f.context())
|
||||
&& matches!(self.kind, SuiteKind::Function | SuiteKind::Class));
|
||||
|
||||
if should_collapse_stub
|
||||
&& contains_only_an_ellipsis(self.body, f.context().comments())
|
||||
&& self.trailing_comments.is_empty()
|
||||
{
|
||||
|
||||
@@ -2,9 +2,13 @@ use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAnnAssign;
|
||||
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
use crate::expression::has_parentheses;
|
||||
use crate::expression::parentheses::Parentheses;
|
||||
use crate::expression::{has_parentheses, is_splittable_expression};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
|
||||
use crate::preview::{
|
||||
is_parenthesize_long_type_hints_enabled,
|
||||
is_prefer_splitting_right_hand_side_of_assignments_enabled,
|
||||
};
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
};
|
||||
@@ -27,7 +31,11 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
|
||||
if let Some(value) = value {
|
||||
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
|
||||
&& has_parentheses(annotation, f.context()).is_some()
|
||||
// The `has_parentheses` check can be removed when stabilizing `is_parenthesize_long_type_hints`.
|
||||
// because `is_splittable_expression` covers both.
|
||||
&& (has_parentheses(annotation, f.context()).is_some()
|
||||
|| (is_parenthesize_long_type_hints_enabled(f.context())
|
||||
&& is_splittable_expression(annotation, f.context())))
|
||||
{
|
||||
FormatStatementsLastExpression::RightToLeft {
|
||||
before_operator: AnyBeforeOperator::Expression(annotation),
|
||||
@@ -37,10 +45,28 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
}
|
||||
.fmt(f)?;
|
||||
} else {
|
||||
// Remove unnecessary parentheses around the annotation if the parenthesize long type hints preview style is enabled.
|
||||
// Ensure we keep the parentheses if the annotation has any comments.
|
||||
if is_parenthesize_long_type_hints_enabled(f.context()) {
|
||||
if f.context().comments().has_leading(annotation.as_ref())
|
||||
|| f.context().comments().has_trailing(annotation.as_ref())
|
||||
{
|
||||
annotation
|
||||
.format()
|
||||
.with_options(Parentheses::Always)
|
||||
.fmt(f)?;
|
||||
} else {
|
||||
annotation
|
||||
.format()
|
||||
.with_options(Parentheses::Never)
|
||||
.fmt(f)?;
|
||||
}
|
||||
} else {
|
||||
annotation.format().fmt(f)?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
annotation.format(),
|
||||
space(),
|
||||
token("="),
|
||||
space(),
|
||||
@@ -49,7 +75,19 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
annotation.format().fmt(f)?;
|
||||
// Parenthesize the value and inline the comment if it is a "simple" type annotation, similar
|
||||
// to what we do with the value.
|
||||
// ```python
|
||||
// class Test:
|
||||
// safe_age: (
|
||||
// Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
// )
|
||||
// ```
|
||||
if is_parenthesize_long_type_hints_enabled(f.context()) {
|
||||
FormatStatementsLastExpression::left_to_right(annotation, item).fmt(f)?;
|
||||
} else {
|
||||
annotation.format().fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
if f.options().source_type().is_ipynb()
|
||||
|
||||
@@ -9,11 +9,18 @@ use crate::comments::{
|
||||
};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
|
||||
is_expression_parenthesized, optional_parentheses, NeedsParentheses, OptionalParentheses,
|
||||
Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::expression::{
|
||||
can_omit_optional_parentheses, has_own_parentheses, has_parentheses,
|
||||
maybe_parenthesize_expression,
|
||||
};
|
||||
use crate::expression::{has_own_parentheses, has_parentheses, maybe_parenthesize_expression};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
|
||||
use crate::preview::{
|
||||
is_parenthesize_long_type_hints_enabled,
|
||||
is_prefer_splitting_right_hand_side_of_assignments_enabled,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -686,8 +693,17 @@ impl Format<PyFormatContext<'_>> for AnyBeforeOperator<'_> {
|
||||
}
|
||||
// Never parenthesize targets that come with their own parentheses, e.g. don't parenthesize lists or dictionary literals.
|
||||
else if should_parenthesize_target(expression, f.context()) {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
if is_parenthesize_long_type_hints_enabled(f.context())
|
||||
&& can_omit_optional_parentheses(expression, f.context())
|
||||
{
|
||||
optional_parentheses(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
} else {
|
||||
parenthesize_if_expands(
|
||||
&expression.format().with_options(Parentheses::Never),
|
||||
)
|
||||
.fmt(f)
|
||||
}
|
||||
} else {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
}
|
||||
|
||||
@@ -6,17 +6,21 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::builders::parenthesize_if_expands;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::parenthesized;
|
||||
use crate::expression::can_omit_optional_parentheses;
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, optional_parentheses, parenthesized,
|
||||
};
|
||||
use crate::other::commas;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_wrap_multiple_context_managers_in_parens_enabled;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
use crate::PyFormatOptions;
|
||||
use crate::{PyFormatOptions, PythonVersion};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtWith;
|
||||
|
||||
impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||
fn fmt_fields(&self, item: &StmtWith, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
fn fmt_fields(&self, with_stmt: &StmtWith, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
// The `with` statement can have one dangling comment on the open parenthesis, like:
|
||||
// ```python
|
||||
// with ( # comment
|
||||
@@ -31,9 +35,10 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||
// ...
|
||||
// ```
|
||||
let comments = f.context().comments().clone();
|
||||
let dangling_comments = comments.dangling(item.as_any_node_ref());
|
||||
let dangling_comments = comments.dangling(with_stmt.as_any_node_ref());
|
||||
let partition_point = dangling_comments.partition_point(|comment| {
|
||||
item.items
|
||||
with_stmt
|
||||
.items
|
||||
.first()
|
||||
.is_some_and(|with_item| with_item.start() > comment.start())
|
||||
});
|
||||
@@ -43,35 +48,26 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||
f,
|
||||
[
|
||||
clause_header(
|
||||
ClauseHeader::With(item),
|
||||
ClauseHeader::With(with_stmt),
|
||||
colon_comments,
|
||||
&format_with(|f| {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
item.is_async
|
||||
with_stmt
|
||||
.is_async
|
||||
.then_some(format_args![token("async"), space()]),
|
||||
token("with"),
|
||||
space()
|
||||
]
|
||||
)?;
|
||||
|
||||
if !parenthesized_comments.is_empty() {
|
||||
let joined = format_with(|f: &mut PyFormatter| {
|
||||
f.join_comma_separated(item.body.first().unwrap().start())
|
||||
.nodes(&item.items)
|
||||
.finish()
|
||||
});
|
||||
|
||||
parenthesized("(", &joined, ")")
|
||||
.with_dangling_comments(parenthesized_comments)
|
||||
.fmt(f)?;
|
||||
} else if should_parenthesize(item, f.options(), f.context())? {
|
||||
parenthesize_if_expands(&format_with(|f| {
|
||||
if parenthesized_comments.is_empty() {
|
||||
let format_items = format_with(|f| {
|
||||
let mut joiner =
|
||||
f.join_comma_separated(item.body.first().unwrap().start());
|
||||
f.join_comma_separated(with_stmt.body.first().unwrap().start());
|
||||
|
||||
for item in &item.items {
|
||||
for item in &with_stmt.items {
|
||||
joiner.entry_with_line_separator(
|
||||
item,
|
||||
&item.format(),
|
||||
@@ -79,26 +75,48 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||
);
|
||||
}
|
||||
joiner.finish()
|
||||
}))
|
||||
.fmt(f)?;
|
||||
} else if let [item] = item.items.as_slice() {
|
||||
// This is similar to `maybe_parenthesize_expression`, but we're not
|
||||
// dealing with an expression here, it's a `WithItem`.
|
||||
if comments.has_leading(item) || comments.has_trailing(item) {
|
||||
parenthesized("(", &item.format(), ")").fmt(f)?;
|
||||
} else {
|
||||
item.format().fmt(f)?;
|
||||
});
|
||||
|
||||
match should_parenthesize(with_stmt, f.options(), f.context())? {
|
||||
ParenthesizeWith::Optional => {
|
||||
optional_parentheses(&format_items).fmt(f)?;
|
||||
}
|
||||
ParenthesizeWith::IfExpands => {
|
||||
parenthesize_if_expands(&format_items).fmt(f)?;
|
||||
}
|
||||
ParenthesizeWith::UnlessCommented => {
|
||||
if let [item] = with_stmt.items.as_slice() {
|
||||
// This is similar to `maybe_parenthesize_expression`, but we're not
|
||||
// dealing with an expression here, it's a `WithItem`.
|
||||
if comments.has_leading(item) || comments.has_trailing(item)
|
||||
{
|
||||
parenthesized("(", &item.format(), ")").fmt(f)?;
|
||||
} else {
|
||||
item.format().fmt(f)?;
|
||||
}
|
||||
} else {
|
||||
f.join_with(format_args![token(","), space()])
|
||||
.entries(with_stmt.items.iter().formatted())
|
||||
.finish()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.join_with(format_args![token(","), space()])
|
||||
.entries(item.items.iter().formatted())
|
||||
.finish()?;
|
||||
let joined = format_with(|f: &mut PyFormatter| {
|
||||
f.join_comma_separated(with_stmt.body.first().unwrap().start())
|
||||
.nodes(&with_stmt.items)
|
||||
.finish()
|
||||
});
|
||||
|
||||
parenthesized("(", &joined, ")")
|
||||
.with_dangling_comments(parenthesized_comments)
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
),
|
||||
clause_body(&item.body, colon_comments)
|
||||
clause_body(&with_stmt.body, colon_comments)
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -113,24 +131,79 @@ impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the `with` items should be parenthesized, if at least one item expands.
|
||||
/// Determines whether the `with` items should be parenthesized (over parenthesizing each item),
|
||||
/// and if so, which parenthesizing layout to use.
|
||||
///
|
||||
/// Black parenthesizes `with` items if there's more than one item and they're already
|
||||
/// parenthesized, _or_ there's a single item with a trailing comma.
|
||||
/// Parenthesize `with` items if
|
||||
/// * The last item has a trailing comma (implying that the with items were parenthesized in the source)
|
||||
/// * There's more than one item and they're already parenthesized
|
||||
/// * There's more than one item, the [`wrap_multiple_context_managers_in_parens`](is_wrap_multiple_context_managers_in_parens) preview style is enabled,
|
||||
/// and the target python version is >= 3.9
|
||||
/// * There's a single non-parenthesized item. The function returns [`ParenthesizeWith::Optional`]
|
||||
/// if the parentheses can be omitted if breaking around parenthesized sub-expressions is sufficient
|
||||
/// to make the expression fit. It returns [`ParenthesizeWith::IfExpands`] otherwise.
|
||||
/// * The only item is parenthesized and has comments.
|
||||
fn should_parenthesize(
|
||||
with: &StmtWith,
|
||||
options: &PyFormatOptions,
|
||||
context: &PyFormatContext,
|
||||
) -> FormatResult<bool> {
|
||||
) -> FormatResult<ParenthesizeWith> {
|
||||
if has_magic_trailing_comma(with, options, context) {
|
||||
return Ok(true);
|
||||
return Ok(ParenthesizeWith::IfExpands);
|
||||
}
|
||||
|
||||
if are_with_items_parenthesized(with, context)? {
|
||||
return Ok(true);
|
||||
let can_parenthesize = (is_wrap_multiple_context_managers_in_parens_enabled(context)
|
||||
&& options.target_version() >= PythonVersion::Py39)
|
||||
|| are_with_items_parenthesized(with, context)?;
|
||||
|
||||
if !can_parenthesize {
|
||||
return Ok(ParenthesizeWith::UnlessCommented);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
if let [single] = with.items.as_slice() {
|
||||
return Ok(
|
||||
// If the with item itself has comments (not the context expression), then keep the parentheses
|
||||
if context.comments().has_leading(single) || context.comments().has_trailing(single) {
|
||||
ParenthesizeWith::IfExpands
|
||||
}
|
||||
// If it is the only expression and it has comments, then the with statement
|
||||
// as well as the with item add parentheses
|
||||
else if is_expression_parenthesized(
|
||||
(&single.context_expr).into(),
|
||||
context.comments().ranges(),
|
||||
context.source(),
|
||||
) {
|
||||
// Preserve the parentheses around the context expression instead of parenthesizing the entire
|
||||
// with items.
|
||||
ParenthesizeWith::UnlessCommented
|
||||
} else if is_wrap_multiple_context_managers_in_parens_enabled(context)
|
||||
&& can_omit_optional_parentheses(&single.context_expr, context)
|
||||
{
|
||||
ParenthesizeWith::Optional
|
||||
} else {
|
||||
ParenthesizeWith::IfExpands
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Always parenthesize multiple items
|
||||
Ok(ParenthesizeWith::IfExpands)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ParenthesizeWith {
|
||||
/// Don't wrap the with items in parentheses except if it is a single item
|
||||
/// and it has leading or trailing comment.
|
||||
///
|
||||
/// This is required because `are_with_items_parenthesized` cannot determine if
|
||||
/// `with (expr)` is a parenthesized expression or a parenthesized with item.
|
||||
UnlessCommented,
|
||||
|
||||
/// Wrap the with items in optional parentheses
|
||||
Optional,
|
||||
|
||||
/// Wrap the with items in parentheses if they expand
|
||||
IfExpands,
|
||||
}
|
||||
|
||||
fn has_magic_trailing_comma(
|
||||
|
||||
@@ -11,7 +11,10 @@ use crate::comments::{
|
||||
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
|
||||
use crate::expression::expr_string_literal::ExprStringLiteralKind;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_no_blank_line_before_class_docstring_enabled;
|
||||
use crate::preview::{
|
||||
is_dummy_implementations_enabled, is_module_docstring_newlines_enabled,
|
||||
is_no_blank_line_before_class_docstring_enabled,
|
||||
};
|
||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||
use crate::verbatim::{
|
||||
suppressed_node, write_suppressed_statements_starting_with_leading_comment,
|
||||
@@ -166,7 +169,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
&& self.kind == SuiteKind::Class
|
||||
{
|
||||
true
|
||||
} else if f.options().preview().is_enabled()
|
||||
} else if is_module_docstring_newlines_enabled(f.context())
|
||||
&& self.kind == SuiteKind::TopLevel
|
||||
&& DocstringStmt::try_from_statement(first.statement(), self.kind).is_some()
|
||||
{
|
||||
@@ -259,12 +262,31 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
f,
|
||||
)?;
|
||||
} else {
|
||||
match self.kind {
|
||||
SuiteKind::TopLevel => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
empty_line().fmt(f)?;
|
||||
// Preserve empty lines after a stub implementation but don't insert a new one if there isn't any present in the source.
|
||||
// This is useful when having multiple function overloads that should be grouped to getter by omitting new lines between them.
|
||||
let is_preceding_stub_function_without_empty_line =
|
||||
is_dummy_implementations_enabled(f.context())
|
||||
&& following.is_function_def_stmt()
|
||||
&& preceding
|
||||
.as_function_def_stmt()
|
||||
.is_some_and(|preceding_stub| {
|
||||
contains_only_an_ellipsis(
|
||||
&preceding_stub.body,
|
||||
f.context().comments(),
|
||||
) && lines_after_ignoring_end_of_line_trivia(
|
||||
preceding_stub.end(),
|
||||
f.context().source(),
|
||||
) < 2
|
||||
});
|
||||
|
||||
if !is_preceding_stub_function_without_empty_line {
|
||||
match self.kind {
|
||||
SuiteKind::TopLevel => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,39 @@ fn format() {
|
||||
CodeFrame::new("python", &formatted_code)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if options.preview().is_enabled() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We want to capture the differences in the preview style in our fixtures
|
||||
let options_preview = options.with_preview(PreviewMode::Enabled);
|
||||
let printed_preview = format_module_source(&content, options_preview.clone())
|
||||
.expect("Formatting to succeed");
|
||||
let formatted_preview = printed_preview.as_code();
|
||||
|
||||
ensure_unchanged_ast(&content, formatted_preview, &options_preview, input_path);
|
||||
ensure_stability_when_formatting_twice(
|
||||
formatted_preview,
|
||||
&options_preview,
|
||||
input_path,
|
||||
);
|
||||
|
||||
if formatted_code != formatted_preview {
|
||||
// Having both snapshots makes it hard to see the difference, so we're keeping only
|
||||
// diff.
|
||||
writeln!(
|
||||
snapshot,
|
||||
"#### Preview changes\n{}",
|
||||
CodeFrame::new(
|
||||
"diff",
|
||||
TextDiff::from_lines(formatted_code, formatted_preview)
|
||||
.unified_diff()
|
||||
.header("Stable", "Preview")
|
||||
)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We want to capture the differences in the preview style in our fixtures
|
||||
|
||||
@@ -95,36 +95,7 @@ def f(
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -7,23 +7,13 @@
|
||||
)
|
||||
|
||||
# "AnnAssign"s now also work
|
||||
-z: (
|
||||
- Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
-)
|
||||
-z: Short | Short2 | Short3 | Short4
|
||||
-z: int
|
||||
-z: int
|
||||
+z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong
|
||||
+z: (Short | Short2 | Short3 | Short4)
|
||||
+z: (int)
|
||||
+z: (int)
|
||||
|
||||
|
||||
-z: (
|
||||
- Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
- | Loooooooooooooooooooooooong
|
||||
-) = 7
|
||||
+z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
|
||||
z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
@@ -63,7 +53,7 @@
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
|
||||
# remove unnecessary paren
|
||||
@@ -133,7 +104,7 @@ def f(
|
||||
|
||||
|
||||
# this is a syntax error in the type annotation according to mypy, but it's not invalid *python* code, so make sure we don't mess with it and make it so.
|
||||
@@ -72,12 +62,10 @@
|
||||
@@ -72,12 +72,10 @@
|
||||
|
||||
def foo(
|
||||
i: int,
|
||||
@@ -150,7 +121,7 @@ def f(
|
||||
*,
|
||||
s: str,
|
||||
) -> None:
|
||||
@@ -88,7 +76,7 @@
|
||||
@@ -88,7 +86,7 @@
|
||||
async def foo(
|
||||
q: str | None = Query(
|
||||
None, title="Some long title", description="Some long description"
|
||||
@@ -173,13 +144,23 @@ z = (
|
||||
)
|
||||
|
||||
# "AnnAssign"s now also work
|
||||
z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong
|
||||
z: (Short | Short2 | Short3 | Short4)
|
||||
z: (int)
|
||||
z: (int)
|
||||
z: (
|
||||
Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
)
|
||||
z: Short | Short2 | Short3 | Short4
|
||||
z: int
|
||||
z: int
|
||||
|
||||
|
||||
z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
|
||||
z: (
|
||||
Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
| Loooooooooooooooooooooooong
|
||||
) = 7
|
||||
z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_context_managers_39.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2(), \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() \
|
||||
:
|
||||
pass
|
||||
|
||||
|
||||
with \
|
||||
new_new_new1() as cm1, \
|
||||
new_new_new2() \
|
||||
:
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
new_new_new1() as cm1,
|
||||
new_new_new2()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment.
|
||||
with (
|
||||
# First comment.
|
||||
new_new_new1() as cm1,
|
||||
# Second comment.
|
||||
new_new_new2()
|
||||
# Last comment.
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with \
|
||||
this_is_a_very_long_call(looong_arg1=looong_value1, looong_arg2=looong_value2) as cm1, \
|
||||
this_is_a_very_long_call(looong_arg1=looong_value1, looong_arg2=looong_value2, looong_arg3=looong_value3, looong_arg4=looong_value4) as cm2 \
|
||||
:
|
||||
pass
|
||||
|
||||
|
||||
with mock.patch.object(
|
||||
self.my_runner, "first_method", autospec=True
|
||||
) as mock_run_adb, mock.patch.object(
|
||||
self.my_runner, "second_method", autospec=True, return_value="foo"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method() as cmd:
|
||||
pass
|
||||
|
||||
|
||||
async def func():
|
||||
async with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
pass
|
||||
|
||||
async with some_function(
|
||||
argument1, argument2, argument3="some_value"
|
||||
) as some_cm, some_other_function(
|
||||
argument1, argument2, argument3="some_value"
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,19 +1,9 @@
|
||||
-with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2() as cm2,
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4() as cm4,
|
||||
-):
|
||||
+with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment
|
||||
-with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2(),
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4(),
|
||||
-):
|
||||
+with make_context_manager1() as cm1, make_context_manager2(), make_context_manager3() as cm3, make_context_manager4():
|
||||
pass
|
||||
|
||||
|
||||
@@ -36,25 +26,21 @@
|
||||
pass
|
||||
|
||||
|
||||
-with (
|
||||
- this_is_a_very_long_call(
|
||||
- looong_arg1=looong_value1, looong_arg2=looong_value2
|
||||
- ) as cm1,
|
||||
- this_is_a_very_long_call(
|
||||
- looong_arg1=looong_value1,
|
||||
- looong_arg2=looong_value2,
|
||||
- looong_arg3=looong_value3,
|
||||
- looong_arg4=looong_value4,
|
||||
- ) as cm2,
|
||||
-):
|
||||
+with this_is_a_very_long_call(
|
||||
+ looong_arg1=looong_value1, looong_arg2=looong_value2
|
||||
+) as cm1, this_is_a_very_long_call(
|
||||
+ looong_arg1=looong_value1,
|
||||
+ looong_arg2=looong_value2,
|
||||
+ looong_arg3=looong_value3,
|
||||
+ looong_arg4=looong_value4,
|
||||
+) as cm2:
|
||||
pass
|
||||
|
||||
|
||||
-with (
|
||||
- mock.patch.object(self.my_runner, "first_method", autospec=True) as mock_run_adb,
|
||||
- mock.patch.object(
|
||||
- self.my_runner, "second_method", autospec=True, return_value="foo"
|
||||
- ),
|
||||
+with mock.patch.object(
|
||||
+ self.my_runner, "first_method", autospec=True
|
||||
+) as mock_run_adb, mock.patch.object(
|
||||
+ self.my_runner, "second_method", autospec=True, return_value="foo"
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -70,16 +56,10 @@
|
||||
|
||||
|
||||
async def func():
|
||||
- async with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2() as cm2,
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4() as cm4,
|
||||
- ):
|
||||
+ async with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
- async with (
|
||||
- some_function(argument1, argument2, argument3="some_value") as some_cm,
|
||||
- some_other_function(argument1, argument2, argument3="some_value"),
|
||||
- ):
|
||||
+ async with some_function(
|
||||
+ argument1, argument2, argument3="some_value"
|
||||
+ ) as some_cm, some_other_function(argument1, argument2, argument3="some_value"):
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment
|
||||
with make_context_manager1() as cm1, make_context_manager2(), make_context_manager3() as cm3, make_context_manager4():
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment.
|
||||
with (
|
||||
# First comment.
|
||||
new_new_new1() as cm1,
|
||||
# Second comment.
|
||||
new_new_new2(),
|
||||
# Last comment.
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with this_is_a_very_long_call(
|
||||
looong_arg1=looong_value1, looong_arg2=looong_value2
|
||||
) as cm1, this_is_a_very_long_call(
|
||||
looong_arg1=looong_value1,
|
||||
looong_arg2=looong_value2,
|
||||
looong_arg3=looong_value3,
|
||||
looong_arg4=looong_value4,
|
||||
) as cm2:
|
||||
pass
|
||||
|
||||
|
||||
with mock.patch.object(
|
||||
self.my_runner, "first_method", autospec=True
|
||||
) as mock_run_adb, mock.patch.object(
|
||||
self.my_runner, "second_method", autospec=True, return_value="foo"
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method() as cmd:
|
||||
pass
|
||||
|
||||
|
||||
async def func():
|
||||
async with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
async with some_function(
|
||||
argument1, argument2, argument3="some_value"
|
||||
) as some_cm, some_other_function(argument1, argument2, argument3="some_value"):
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2() as cm2,
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4() as cm4,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment
|
||||
with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2(),
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4(),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
|
||||
|
||||
# Leading comment.
|
||||
with (
|
||||
# First comment.
|
||||
new_new_new1() as cm1,
|
||||
# Second comment.
|
||||
new_new_new2(),
|
||||
# Last comment.
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
this_is_a_very_long_call(
|
||||
looong_arg1=looong_value1, looong_arg2=looong_value2
|
||||
) as cm1,
|
||||
this_is_a_very_long_call(
|
||||
looong_arg1=looong_value1,
|
||||
looong_arg2=looong_value2,
|
||||
looong_arg3=looong_value3,
|
||||
looong_arg4=looong_value4,
|
||||
) as cm2,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
mock.patch.object(self.my_runner, "first_method", autospec=True) as mock_run_adb,
|
||||
mock.patch.object(
|
||||
self.my_runner, "second_method", autospec=True, return_value="foo"
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method() as cmd:
|
||||
pass
|
||||
|
||||
|
||||
async def func():
|
||||
async with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2() as cm2,
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4() as cm4,
|
||||
):
|
||||
pass
|
||||
|
||||
async with (
|
||||
some_function(argument1, argument2, argument3="some_value") as some_cm,
|
||||
some_other_function(argument1, argument2, argument3="some_value"),
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_context_managers_autodetect_310.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
# This file uses pattern matching introduced in Python 3.10.
|
||||
|
||||
|
||||
match http_code:
|
||||
case 404:
|
||||
print("Not found")
|
||||
|
||||
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -6,10 +6,5 @@
|
||||
print("Not found")
|
||||
|
||||
|
||||
-with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2() as cm2,
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4() as cm4,
|
||||
-):
|
||||
+with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
# This file uses pattern matching introduced in Python 3.10.
|
||||
|
||||
|
||||
match http_code:
|
||||
case 404:
|
||||
print("Not found")
|
||||
|
||||
|
||||
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
# This file uses pattern matching introduced in Python 3.10.
|
||||
|
||||
|
||||
match http_code:
|
||||
case 404:
|
||||
print("Not found")
|
||||
|
||||
|
||||
with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2() as cm2,
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4() as cm4,
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_context_managers_autodetect_311.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
# This file uses except* clause in Python 3.11.
|
||||
|
||||
|
||||
try:
|
||||
some_call()
|
||||
except* Error as e:
|
||||
pass
|
||||
|
||||
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -7,10 +7,5 @@
|
||||
pass
|
||||
|
||||
|
||||
-with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2() as cm2,
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4() as cm4,
|
||||
-):
|
||||
+with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
# This file uses except* clause in Python 3.11.
|
||||
|
||||
|
||||
try:
|
||||
some_call()
|
||||
except* Error as e:
|
||||
pass
|
||||
|
||||
|
||||
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
# This file uses except* clause in Python 3.11.
|
||||
|
||||
|
||||
try:
|
||||
some_call()
|
||||
except* Error as e:
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2() as cm2,
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4() as cm4,
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_context_managers_autodetect_39.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
# This file uses parenthesized context managers introduced in Python 3.9.
|
||||
|
||||
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
new_new_new1() as cm1,
|
||||
new_new_new2()
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,12 +1,7 @@
|
||||
# This file uses parenthesized context managers introduced in Python 3.9.
|
||||
|
||||
|
||||
-with (
|
||||
- make_context_manager1() as cm1,
|
||||
- make_context_manager2() as cm2,
|
||||
- make_context_manager3() as cm3,
|
||||
- make_context_manager4() as cm4,
|
||||
-):
|
||||
+with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
# This file uses parenthesized context managers introduced in Python 3.9.
|
||||
|
||||
|
||||
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
# This file uses parenthesized context managers introduced in Python 3.9.
|
||||
|
||||
|
||||
with (
|
||||
make_context_manager1() as cm1,
|
||||
make_context_manager2() as cm2,
|
||||
make_context_manager3() as cm3,
|
||||
make_context_manager4() as cm4,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with new_new_new1() as cm1, new_new_new2():
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/preview_dummy_implementations.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
class Empty:
|
||||
...
|
||||
|
||||
def dummy(a): ...
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int:
|
||||
...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
def baz(self, c: bytes) -> str:
|
||||
...
|
||||
|
||||
|
||||
def dummy_two():
|
||||
...
|
||||
@dummy
|
||||
def dummy_three():
|
||||
...
|
||||
|
||||
def dummy_four():
|
||||
...
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
def has_comment():
|
||||
... # still a dummy
|
||||
|
||||
if some_condition:
|
||||
...
|
||||
|
||||
if already_dummy: ...
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -5,15 +5,23 @@
|
||||
|
||||
|
||||
def dummy(a): ...
|
||||
+
|
||||
+
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
+
|
||||
+
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
+
|
||||
+
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
+
|
||||
+
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
@@ -24,10 +32,13 @@
|
||||
def foo(self, a: int) -> int: ...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
+
|
||||
def baz(self, c: bytes) -> str: ...
|
||||
|
||||
|
||||
def dummy_two(): ...
|
||||
+
|
||||
+
|
||||
@dummy
|
||||
def dummy_three(): ...
|
||||
|
||||
@@ -41,6 +52,8 @@
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
+
|
||||
+
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
@@ -54,8 +67,6 @@
|
||||
def has_comment(): ... # still a dummy
|
||||
|
||||
|
||||
-if some_condition:
|
||||
- ...
|
||||
+if some_condition: ...
|
||||
|
||||
-if already_dummy:
|
||||
- ...
|
||||
+if already_dummy: ...
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
|
||||
class Empty: ...
|
||||
|
||||
|
||||
def dummy(a): ...
|
||||
|
||||
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
|
||||
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int: ...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
|
||||
def baz(self, c: bytes) -> str: ...
|
||||
|
||||
|
||||
def dummy_two(): ...
|
||||
|
||||
|
||||
@dummy
|
||||
def dummy_three(): ...
|
||||
|
||||
|
||||
def dummy_four(): ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
def has_comment(): ... # still a dummy
|
||||
|
||||
|
||||
if some_condition: ...
|
||||
|
||||
if already_dummy: ...
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
|
||||
class Empty: ...
|
||||
|
||||
|
||||
def dummy(a): ...
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int: ...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
def baz(self, c: bytes) -> str: ...
|
||||
|
||||
|
||||
def dummy_two(): ...
|
||||
@dummy
|
||||
def dummy_three(): ...
|
||||
|
||||
|
||||
def dummy_four(): ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
def has_comment(): ... # still a dummy
|
||||
|
||||
|
||||
if some_condition:
|
||||
...
|
||||
|
||||
if already_dummy:
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
@@ -535,6 +535,28 @@ def doctest_extra_indent3():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -270,9 +270,11 @@
|
||||
|
||||
Examples
|
||||
--------
|
||||
- >>> df = pl.DataFrame(
|
||||
- ... {"foo": [1, 2, 3], "bar": [6, 7, 8], "ham": ["a", "b", "c"]}
|
||||
- ... )
|
||||
+ >>> df = pl.DataFrame({
|
||||
+ ... "foo": [1, 2, 3],
|
||||
+ ... "bar": [6, 7, 8],
|
||||
+ ... "ham": ["a", "b", "c"],
|
||||
+ ... })
|
||||
"""
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = space
|
||||
@@ -1139,6 +1161,28 @@ def doctest_extra_indent3():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -270,9 +270,11 @@
|
||||
|
||||
Examples
|
||||
--------
|
||||
- >>> df = pl.DataFrame(
|
||||
- ... {"foo": [1, 2, 3], "bar": [6, 7, 8], "ham": ["a", "b", "c"]}
|
||||
- ... )
|
||||
+ >>> df = pl.DataFrame({
|
||||
+ ... "foo": [1, 2, 3],
|
||||
+ ... "bar": [6, 7, 8],
|
||||
+ ... "ham": ["a", "b", "c"],
|
||||
+ ... })
|
||||
"""
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Output 4
|
||||
```
|
||||
indent-style = tab
|
||||
@@ -1876,4 +1920,26 @@ def doctest_extra_indent3():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -700,9 +700,11 @@
|
||||
|
||||
Examples
|
||||
--------
|
||||
- >>> df = pl.DataFrame(
|
||||
- ... {"foo": [1, 2, 3], "bar": [6, 7, 8], "ham": ["a", "b", "c"]}
|
||||
- ... )
|
||||
+ >>> df = pl.DataFrame({
|
||||
+ ... "foo": [1, 2, 3],
|
||||
+ ... "bar": [6, 7, 8],
|
||||
+ ... "ham": ["a", "b", "c"],
|
||||
+ ... })
|
||||
"""
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -319,6 +319,19 @@ trailing_preferred_quote_texts = [''' "''', ''' ""''', ''' """''', ''' """"''']
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,4 +1,5 @@
|
||||
"' test"
|
||||
+
|
||||
'" test'
|
||||
|
||||
'" test'
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = space
|
||||
@@ -496,4 +509,17 @@ trailing_preferred_quote_texts = [''' "''', ''' ""''', ''' """''', ''' """"''']
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,4 +1,5 @@
|
||||
"' test"
|
||||
+
|
||||
'" test'
|
||||
|
||||
'" test'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -259,7 +259,27 @@ if True:
|
||||
# empty line(s) at the end of the file due to nested function
|
||||
if True:
|
||||
def nested_trailing_function():
|
||||
pass```
|
||||
pass
|
||||
|
||||
|
||||
def overload1(): ... # trailing comment
|
||||
def overload1(a: int): ...
|
||||
|
||||
def overload2(): ... # trailing comment
|
||||
|
||||
def overload2(a: int): ...
|
||||
|
||||
def overload3():
|
||||
...
|
||||
# trailing comment
|
||||
def overload3(a: int): ...
|
||||
|
||||
def overload4():
|
||||
...
|
||||
# trailing comment
|
||||
|
||||
def overload4(a: int): ...
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
@@ -546,6 +566,40 @@ if True:
|
||||
|
||||
def nested_trailing_function():
|
||||
pass
|
||||
|
||||
|
||||
def overload1():
|
||||
... # trailing comment
|
||||
|
||||
|
||||
def overload1(a: int):
|
||||
...
|
||||
|
||||
|
||||
def overload2():
|
||||
... # trailing comment
|
||||
|
||||
|
||||
def overload2(a: int):
|
||||
...
|
||||
|
||||
|
||||
def overload3():
|
||||
...
|
||||
# trailing comment
|
||||
|
||||
|
||||
def overload3(a: int):
|
||||
...
|
||||
|
||||
|
||||
def overload4():
|
||||
...
|
||||
# trailing comment
|
||||
|
||||
|
||||
def overload4(a: int):
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
@@ -569,6 +623,48 @@ if True:
|
||||
|
||||
|
||||
def fakehttp():
|
||||
@@ -283,20 +281,14 @@
|
||||
pass
|
||||
|
||||
|
||||
-def overload1():
|
||||
- ... # trailing comment
|
||||
+def overload1(): ... # trailing comment
|
||||
+def overload1(a: int): ...
|
||||
|
||||
|
||||
-def overload1(a: int):
|
||||
- ...
|
||||
+def overload2(): ... # trailing comment
|
||||
|
||||
|
||||
-def overload2():
|
||||
- ... # trailing comment
|
||||
-
|
||||
-
|
||||
-def overload2(a: int):
|
||||
- ...
|
||||
+def overload2(a: int): ...
|
||||
|
||||
|
||||
def overload3():
|
||||
@@ -304,8 +296,7 @@
|
||||
# trailing comment
|
||||
|
||||
|
||||
-def overload3(a: int):
|
||||
- ...
|
||||
+def overload3(a: int): ...
|
||||
|
||||
|
||||
def overload4():
|
||||
@@ -313,5 +304,4 @@
|
||||
# trailing comment
|
||||
|
||||
|
||||
-def overload4(a: int):
|
||||
- ...
|
||||
+def overload4(a: int): ...
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -156,6 +156,78 @@ def f():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
Black's `Preview.module_docstring_newlines`
|
||||
"""
|
||||
+
|
||||
first_stmt_after_module_level_docstring = 1
|
||||
|
||||
|
||||
class CachedRepository:
|
||||
# Black's `Preview.dummy_implementations`
|
||||
- def get_release_info(self):
|
||||
- ...
|
||||
+ def get_release_info(self): ...
|
||||
|
||||
|
||||
def raw_docstring():
|
||||
@@ -27,23 +27,22 @@
|
||||
|
||||
|
||||
class RemoveNewlineBeforeClassDocstring:
|
||||
-
|
||||
"""Black's `Preview.no_blank_line_before_class_docstring`"""
|
||||
|
||||
|
||||
def f():
|
||||
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
- ] = cccccccc.ccccccccccccc.cccccccc
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
|
||||
+ cccccccc.ccccccccccccc.cccccccc
|
||||
+ )
|
||||
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
- ] = cccccccc.ccccccccccccc().cccccccc
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
|
||||
+ cccccccc.ccccccccccccc().cccccccc
|
||||
+ )
|
||||
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
- ] = cccccccc.ccccccccccccc(d).cccccccc
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
|
||||
+ cccccccc.ccccccccccccc(d).cccccccc
|
||||
+ )
|
||||
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
|
||||
cccccccc.ccccccccccccc(d).cccccccc + e
|
||||
@@ -57,9 +56,9 @@
|
||||
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
|
||||
)
|
||||
|
||||
- self._cache: dict[
|
||||
- DependencyCacheKey, list[list[DependencyPackage]]
|
||||
- ] = collections.defaultdict(list)
|
||||
- self._cached_dependencies_by_level: dict[
|
||||
- int, list[DependencyCacheKey]
|
||||
- ] = collections.defaultdict(list)
|
||||
+ self._cache: dict[DependencyCacheKey, list[list[DependencyPackage]]] = (
|
||||
+ collections.defaultdict(list)
|
||||
+ )
|
||||
+ self._cached_dependencies_by_level: dict[int, list[DependencyCacheKey]] = (
|
||||
+ collections.defaultdict(list)
|
||||
+ )
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = space
|
||||
|
||||
@@ -129,6 +129,19 @@ def docstring_single():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,4 +1,5 @@
|
||||
'single'
|
||||
+
|
||||
'double'
|
||||
r'r single'
|
||||
r'r double'
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = space
|
||||
@@ -201,6 +214,19 @@ def docstring_single():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,4 +1,5 @@
|
||||
"single"
|
||||
+
|
||||
"double"
|
||||
r"r single"
|
||||
r"r double"
|
||||
```
|
||||
|
||||
|
||||
### Output 3
|
||||
```
|
||||
indent-style = space
|
||||
@@ -273,4 +299,17 @@ def docstring_single():
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,4 +1,5 @@
|
||||
'single'
|
||||
+
|
||||
"double"
|
||||
r'r single'
|
||||
r"r double"
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -84,6 +84,16 @@ class DefaultRunner:
|
||||
|
||||
JSONSerializable: TypeAlias = (
|
||||
"str | int | float | bool | None | list | tuple | JSONMapping"
|
||||
@@ -29,6 +29,6 @@
|
||||
|
||||
# Regression test: Don't forget the parentheses in the annotation when breaking
|
||||
class DefaultRunner:
|
||||
- task_runner_cls: TaskRunnerProtocol | typing.Callable[
|
||||
- [], typing.Any
|
||||
- ] = DefaultTaskRunner
|
||||
+ task_runner_cls: TaskRunnerProtocol | typing.Callable[[], typing.Any] = (
|
||||
+ DefaultTaskRunner
|
||||
+ )
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/long_type_annotations.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
x1: A[b] | EventHandler | EventSpec | list[EventHandler | EventSpec] | Other | More | AndMore | None = None
|
||||
|
||||
x2: "VeryLongClassNameWithAwkwardGenericSubtype[int] |" "VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
|
||||
x6: VeryLongClassNameWithAwkwardGenericSubtype[
|
||||
integeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer,
|
||||
VeryLongClassNameWithAwkwardGenericSubtype,
|
||||
str
|
||||
] = True
|
||||
|
||||
|
||||
x7: CustomTrainingJob | CustomContainerTrainingJob | CustomPythonPackageTrainingJob
|
||||
x8: (
|
||||
None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
x9: None | (
|
||||
datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
|
||||
x10: (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbb,
|
||||
Subscript
|
||||
| None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset,
|
||||
],
|
||||
bbb[other],
|
||||
) = None
|
||||
|
||||
x11: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x12: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
|
||||
x13: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x14: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x15: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x16: None | Literal[
|
||||
"split",
|
||||
"a bit longer",
|
||||
"records",
|
||||
"index",
|
||||
"table",
|
||||
"columns",
|
||||
"values",
|
||||
] = None
|
||||
|
||||
x17: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
]
|
||||
|
||||
|
||||
class Test:
|
||||
safe_age: Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
applied_fixes: int # the number of fixes that this user applied. Used for ranking the users with the most applied fixes.
|
||||
string_annotation: "Test" # a long comment after a quoted, runtime-only type annotation
|
||||
|
||||
|
||||
##########
|
||||
# Comments
|
||||
|
||||
leading: (
|
||||
# Leading comment
|
||||
None | dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_with_value: (
|
||||
# Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
leading_open_parentheses: ( # Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_open_parentheses_with_value: ( # Leading comment
|
||||
None
|
||||
| dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
trailing: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
)
|
||||
|
||||
trailing_with_value: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
) = None
|
||||
|
||||
trailing_own_line: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
)
|
||||
|
||||
trailing_own_line_with_value: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
) = None
|
||||
|
||||
nested_comment: None | [
|
||||
# a list of strings
|
||||
str
|
||||
] = None
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
x1: A[b] | EventHandler | EventSpec | list[
|
||||
EventHandler | EventSpec
|
||||
] | Other | More | AndMore | None = None
|
||||
|
||||
x2: "VeryLongClassNameWithAwkwardGenericSubtype[int] |" "VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
|
||||
x6: VeryLongClassNameWithAwkwardGenericSubtype[
|
||||
integeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer,
|
||||
VeryLongClassNameWithAwkwardGenericSubtype,
|
||||
str,
|
||||
] = True
|
||||
|
||||
|
||||
x7: CustomTrainingJob | CustomContainerTrainingJob | CustomPythonPackageTrainingJob
|
||||
x8: (
|
||||
None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
x9: None | (
|
||||
datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset
|
||||
) = None
|
||||
|
||||
|
||||
x10: (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbb,
|
||||
Subscript
|
||||
| None
|
||||
| datasets.ImageDataset
|
||||
| datasets.TabularDataset
|
||||
| datasets.TextDataset
|
||||
| datasets.VideoDataset,
|
||||
],
|
||||
bbb[other],
|
||||
) = None
|
||||
|
||||
x11: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x12: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
|
||||
x13: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x14: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
x15: [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
] | Other = None
|
||||
|
||||
x16: None | Literal[
|
||||
"split",
|
||||
"a bit longer",
|
||||
"records",
|
||||
"index",
|
||||
"table",
|
||||
"columns",
|
||||
"values",
|
||||
] = None
|
||||
|
||||
x17: None | [
|
||||
datasets.ImageDataset,
|
||||
datasets.TabularDataset,
|
||||
datasets.TextDataset,
|
||||
datasets.VideoDataset,
|
||||
]
|
||||
|
||||
|
||||
class Test:
|
||||
safe_age: Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
applied_fixes: int # the number of fixes that this user applied. Used for ranking the users with the most applied fixes.
|
||||
string_annotation: "Test" # a long comment after a quoted, runtime-only type annotation
|
||||
|
||||
|
||||
##########
|
||||
# Comments
|
||||
|
||||
leading: (
|
||||
# Leading comment
|
||||
None | dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_with_value: (
|
||||
# Leading comment
|
||||
None | dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
leading_open_parentheses: ( # Leading comment
|
||||
None | dataset.ImageDataset
|
||||
)
|
||||
|
||||
leading_open_parentheses_with_value: ( # Leading comment
|
||||
None | dataset.ImageDataset
|
||||
) = None
|
||||
|
||||
trailing: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
)
|
||||
|
||||
trailing_with_value: (
|
||||
None | dataset.ImageDataset # trailing comment
|
||||
) = None
|
||||
|
||||
trailing_own_line: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
)
|
||||
|
||||
trailing_own_line_with_value: (
|
||||
None | dataset.ImageDataset
|
||||
# trailing own line
|
||||
) = None
|
||||
|
||||
nested_comment: None | [
|
||||
# a list of strings
|
||||
str
|
||||
] = None
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -1,8 +1,18 @@
|
||||
-x1: A[b] | EventHandler | EventSpec | list[
|
||||
- EventHandler | EventSpec
|
||||
-] | Other | More | AndMore | None = None
|
||||
+x1: (
|
||||
+ A[b]
|
||||
+ | EventHandler
|
||||
+ | EventSpec
|
||||
+ | list[EventHandler | EventSpec]
|
||||
+ | Other
|
||||
+ | More
|
||||
+ | AndMore
|
||||
+ | None
|
||||
+) = None
|
||||
|
||||
-x2: "VeryLongClassNameWithAwkwardGenericSubtype[int] |" "VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
+x2: (
|
||||
+ "VeryLongClassNameWithAwkwardGenericSubtype[int] |"
|
||||
+ "VeryLongClassNameWithAwkwardGenericSubtype[str]"
|
||||
+)
|
||||
|
||||
x6: VeryLongClassNameWithAwkwardGenericSubtype[
|
||||
integeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer,
|
||||
@@ -48,12 +58,16 @@
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
-x12: None | [
|
||||
- datasets.ImageDataset,
|
||||
- datasets.TabularDataset,
|
||||
- datasets.TextDataset,
|
||||
- datasets.VideoDataset,
|
||||
-] | Other = None
|
||||
+x12: (
|
||||
+ None
|
||||
+ | [
|
||||
+ datasets.ImageDataset,
|
||||
+ datasets.TabularDataset,
|
||||
+ datasets.TextDataset,
|
||||
+ datasets.VideoDataset,
|
||||
+ ]
|
||||
+ | Other
|
||||
+) = None
|
||||
|
||||
|
||||
x13: [
|
||||
@@ -75,27 +89,34 @@
|
||||
datasets.VideoDataset,
|
||||
] = None
|
||||
|
||||
-x15: [
|
||||
- datasets.ImageDataset,
|
||||
- datasets.TabularDataset,
|
||||
- datasets.TextDataset,
|
||||
- datasets.VideoDataset,
|
||||
-] | [
|
||||
- datasets.ImageDataset,
|
||||
- datasets.TabularDataset,
|
||||
- datasets.TextDataset,
|
||||
- datasets.VideoDataset,
|
||||
-] | Other = None
|
||||
+x15: (
|
||||
+ [
|
||||
+ datasets.ImageDataset,
|
||||
+ datasets.TabularDataset,
|
||||
+ datasets.TextDataset,
|
||||
+ datasets.VideoDataset,
|
||||
+ ]
|
||||
+ | [
|
||||
+ datasets.ImageDataset,
|
||||
+ datasets.TabularDataset,
|
||||
+ datasets.TextDataset,
|
||||
+ datasets.VideoDataset,
|
||||
+ ]
|
||||
+ | Other
|
||||
+) = None
|
||||
|
||||
-x16: None | Literal[
|
||||
- "split",
|
||||
- "a bit longer",
|
||||
- "records",
|
||||
- "index",
|
||||
- "table",
|
||||
- "columns",
|
||||
- "values",
|
||||
-] = None
|
||||
+x16: (
|
||||
+ None
|
||||
+ | Literal[
|
||||
+ "split",
|
||||
+ "a bit longer",
|
||||
+ "records",
|
||||
+ "index",
|
||||
+ "table",
|
||||
+ "columns",
|
||||
+ "values",
|
||||
+ ]
|
||||
+) = None
|
||||
|
||||
x17: None | [
|
||||
datasets.ImageDataset,
|
||||
@@ -106,9 +127,13 @@
|
||||
|
||||
|
||||
class Test:
|
||||
- safe_age: Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
+ safe_age: (
|
||||
+ Decimal # the user's age, used to determine if it's safe for them to use ruff
|
||||
+ )
|
||||
applied_fixes: int # the number of fixes that this user applied. Used for ranking the users with the most applied fixes.
|
||||
- string_annotation: "Test" # a long comment after a quoted, runtime-only type annotation
|
||||
+ string_annotation: (
|
||||
+ "Test" # a long comment after a quoted, runtime-only type annotation
|
||||
+ )
|
||||
|
||||
|
||||
##########
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -150,21 +150,23 @@ def quuz():
|
||||
|
||||
|
||||
class Baz(Qux):
|
||||
@@ -49,12 +44,10 @@
|
||||
@@ -49,14 +44,8 @@
|
||||
pass
|
||||
|
||||
|
||||
-def bar():
|
||||
- ...
|
||||
+def bar(): ...
|
||||
|
||||
|
||||
-
|
||||
-
|
||||
-def baz():
|
||||
- ...
|
||||
-
|
||||
-
|
||||
+def bar(): ...
|
||||
+def baz(): ...
|
||||
|
||||
|
||||
def quux():
|
||||
"""Some docstring."""
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -315,7 +315,21 @@ with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(aaaaa, bbbbbbbbbb, dddd
|
||||
pass
|
||||
```
|
||||
|
||||
## Output
|
||||
## Outputs
|
||||
### Output 1
|
||||
```
|
||||
indent-style = space
|
||||
line-width = 88
|
||||
indent-width = 4
|
||||
quote-style = Double
|
||||
line-ending = LineFeed
|
||||
magic-trailing-comma = Respect
|
||||
docstring-code = Disabled
|
||||
docstring-code-line-width = "dynamic"
|
||||
preview = Disabled
|
||||
target_version = Py38
|
||||
```
|
||||
|
||||
```python
|
||||
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
pass
|
||||
@@ -653,4 +667,414 @@ with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(
|
||||
```
|
||||
|
||||
|
||||
#### Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -295,8 +295,9 @@
|
||||
pass
|
||||
|
||||
with (
|
||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
- + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b,
|
||||
+ (
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
+ ) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
### Output 2
|
||||
```
|
||||
indent-style = space
|
||||
line-width = 88
|
||||
indent-width = 4
|
||||
quote-style = Double
|
||||
line-ending = LineFeed
|
||||
magic-trailing-comma = Respect
|
||||
docstring-code = Disabled
|
||||
docstring-code-line-width = "dynamic"
|
||||
preview = Enabled
|
||||
target_version = Py39
|
||||
```
|
||||
|
||||
```python
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
):
|
||||
pass
|
||||
# trailing
|
||||
|
||||
with a, a: # after colon
|
||||
pass
|
||||
# trailing
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
):
|
||||
pass
|
||||
# trailing
|
||||
|
||||
|
||||
with (
|
||||
a, # a # comma
|
||||
b, # c
|
||||
): # colon
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
a as ( # a # as
|
||||
# own line
|
||||
b
|
||||
), # b # comma
|
||||
c, # c
|
||||
): # colon
|
||||
pass # body
|
||||
# body trailing own
|
||||
|
||||
with (
|
||||
a as ( # a # as
|
||||
# own line
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) # b
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
a,
|
||||
): # magic trailing comma
|
||||
pass
|
||||
|
||||
|
||||
with a: # should remove brackets
|
||||
pass
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# currently unparsable by black: https://github.com/psf/black/issues/3678
|
||||
with (name_2 for name_0 in name_4):
|
||||
pass
|
||||
with (a, *b):
|
||||
pass
|
||||
|
||||
with (
|
||||
# leading comment
|
||||
a
|
||||
) as b:
|
||||
pass
|
||||
|
||||
with (
|
||||
# leading comment
|
||||
a as b
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as b
|
||||
# trailing comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as (
|
||||
# leading comment
|
||||
b
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as (
|
||||
b
|
||||
# trailing comment
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as b # trailing same line comment
|
||||
# trailing own line comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a # trailing same line comment
|
||||
# trailing own line comment
|
||||
) as b:
|
||||
pass
|
||||
|
||||
with (
|
||||
(
|
||||
a
|
||||
# trailing own line comment
|
||||
) as ( # trailing as same line comment
|
||||
b
|
||||
) # trailing b same line comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
# comment
|
||||
a
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a # comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a
|
||||
# comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
# comment
|
||||
a as b
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as b # comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as b
|
||||
# comment
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
[
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccccccccccccccccccccccccccccccccccc",
|
||||
dddddddddddddddddddddddddddddddd,
|
||||
] as example1,
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
+ cccccccccccccccccccccccccccc
|
||||
+ ddddddddddddddddd as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
[
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"bbbbbbbbbb",
|
||||
"cccccccccccccccccccccccccccccccccccccccccc",
|
||||
dddddddddddddddddddddddddddddddd,
|
||||
] as example1,
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
* bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
* cccccccccccccccccccccccccccc
|
||||
+ ddddddddddddddddd as example2,
|
||||
CtxManager222222222222222() as example2,
|
||||
):
|
||||
pass
|
||||
|
||||
# Comments on open parentheses
|
||||
with ( # comment
|
||||
CtxManager1() as example1,
|
||||
CtxManager2() as example2,
|
||||
CtxManager3() as example3,
|
||||
):
|
||||
pass
|
||||
|
||||
with ( # outer comment
|
||||
( # inner comment
|
||||
CtxManager1()
|
||||
) as example1,
|
||||
CtxManager2() as example2,
|
||||
CtxManager3() as example3,
|
||||
):
|
||||
pass
|
||||
|
||||
with ( # outer comment
|
||||
CtxManager()
|
||||
) as example:
|
||||
pass
|
||||
|
||||
with (
|
||||
( # outer comment
|
||||
CtxManager()
|
||||
) as example,
|
||||
( # inner comment
|
||||
CtxManager2()
|
||||
) as example2,
|
||||
):
|
||||
pass
|
||||
|
||||
with ( # outer comment
|
||||
CtxManager1(),
|
||||
CtxManager2(),
|
||||
) as example:
|
||||
pass
|
||||
|
||||
with ( # outer comment
|
||||
( # inner comment
|
||||
CtxManager1()
|
||||
),
|
||||
CtxManager2(),
|
||||
) as example:
|
||||
pass
|
||||
|
||||
# Breaking of with items.
|
||||
with (
|
||||
test as ( # bar # foo
|
||||
# test
|
||||
foo
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
test as (
|
||||
# test
|
||||
foo
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
test as ( # bar # foo # baz
|
||||
# test
|
||||
foo
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with a as b, c as d:
|
||||
pass
|
||||
|
||||
with (
|
||||
a as b,
|
||||
# foo
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
a as ( # foo
|
||||
b
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
with f(
|
||||
a,
|
||||
) as b:
|
||||
pass
|
||||
|
||||
with (x := 1) as d:
|
||||
pass
|
||||
|
||||
with x[
|
||||
1,
|
||||
2,
|
||||
] as d:
|
||||
pass
|
||||
|
||||
with (
|
||||
f(
|
||||
a,
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
f(
|
||||
a,
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b:
|
||||
pass
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
with foo() as bar, baz() as bop:
|
||||
pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
and B
|
||||
and [aaaaaaaa, bbbbbbbbbbbbb, cccccccccc, dddddddddddd, eeeeeeeeeeeee]
|
||||
):
|
||||
pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
with (
|
||||
Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc),
|
||||
Document(aaaaa, bbbbbbbbbb, ddddddddddddd),
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with_39.py
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parenthesizing the with because it can make all with items fit by just breaking
|
||||
# around parentheses. We don't implement this optimisation because it makes it difficult to see where
|
||||
# the different context managers start and end.
|
||||
with cmd, xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd, another, and_more as x:
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses (can_omit_optional_parentheses)
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method(): pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parentheses here because it can make the entire with
|
||||
# header fit without requiring parentheses to do so.
|
||||
# We don't implement this optimisation because it very difficult to see where
|
||||
# the different context managers start or end.
|
||||
with cmd, xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd, another, and_more as x:
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method(): pass
|
||||
|
||||
# Parenthesize the with items if it makes them fit. Breaking the binary expression isn't
|
||||
# necessary because the entire items fit just into the 88 character limit.
|
||||
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes this binary expression but also preserves the parentheses of the first with-item.
|
||||
# It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses
|
||||
# like in this case.
|
||||
with (
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
if True:
|
||||
with anyio.CancelScope(shield=True) if get_running_loop() else contextlib.nullcontext():
|
||||
pass
|
||||
|
||||
with (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c):
|
||||
pass
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Outputs
|
||||
### Output 1
|
||||
```
|
||||
indent-style = space
|
||||
line-width = 88
|
||||
indent-width = 4
|
||||
quote-style = Double
|
||||
line-ending = LineFeed
|
||||
magic-trailing-comma = Respect
|
||||
docstring-code = Disabled
|
||||
docstring-code-line-width = "dynamic"
|
||||
preview = Enabled
|
||||
target_version = Py39
|
||||
```
|
||||
|
||||
```python
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parenthesizing the with because it can make all with items fit by just breaking
|
||||
# around parentheses. We don't implement this optimisation because it makes it difficult to see where
|
||||
# the different context managers start and end.
|
||||
with (
|
||||
cmd,
|
||||
xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd,
|
||||
another,
|
||||
and_more as x,
|
||||
):
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses (can_omit_optional_parentheses)
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method():
|
||||
pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black avoids parentheses here because it can make the entire with
|
||||
# header fit without requiring parentheses to do so.
|
||||
# We don't implement this optimisation because it very difficult to see where
|
||||
# the different context managers start or end.
|
||||
with (
|
||||
cmd,
|
||||
xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
) as cmd,
|
||||
another,
|
||||
and_more as x,
|
||||
):
|
||||
pass
|
||||
|
||||
# Avoid parenthesizing single item context managers when splitting after the parentheses
|
||||
# is sufficient
|
||||
with xxxxxxxx.some_kind_of_method(
|
||||
some_argument=[
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
]
|
||||
).another_method():
|
||||
pass
|
||||
|
||||
# Parenthesize the with items if it makes them fit. Breaking the binary expression isn't
|
||||
# necessary because the entire items fit just into the 88 character limit.
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Black parenthesizes this binary expression but also preserves the parentheses of the first with-item.
|
||||
# It does so because it prefers splitting already parenthesized context managers, even if it leads to more parentheses
|
||||
# like in this case.
|
||||
with (
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) as b,
|
||||
c as d,
|
||||
):
|
||||
pass
|
||||
|
||||
if True:
|
||||
with (
|
||||
anyio.CancelScope(shield=True)
|
||||
if get_running_loop()
|
||||
else contextlib.nullcontext()
|
||||
):
|
||||
pass
|
||||
|
||||
with (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1293,7 +1293,7 @@ impl FusedIterator for Lexer<'_> {}
|
||||
/// [lexer] implementation.
|
||||
///
|
||||
/// [lexer]: crate::lexer
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LexicalError {
|
||||
/// The type of error that occurred.
|
||||
pub error: LexicalErrorType,
|
||||
@@ -1309,7 +1309,7 @@ impl LexicalError {
|
||||
}
|
||||
|
||||
/// Represents the different types of errors that can occur during lexing.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LexicalErrorType {
|
||||
// TODO: Can probably be removed, the places it is used seem to be able
|
||||
// to use the `UnicodeError` variant instead.
|
||||
|
||||
@@ -409,7 +409,7 @@ pub(crate) fn concatenated_strings(
|
||||
|
||||
// TODO: consolidate these with ParseError
|
||||
/// An error that occurred during parsing of an f-string.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct FStringError {
|
||||
/// The type of error that occurred.
|
||||
pub(crate) error: FStringErrorType,
|
||||
@@ -427,7 +427,7 @@ impl From<FStringError> for LexicalError {
|
||||
}
|
||||
|
||||
/// Represents the different types of errors that can occur during parsing of an f-string.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FStringErrorType {
|
||||
/// Expected a right brace after an opened left brace.
|
||||
UnclosedLbrace,
|
||||
|
||||
@@ -17,7 +17,7 @@ license = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.10.0"
|
||||
env_logger = "0.10.1"
|
||||
tempfile = "3.8.1"
|
||||
insta = { workspace = true }
|
||||
|
||||
|
||||
@@ -333,6 +333,69 @@ pub fn is_sys_version_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> boo
|
||||
})
|
||||
}
|
||||
|
||||
/// Traverse a "union" type annotation, applying `func` to each union member.
|
||||
///
|
||||
/// Supports traversal of `Union` and `|` union expressions.
|
||||
///
|
||||
/// The function is called with each expression in the union (excluding declarations of nested
|
||||
/// unions) and the parent expression.
|
||||
pub fn traverse_union<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr)
|
||||
where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
fn inner<'a, F>(
|
||||
func: &mut F,
|
||||
semantic: &SemanticModel,
|
||||
expr: &'a Expr,
|
||||
parent: Option<&'a Expr>,
|
||||
) where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
// Ex) x | y
|
||||
if let Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
left,
|
||||
right,
|
||||
range: _,
|
||||
}) = expr
|
||||
{
|
||||
// The union data structure usually looks like this:
|
||||
// a | b | c -> (a | b) | c
|
||||
//
|
||||
// However, parenthesized expressions can coerce it into any structure:
|
||||
// a | (b | c)
|
||||
//
|
||||
// So we have to traverse both branches in order (left, then right), to report members
|
||||
// in the order they appear in the source code.
|
||||
|
||||
// Traverse the left then right arms
|
||||
inner(func, semantic, left, Some(expr));
|
||||
inner(func, semantic, right, Some(expr));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) Union[x, y]
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
if semantic.match_typing_expr(value, "Union") {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
// Traverse each element of the tuple within the union recursively to handle cases
|
||||
// such as `Union[..., Union[...]]
|
||||
elts.iter()
|
||||
.for_each(|elt| inner(func, semantic, elt, Some(expr)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, call the function on expression, if it's not the top-level expression.
|
||||
if let Some(parent) = parent {
|
||||
func(expr, parent);
|
||||
}
|
||||
}
|
||||
|
||||
inner(func, semantic, expr, None);
|
||||
}
|
||||
|
||||
/// Abstraction for a type checker, conservatively checks for the intended type(s).
|
||||
trait TypeChecker {
|
||||
/// Check annotation expression to match the intended type(s).
|
||||
|
||||
@@ -1715,6 +1715,16 @@ bitflags! {
|
||||
/// ```
|
||||
const FUTURE_ANNOTATIONS = 1 << 15;
|
||||
|
||||
/// The model has traversed past the module docstring.
|
||||
///
|
||||
/// For example, the model could be visiting `x` in:
|
||||
/// ```python
|
||||
/// """Module docstring."""
|
||||
///
|
||||
/// x: int = 1
|
||||
/// ```
|
||||
const MODULE_DOCSTRING = 1 << 16;
|
||||
|
||||
/// The model is in a type parameter definition.
|
||||
///
|
||||
/// For example, the model could be visiting `Record` in:
|
||||
@@ -1723,11 +1733,10 @@ bitflags! {
|
||||
///
|
||||
/// Record = TypeVar("Record")
|
||||
///
|
||||
const TYPE_PARAM_DEFINITION = 1 << 16;
|
||||
const TYPE_PARAM_DEFINITION = 1 << 17;
|
||||
|
||||
/// The context is in any type annotation.
|
||||
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
||||
|
||||
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
||||
|
||||
/// The context is in any string type definition.
|
||||
const STRING_TYPE_DEFINITION = Self::SIMPLE_STRING_TYPE_DEFINITION.bits()
|
||||
|
||||
@@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*;
|
||||
use ruff_formatter::{FormatResult, Formatted, IndentStyle};
|
||||
use ruff_linter::directives;
|
||||
use ruff_linter::line_width::{IndentWidth, LineLength};
|
||||
use ruff_linter::linter::{check_path, LinterResult};
|
||||
use ruff_linter::linter::{check_path, LinterResult, TokenSource};
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_linter::settings::{flags, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX};
|
||||
@@ -182,7 +182,6 @@ impl Workspace {
|
||||
} = check_path(
|
||||
Path::new("<filename>"),
|
||||
None,
|
||||
tokens,
|
||||
&locator,
|
||||
&stylist,
|
||||
&indexer,
|
||||
@@ -191,6 +190,7 @@ impl Workspace {
|
||||
flags::Noqa::Enabled,
|
||||
&source_kind,
|
||||
source_type,
|
||||
TokenSource::Tokens(tokens),
|
||||
);
|
||||
|
||||
let source_code = locator.to_source_code();
|
||||
|
||||
@@ -32,7 +32,7 @@ glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
path-absolutize = { workspace = true }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
|
||||
@@ -714,7 +714,9 @@ impl LintConfiguration {
|
||||
.collect();
|
||||
|
||||
// The fixable set keeps track of which rules are fixable.
|
||||
let mut fixable_set: RuleSet = RuleSelector::All.rules(&preview).collect();
|
||||
let mut fixable_set: RuleSet = RuleSelector::All
|
||||
.rules(&preview.without_require_explicit())
|
||||
.collect();
|
||||
|
||||
// Ignores normally only subtract from the current set of selected
|
||||
// rules. By that logic the ignore in `select = [], ignore = ["E501"]`
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function SettingsEditor({
|
||||
monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
schemas: [
|
||||
{
|
||||
uri: "https://raw.githubusercontent.com/charliermarsh/ruff/main/ruff.schema.json",
|
||||
uri: "https://raw.githubusercontent.com/astral-sh/ruff/main/ruff.schema.json",
|
||||
fileMatch: ["*"],
|
||||
schema,
|
||||
},
|
||||
|
||||
2
ruff.schema.json
generated
2
ruff.schema.json
generated
@@ -3429,6 +3429,8 @@
|
||||
"RUF017",
|
||||
"RUF018",
|
||||
"RUF019",
|
||||
"RUF02",
|
||||
"RUF020",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
|
||||
Reference in New Issue
Block a user