Compare commits

..

1 Commits

Author SHA1 Message Date
konstin
d4cfe1f2a7 Break before slice colon
**Summary** Break slices at the colon first, since the colon is separator with the lowest precedence and we're in a parenthesized context.

**Input**
```python
section_header_data = byte_array[byte_begin_index + byte_step_index * event_index : byte_begin_index + byte_step_index * (event_index + 1)]
```
**Black**
```python
section_header_data = byte_array[
    byte_begin_index
    + byte_step_index * event_index : byte_begin_index
    + byte_step_index * (event_index + 1)
]
```
**Current formatting**
```python
section_header_data = byte_array[
    byte_begin_index + byte_step_index * event_index : byte_begin_index
    + byte_step_index * (event_index + 1)
]
```
**Proposed formatting**
```python
section_header_data = byte_array[
    byte_begin_index + byte_step_index * event_index
    : byte_begin_index + byte_step_index * (event_index + 1)
]
```

This is another intentional black deviation, but i find it a clear style improvement.

This is consistent with adding a step:
```python
section_header_data2 = byte_array[
    byte_begin_index + byte_step_index * event_index
    : byte_begin_index + byte_step_index
    : section_size
]
```

As-is, this regresses trailing colon comments:

**in**
```python
c1 = "c"[
    1:  # e
    # f
    2
]
```
**out**
```python
c1 = "c"[
    1
    :  # e
    # f
    2
]
```

Fixes #7316

**Test Plan** Added the fixtures above.
2023-09-13 12:11:53 +02:00
155 changed files with 1757 additions and 3304 deletions

View File

@@ -366,31 +366,3 @@ jobs:
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
tmp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main # We checkout the main branch to check for the commit
- name: Check main branch
run: |
# Fetch the main branch since a shallow checkout is used by default
git fetch origin main --unshallow
if ! git branch --contains 0c030b5bf31e425cb6070db7386243eca6dbd8f1 | grep -E '(^|\s)main$'; then
echo "The specified sha is not on the main branch" >&2
exit 1
fi
- name: Check tag consistency
run: |
# Switch to the commit we want to release
git checkout 0c030b5bf31e425cb6070db7386243eca6dbd8f1
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
if [ "0.0.290" != "${version}" ]; then
echo "The input tag does not match the version from pyproject.toml:" >&2
echo "0.0.290" >&2
echo "${version}" >&2
exit 1
else
echo "Releasing ${version}"
fi

View File

@@ -7,15 +7,12 @@ on:
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
type: string
sha:
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
default: ""
description: "Optionally, the full sha of the commit to be released"
type: string
pull_request:
paths:
# When we change pyproject.toml, we want to ensure that the maturin builds still work
- pyproject.toml
# And when we change this workflow itself...
- .github/workflows/release.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -34,8 +31,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -62,8 +57,6 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -102,8 +95,6 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -150,8 +141,6 @@ jobs:
arch: x64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -198,8 +187,6 @@ jobs:
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -257,8 +244,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -312,8 +297,6 @@ jobs:
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -368,8 +351,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -418,8 +399,6 @@ jobs:
if: ${{ inputs.tag }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: Check tag consistency
run: |
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
@@ -431,15 +410,6 @@ jobs:
else
echo "Releasing ${version}"
fi
- name: Check main branch
if: ${{ inputs.sha }}
run: |
# Fetch the main branch since a shallow checkout is used by default
git fetch origin main --unshallow
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
echo "The specified sha is not on the main branch" >&2
exit 1
fi
- name: Check SHA consistency
if: ${{ inputs.sha }}
run: |
@@ -495,8 +465,6 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: git tag
run: |
git config user.email "hey@astral.sh"

258
Cargo.lock generated
View File

@@ -128,11 +128,10 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "argfile"
version = "0.1.6"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6"
checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638"
dependencies = [
"fs-err",
"os_str_bytes",
]
@@ -221,7 +220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
dependencies = [
"memchr",
"regex-automata 0.3.8",
"regex-automata 0.3.7",
"serde",
]
@@ -272,14 +271,16 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.30"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"time 0.1.45",
"wasm-bindgen",
"windows-targets 0.48.5",
]
@@ -313,19 +314,20 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.3"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.4.2"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
dependencies = [
"anstream",
"anstyle",
@@ -376,14 +378,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.2"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -608,7 +610,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -619,7 +621,16 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
dependencies = [
"serde",
]
[[package]]
@@ -810,7 +821,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"anyhow",
"clap",
@@ -881,7 +892,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@@ -934,6 +945,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hexf-parse"
version = "0.2.1"
@@ -1022,6 +1039,17 @@ dependencies = [
"rust-stemmers",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.0.0"
@@ -1112,15 +1140,15 @@ dependencies = [
[[package]]
name = "is-macro"
version = "0.3.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e"
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
dependencies = [
"Inflector",
"pmutil 0.6.1",
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 1.0.109",
]
[[package]]
@@ -1275,9 +1303,9 @@ dependencies = [
[[package]]
name = "libmimalloc-sys"
version = "0.1.35"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664"
checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d"
dependencies = [
"cc",
"libc",
@@ -1328,9 +1356,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
version = "2.6.3"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
[[package]]
name = "memoffset"
@@ -1343,9 +1371,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.39"
version = "0.1.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c"
checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127"
dependencies = [
"libmimalloc-sys",
]
@@ -1373,7 +1401,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@@ -1418,21 +1446,20 @@ dependencies = [
[[package]]
name = "notify"
version = "6.1.1"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
dependencies = [
"bitflags 2.4.0",
"bitflags 1.3.2",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -1555,18 +1582,18 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "path-absolutize"
version = "3.1.1"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
checksum = "43eb3595c63a214e1b37b44f44b0a84900ef7ae0b4c5efce59e123d246d7a0de"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.1.1"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
checksum = "9d55e486337acb9973cdea3ec5638c1b3bcb22e573b2b7b41969e0c744d5a15e"
dependencies = [
"once_cell",
]
@@ -1645,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap",
"indexmap 2.0.0",
]
[[package]]
@@ -1712,17 +1739,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "pmutil"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
]
[[package]]
name = "portable-atomic"
version = "1.4.3"
@@ -1805,20 +1821,20 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.67"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyproject-toml"
version = "0.7.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
checksum = "ee79feaa9d31e1c417e34219e610b67db4e786ce9b49d77dda549640abb9dc5f"
dependencies = [
"indexmap",
"indexmap 1.9.3",
"pep440_rs",
"pep508_rs",
"serde",
@@ -1832,7 +1848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
dependencies = [
"chrono",
"indexmap",
"indexmap 2.0.0",
"nextest-workspace-hack",
"quick-xml",
"thiserror",
@@ -1940,13 +1956,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.9.5"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.8",
"regex-automata 0.3.7",
"regex-syntax 0.7.5",
]
@@ -1961,9 +1977,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.8"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
@@ -1997,7 +2013,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42"
dependencies = [
"pmutil 0.5.3",
"pmutil",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -2021,7 +2037,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2119,7 +2135,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2259,7 +2275,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2673,9 +2689,9 @@ dependencies = [
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
@@ -2690,7 +2706,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2706,9 +2722,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.106"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
@@ -2739,8 +2755,15 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_json",
"serde_with_macros",
"time 0.3.28",
]
[[package]]
@@ -2752,7 +2775,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2775,9 +2798,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "similar"
@@ -2830,24 +2853,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.25.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.25.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.33",
"syn 1.0.109",
]
[[package]]
@@ -2863,9 +2886,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.33"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
@@ -2970,22 +2993,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.48"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.48"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3018,6 +3041,45 @@ dependencies = [
"tikv-jemalloc-sys",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [
"deranged",
"itoa",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
dependencies = [
"time-core",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -3054,9 +3116,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.7.8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
"serde",
"serde_spanned",
@@ -3075,11 +3137,11 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.19.15"
version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap",
"indexmap 2.0.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3107,7 +3169,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3310,7 +3372,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3369,14 +3431,20 @@ dependencies = [
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -3404,7 +3472,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
"wasm-bindgen-shared",
]
@@ -3438,7 +3506,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -14,39 +14,39 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
clap = { version = "4.4.3", features = ["derive"] }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.31.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
log = { version = "0.4.17" }
memchr = "2.6.3"
memchr = "2.5.0"
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.1.1" }
proc-macro2 = { version = "1.0.67" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.9.5" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.106" }
serde_json = { version = "1.0.93" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1", features = ["inline"] }
smallvec = { version = "1.10.0" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.2" }
syn = { version = "2.0.33" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.48" }
toml = { version = "0.7.8" }
thiserror = { version = "1.0.43" }
toml = { version = "0.7.2" }
tracing = "0.1.37"
tracing-indicatif = "0.3.4"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

25
LICENSE
View File

@@ -1224,31 +1224,6 @@ are:
SOFTWARE.
"""
- flake8-logging, licensed as follows:
"""
MIT License
Copyright (c) 2023 Adam Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Pyright, licensed as follows:
"""
MIT License

View File

@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.290
rev: v0.0.289
hooks:
- id: ruff
```
@@ -274,7 +274,6 @@ quality tools, including:
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
- [flake8-logging](https://pypi.org/project/flake8-logging/)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.290"
version = "0.0.289"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -56,7 +56,7 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
pyproject-toml = { version = "0.7.0" }
pyproject-toml = { version = "0.6.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }

View File

@@ -7,13 +7,6 @@ reversed(sorted(x, reverse=True))
reversed(sorted(x, key=lambda e: e, reverse=True))
reversed(sorted(x, reverse=True, key=lambda e: e))
reversed(sorted(x, reverse=False))
reversed(sorted(x, reverse=x))
reversed(sorted(x, reverse=not x))
# Regression test for: https://github.com/astral-sh/ruff/issues/7289
reversed(sorted(i for i in range(42)))
reversed(sorted((i for i in range(42)), reverse=True))
def reversed(*args, **kwargs):
return None

View File

@@ -7,8 +7,6 @@ d = {"a": 1, "b": 2, "c": 3}
{i for i in x}
{k: v for k, v in y}
{k: v for k, v in d.items()}
[(k, v) for k, v in d.items()]
{k: (a, b) for k, (a, b) in d.items()}
[i for i, in z]
[i for i, j in y]

View File

@@ -1,5 +0,0 @@
import logging
logging.Logger(__name__)
logging.Logger()
logging.getLogger(__name__)

View File

@@ -1,9 +0,0 @@
import logging
logging.WARN # LOG009
logging.WARNING # OK
from logging import WARN, WARNING
WARN # LOG009
WARNING # OK

View File

@@ -1,83 +0,0 @@
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
result[idx] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # Ok (false negative: edge case where `else` is same as `if`)
else:
result[idx] = name
def foo():
result = {}
fruit = ["apple", "pear", "orange"]
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = []
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # OK (result is not a dictionary)
else:
result[idx] = name
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # OK (if/elif/else isn't replaceable)
elif idx % 3:
result[idx] = name
else:
result[idx] = name
def foo():
result = {1: "banana"}
fruit = ["apple", "pear", "orange"]
for idx, name in enumerate(fruit):
if idx % 2:
result[idx] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
if idx in result:
result[idx] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for name in fruit:
result[name] = name # PERF403
def foo():
fruit = ["apple", "pear", "orange"]
result = {}
for idx, name in enumerate(fruit):
result[name] = idx # PERF403

View File

@@ -658,8 +658,3 @@ class CommentAfterDocstring:
"After this docstring there's a comment." # priorities=1
def sort_services(self):
pass
def newline_after_closing_quote(self):
"We enforce a newline after the closing quote for a multi-line docstring \
but continuations shouldn't be considered multi-line"

View File

@@ -529,16 +529,3 @@ def replace_equals_with_dash2():
Parameters
===========
"""
@expect(_D213)
def non_empty_blank_line_before_section(): # noqa: D416
"""Toggle the gizmo.
The function's description.
Returns
-------
A value of some sort.
"""

View File

@@ -1,60 +0,0 @@
class Everything:
foo = 1
def __init__(self):
pass
def _private(self):
pass
def method1(self):
pass
def method2(self):
pass
def method3(self):
pass
def method4(self):
pass
def method5(self):
pass
def method6(self):
pass
def method7(self):
pass
def method8(self):
pass
def method9(self):
pass
class Small:
def __init__(self):
pass
def _private(self):
pass
def method1(self):
pass
def method2(self):
pass
def method3(self):
pass
def method4(self):
pass
def method5(self):
pass
def method6(self):
pass

View File

@@ -74,8 +74,6 @@ from typing import Collection
from typing import AsyncGenerator
from typing import Reversible
from typing import Generator
from typing import Callable
from typing import cast
# OK
from a import b

View File

@@ -13,19 +13,18 @@ x: typing.TypeAlias = list[T]
T = typing.TypeVar("T")
x: typing.TypeAlias = list[T]
# UP040 bounded generic
# UP040 bounded generic (todo)
T = typing.TypeVar("T", bound=int)
x: typing.TypeAlias = list[T]
# UP040 constrained generic
T = typing.TypeVar("T", int, str)
x: typing.TypeAlias = list[T]
# UP040 contravariant generic
# UP040 contravariant generic (todo)
T = typing.TypeVar("T", contravariant=True)
x: typing.TypeAlias = list[T]
# UP040 covariant generic
# UP040 covariant generic (todo)
T = typing.TypeVar("T", covariant=True)
x: typing.TypeAlias = list[T]

View File

@@ -1,21 +0,0 @@
l = [1, 2, 3, 4, 5]
# Errors.
a = l[:]
b, c = 1, l[:]
d, e = l[:], 1
m = l[::]
l[:]
print(l[:])
# False negatives.
aa = a[:] # Type inference.
# OK.
t = (1, 2, 3, 4, 5)
f = t[:] # t.copy() is not supported.
g = l[1:3]
h = l[1:]
i = l[:3]
j = l[1:3:2]
k = l[::2]

View File

@@ -13,10 +13,10 @@ use crate::registry::Rule;
use crate::rules::{
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
};
use crate::settings::types::PythonVersion;
@@ -113,12 +113,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
}
if checker.enabled(Rule::InvalidIndexType) {
ruff::rules::invalid_index_type(checker, subscript);
}
if checker.settings.rules.enabled(Rule::SliceCopy) {
refurb::rules::slice_copy(checker, subscript);
}
pandas_vet::rules::subscript(checker, value, expr);
}
@@ -260,9 +258,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SixPY3) {
flake8_2020::rules::name_or_attribute(checker, expr);
}
if checker.enabled(Rule::UndocumentedWarn) {
flake8_logging::rules::undocumented_warn(checker, expr);
}
if checker.enabled(Rule::LoadBeforeGlobalDeclaration) {
pylint::rules::load_before_global_declaration(checker, id, expr);
}
@@ -329,9 +324,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
if checker.enabled(Rule::UndocumentedWarn) {
flake8_logging::rules::undocumented_warn(checker, expr);
}
pandas_vet::rules::attr(checker, attribute);
}
Expr::Call(
@@ -892,9 +884,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::QuadraticListSummation) {
ruff::rules::quadratic_list_summation(checker, call);
}
if checker.enabled(Rule::DirectLoggerInstantiation) {
flake8_logging::rules::direct_logger_instantiation(checker, call);
}
}
Expr::Dict(ast::ExprDict {
keys,

View File

@@ -411,13 +411,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::EqWithoutHash) {
pylint::rules::object_without_hash_method(checker, class_def);
}
if checker.enabled(Rule::TooManyPublicMethods) {
pylint::rules::too_many_public_methods(
checker,
class_def,
checker.settings.pylint.max_public_methods,
);
}
if checker.enabled(Rule::GlobalStatement) {
pylint::rules::global_statement(checker, name);
}
@@ -1184,7 +1177,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
iter,
orelse,
is_async,
range: _,
..
},
) => {
if checker.any_enabled(&[
@@ -1218,9 +1211,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ManualListCopy) {
perflint::rules::manual_list_copy(checker, target, body);
}
if checker.enabled(Rule::ManualDictComprehension) {
perflint::rules::manual_dict_comprehension(checker, target, body);
}
if checker.enabled(Rule::UnnecessaryListCast) {
perflint::rules::unnecessary_list_cast(checker, iter);
}

View File

@@ -269,7 +269,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
#[allow(deprecated)]
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
#[allow(deprecated)]
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
@@ -898,7 +897,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
(Perflint, "403") => (RuleGroup::Preview, rules::perflint::rules::ManualDictComprehension),
// flake8-fixme
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
@@ -918,11 +916,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
#[allow(deprecated)]
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
// flake8-logging
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
_ => return None,
})

View File

@@ -1,6 +1,4 @@
use libcst_native::{
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
};
use libcst_native::{Expression, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace};
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
match expr {
@@ -52,41 +50,3 @@ pub(crate) fn or_space(whitespace: ParenthesizableWhitespace) -> Parenthesizable
whitespace
}
}
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
if let Expression::UnaryOperation(ref expression) = expression {
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
return *expression.expression.clone();
}
}
if let Expression::Name(ref expression) = expression {
match expression.value {
"True" => {
return Expression::Name(Box::new(Name {
value: "False",
lpar: vec![],
rpar: vec![],
}));
}
"False" => {
return Expression::Name(Box::new(Name {
value: "True",
lpar: vec![],
rpar: vec![],
}));
}
_ => {}
}
}
Expression::UnaryOperation(Box::new(UnaryOperation {
operator: libcst_native::UnaryOp::Not {
whitespace_after: space(),
},
expression: Box::new(expression.clone()),
lpar: vec![],
rpar: vec![],
}))
}

View File

@@ -30,11 +30,6 @@ impl<'a> Docstring<'a> {
pub(crate) fn leading_quote(&self) -> &'a str {
&self.contents[TextRange::up_to(self.body_range.start())]
}
pub(crate) fn triple_quoted(&self) -> bool {
let leading_quote = self.leading_quote();
leading_quote.ends_with("\"\"\"") || leading_quote.ends_with("'''")
}
}
impl Ranged for Docstring<'_> {

View File

@@ -5,8 +5,6 @@
//!
//! [Ruff]: https://github.com/astral-sh/ruff
#[cfg(feature = "clap")]
pub use rule_selector::clap_completion::RuleSelectorParser;
pub use rule_selector::RuleSelector;
pub use rules::pycodestyle::rules::{IOError, SyntaxError};

View File

@@ -36,6 +36,9 @@ use crate::settings::{flags, Settings};
use crate::source_kind::SourceKind;
use crate::{directives, fs};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
/// A [`Result`]-like type that returns both data and an error. Used to return
/// diagnostics even in the face of parse errors, since many diagnostics can be
/// generated without a full AST.
@@ -540,9 +543,8 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
if cfg!(debug_assertions) {
eprintln!(
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
"{}: Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
"debug error".red().bold(),
":".bold(),
MAX_ITERATIONS,
fs::relativize_path(path),
codes,
@@ -551,17 +553,18 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
} else {
eprintln!(
r#"
{}{} Failed to converge after {} iterations.
{}: Failed to converge after {} iterations.
This indicates a bug in Ruff. If you could open an issue at:
This indicates a bug in `{}`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D
{}/issues/new?title=%5BInfinite%20loop%5D
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
"#,
"error".red().bold(),
":".bold(),
MAX_ITERATIONS,
CARGO_PKG_NAME,
CARGO_PKG_REPOSITORY,
fs::relativize_path(path),
codes
);
@@ -578,9 +581,8 @@ fn report_autofix_syntax_error(
let codes = collect_rule_codes(rules);
if cfg!(debug_assertions) {
eprintln!(
"{}{} Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
"{}: Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
"error".red().bold(),
":".bold(),
fs::relativize_path(path),
codes,
error,
@@ -589,16 +591,17 @@ fn report_autofix_syntax_error(
} else {
eprintln!(
r#"
{}{} Autofix introduced a syntax error. Reverting all changes.
{}: Autofix introduced a syntax error. Reverting all changes.
This indicates a bug in Ruff. If you could open an issue at:
This indicates a bug in `{}`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BAutofix%20error%5D
{}/issues/new?title=%5BAutofix%20error%5D
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
"#,
"error".red().bold(),
":".bold(),
CARGO_PKG_NAME,
CARGO_PKG_REPOSITORY,
fs::relativize_path(path),
codes,
);

View File

@@ -199,9 +199,6 @@ pub enum Linter {
/// [refurb](https://pypi.org/project/refurb/)
#[prefix = "FURB"]
Refurb,
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
#[prefix = "LOG"]
Flake8Logging,
/// Ruff-specific rules
#[prefix = "RUF"]
Ruff,

View File

@@ -15,8 +15,10 @@ use crate::settings::types::PreviewMode;
pub enum RuleSelector {
/// Select all rules (includes rules in preview if enabled)
All,
/// Category to select all rules in preview (includes legacy nursery rules)
Preview,
/// Legacy category to select all rules in the "nursery" which predated preview mode
#[deprecated(note = "The nursery was replaced with 'preview mode' which has no selector")]
#[deprecated(note = "Use `RuleSelector::Preview` for new rules instead")]
Nursery,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
@@ -52,6 +54,7 @@ impl FromStr for RuleSelector {
"ALL" => Ok(Self::All),
#[allow(deprecated)]
"NURSERY" => Ok(Self::Nursery),
"PREVIEW" => Ok(Self::Preview),
"C" => Ok(Self::C),
"T" => Ok(Self::T),
_ => {
@@ -118,6 +121,7 @@ impl RuleSelector {
RuleSelector::All => ("", "ALL"),
#[allow(deprecated)]
RuleSelector::Nursery => ("", "NURSERY"),
RuleSelector::Preview => ("", "PREVIEW"),
RuleSelector::C => ("", "C"),
RuleSelector::T => ("", "T"),
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
@@ -181,6 +185,9 @@ impl RuleSelector {
RuleSelector::Nursery => {
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
}
RuleSelector::Preview => RuleSelectorIter::Nursery(
Rule::iter().filter(|rule| rule.is_preview() || rule.is_nursery()),
),
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
.rules()
@@ -205,7 +212,7 @@ impl RuleSelector {
// Always include rules that are not in preview or the nursery
!(rule.is_preview() || rule.is_nursery())
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|| ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|| (matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. }) && rule.is_nursery())
// Enabling preview includes all preview or nursery rules
|| preview.is_enabled()
})
@@ -254,9 +261,8 @@ mod schema {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
[
// Include the non-standard "ALL" and "NURSERY" selectors.
// Include the non-standard "ALL" selector.
"ALL".to_string(),
"NURSERY".to_string(),
// Include the legacy "C" and "T" selectors.
"C".to_string(),
"T".to_string(),
@@ -295,6 +301,7 @@ impl RuleSelector {
pub fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::Preview => Specificity::All,
#[allow(deprecated)]
RuleSelector::Nursery => Specificity::All,
RuleSelector::T => Specificity::LinterGroup,
@@ -336,14 +343,13 @@ pub enum Specificity {
}
#[cfg(feature = "clap")]
pub mod clap_completion {
mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use strum::IntoEnumIterator;
use crate::{
codes::RuleCodePrefix,
registry::{Linter, RuleNamespace},
rule_selector::is_single_rule_selector,
RuleSelector,
};
@@ -363,29 +369,17 @@ pub mod clap_completion {
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
_cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let value = value
.to_str()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
value.parse().map_err(|_| {
let mut error =
clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
if let Some(arg) = arg {
error.insert(
clap::error::ContextKind::InvalidArg,
clap::error::ContextValue::String(arg.to_string()),
);
}
error.insert(
clap::error::ContextKind::InvalidValue,
clap::error::ContextValue::String(value.to_string()),
);
error
})
value
.parse()
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
@@ -400,34 +394,27 @@ pub mod clap_completion {
RuleCodePrefix::iter()
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
// off-by-default
.filter(|prefix| {
format!(
"{}{}",
prefix.linter().common_prefix(),
prefix.short_code()
) != "RUF014"
.filter(|p| {
format!("{}{}", p.linter().common_prefix(), p.short_code())
!= "RUF014"
})
.filter_map(|prefix| {
// Ex) `UP`
if prefix.short_code().is_empty() {
let code = prefix.linter().common_prefix();
let name = prefix.linter().name();
return Some(PossibleValue::new(code).help(name));
}
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
// Ex) `UP004`
if is_single_rule_selector(&prefix) {
let rule = prefix.rules().next()?;
let code = format!(
"{}{}",
prefix.linter().common_prefix(),
prefix.short_code()
);
let name: &'static str = rule.into();
return Some(PossibleValue::new(code).help(name));
}
let mut rules_iter = p.rules();
let rule1 = rules_iter.next();
let rule2 = rules_iter.next();
None
let value = PossibleValue::new(format!("{prefix}{code}"));
if rule2.is_none() {
let rule1 = rule1.unwrap();
let name: &'static str = rule1.into();
value.help(name)
} else {
value
}
}),
),
),

View File

@@ -148,7 +148,7 @@ impl Violation for StartProcessWithNoShell {
///
/// ## References
/// - [Python documentation: `subprocess.Popen()`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
/// - [Common Weakness Enumeration: CWE-426](https://cwe.mitre.org/data/definitions/426.html)
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
#[violation]
pub struct StartProcessWithPartialPath;

View File

@@ -70,7 +70,6 @@ impl Violation for MutableArgumentDefault {
fn message(&self) -> String {
format!("Do not use mutable data structures for argument defaults")
}
fn autofix_title(&self) -> Option<String> {
Some(format!("Replace with `None`; initialize within function"))
}

View File

@@ -17,8 +17,8 @@ use crate::checkers::ast::Checker;
/// contains multiple characters, the reader may be misled into thinking that
/// a prefix or suffix is being removed, rather than a set of characters.
///
/// In Python 3.9 and later, you can use `str.removeprefix` and
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
/// In Python 3.9 and later, you can use `str#removeprefix` and
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
/// respectively, which should be preferred when possible.
///
/// ## Example

View File

@@ -137,13 +137,13 @@ fn is_standard_library_override(
return false;
};
match name {
// Ex) `Event.set`
// Ex) `Event#set`
"set" => bases.iter().any(|base| {
semantic
.resolve_call_path(base)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["threading", "Event"]))
}),
// Ex) `Filter.filter`
// Ex) `Filter#filter`
"filter" => bases.iter().any(|base| {
semantic
.resolve_call_path(base)

View File

@@ -7,17 +7,17 @@ use libcst_native::{
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
TrailingWhitespace, Tuple,
};
use ruff_python_ast::Expr;
use ruff_text_size::{Ranged, TextRange};
use ruff_diagnostics::{Edit, Fix};
use ruff_python_ast::Expr;
use ruff_python_codegen::Stylist;
use ruff_python_semantic::SemanticModel;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::autofix::codemods::CodegenStylist;
use crate::autofix::edits::pad;
use crate::cst::helpers::{negate, space};
use crate::cst::helpers::space;
use crate::rules::flake8_comprehensions::rules::ObjectType;
use crate::{
checkers::ast::Checker,
@@ -718,7 +718,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
if outer_name.value == "list" {
tree = Expression::Call(Box::new((*inner_call).clone()));
} else {
// If the `reverse` argument is used...
// If the `reverse` argument is used
let args = if inner_call.args.iter().any(|arg| {
matches!(
arg.keyword,
@@ -728,7 +728,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
})
)
}) {
// Negate the `reverse` argument.
// Negate the `reverse` argument
inner_call
.args
.clone()
@@ -741,35 +741,35 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
..
})
) {
arg.value = negate(&arg.value);
if let Expression::Name(ref val) = arg.value {
if val.value == "True" {
// TODO: even better would be to drop the argument, as False is the default
arg.value = Expression::Name(Box::new(Name {
value: "False",
lpar: vec![],
rpar: vec![],
}));
arg
} else if val.value == "False" {
arg.value = Expression::Name(Box::new(Name {
value: "True",
lpar: vec![],
rpar: vec![],
}));
arg
} else {
arg
}
} else {
arg
}
} else {
arg
}
arg
})
.collect_vec()
} else {
let mut args = inner_call.args.clone();
// If necessary, parenthesize a generator expression, as a generator expression must
// be parenthesized if it's not a solitary argument. For example, given:
// ```python
// reversed(sorted(i for i in range(42)))
// ```
// Rewrite as:
// ```python
// sorted((i for i in range(42)), reverse=True)
// ```
if let [arg] = args.as_mut_slice() {
if matches!(arg.value, Expression::GeneratorExp(_)) {
if arg.value.lpar().is_empty() && arg.value.rpar().is_empty() {
arg.value = arg
.value
.clone()
.with_parens(LeftParen::default(), RightParen::default());
}
}
}
// Add the `reverse=True` argument.
args.push(Arg {
value: Expression::Name(Box::new(Name {
value: "True",

View File

@@ -1,6 +1,5 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::{self as ast, Comprehension, Expr};
use ruff_text_size::Ranged;
@@ -87,16 +86,28 @@ pub(crate) fn unnecessary_dict_comprehension(
if !generator.ifs.is_empty() || generator.is_async {
return;
}
let Some(key) = key.as_name_expr() else {
return;
};
let Some(value) = value.as_name_expr() else {
return;
};
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &generator.target else {
return;
};
let [target_key, target_value] = elts.as_slice() else {
return;
};
if ComparableExpr::from(key) != ComparableExpr::from(target_key) {
let Some(target_key) = target_key.as_name_expr() else {
return;
};
let Some(target_value) = target_value.as_name_expr() else {
return;
};
if target_key.id != key.id {
return;
}
if ComparableExpr::from(value) != ComparableExpr::from(target_value) {
if target_value.id != value.id {
return;
}
add_diagnostic(checker, expr);
@@ -115,7 +126,13 @@ pub(crate) fn unnecessary_list_set_comprehension(
if !generator.ifs.is_empty() || generator.is_async {
return;
}
if ComparableExpr::from(elt) != ComparableExpr::from(&generator.target) {
let Some(elt) = elt.as_name_expr() else {
return;
};
let Some(target) = generator.target.as_name_expr() else {
return;
};
if elt.id != target.id {
return;
}
add_diagnostic(checker, expr);

View File

@@ -103,18 +103,17 @@ C413.py:7:1: C413 [*] Unnecessary `reversed` call around `sorted()`
7 |+sorted(x, key=lambda e: e, reverse=False)
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 9 | reversed(sorted(x, reverse=False))
10 10 | reversed(sorted(x, reverse=x))
10 10 |
C413.py:8:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
6 | reversed(sorted(x, reverse=True))
7 | reversed(sorted(x, key=lambda e: e, reverse=True))
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
9 | reversed(sorted(x, reverse=False))
10 | reversed(sorted(x, reverse=x))
|
= help: Remove unnecessary `reversed` call
|
6 | reversed(sorted(x, reverse=True))
7 | reversed(sorted(x, key=lambda e: e, reverse=True))
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
9 | reversed(sorted(x, reverse=False))
|
= help: Remove unnecessary `reversed` call
Suggested fix
5 5 | reversed(sorted(x, key=lambda e: e))
@@ -123,8 +122,8 @@ C413.py:8:1: C413 [*] Unnecessary `reversed` call around `sorted()`
8 |-reversed(sorted(x, reverse=True, key=lambda e: e))
8 |+sorted(x, reverse=False, key=lambda e: e)
9 9 | reversed(sorted(x, reverse=False))
10 10 | reversed(sorted(x, reverse=x))
11 11 | reversed(sorted(x, reverse=not x))
10 10 |
11 11 | def reversed(*args, **kwargs):
C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
@@ -132,8 +131,8 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 | reversed(sorted(x, reverse=False))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
10 | reversed(sorted(x, reverse=x))
11 | reversed(sorted(x, reverse=not x))
10 |
11 | def reversed(*args, **kwargs):
|
= help: Remove unnecessary `reversed` call
@@ -143,87 +142,8 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 |-reversed(sorted(x, reverse=False))
9 |+sorted(x, reverse=True)
10 10 | reversed(sorted(x, reverse=x))
11 11 | reversed(sorted(x, reverse=not x))
12 12 |
C413.py:10:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 | reversed(sorted(x, reverse=False))
10 | reversed(sorted(x, reverse=x))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
11 | reversed(sorted(x, reverse=not x))
|
= help: Remove unnecessary `reversed` call
Suggested fix
7 7 | reversed(sorted(x, key=lambda e: e, reverse=True))
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 9 | reversed(sorted(x, reverse=False))
10 |-reversed(sorted(x, reverse=x))
10 |+sorted(x, reverse=not x)
11 11 | reversed(sorted(x, reverse=not x))
12 12 |
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
C413.py:11:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
9 | reversed(sorted(x, reverse=False))
10 | reversed(sorted(x, reverse=x))
11 | reversed(sorted(x, reverse=not x))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
12 |
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
= help: Remove unnecessary `reversed` call
Suggested fix
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
9 9 | reversed(sorted(x, reverse=False))
10 10 | reversed(sorted(x, reverse=x))
11 |-reversed(sorted(x, reverse=not x))
11 |+sorted(x, reverse=x)
12 12 |
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
14 14 | reversed(sorted(i for i in range(42)))
C413.py:14:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
14 | reversed(sorted(i for i in range(42)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
15 | reversed(sorted((i for i in range(42)), reverse=True))
|
= help: Remove unnecessary `reversed` call
Suggested fix
11 11 | reversed(sorted(x, reverse=not x))
12 12 |
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
14 |-reversed(sorted(i for i in range(42)))
14 |+sorted((i for i in range(42)), reverse=True)
15 15 | reversed(sorted((i for i in range(42)), reverse=True))
16 16 |
17 17 |
C413.py:15:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
14 | reversed(sorted(i for i in range(42)))
15 | reversed(sorted((i for i in range(42)), reverse=True))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
= help: Remove unnecessary `reversed` call
Suggested fix
12 12 |
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
14 14 | reversed(sorted(i for i in range(42)))
15 |-reversed(sorted((i for i in range(42)), reverse=True))
15 |+sorted((i for i in range(42)), reverse=False)
16 16 |
17 17 |
18 18 | def reversed(*args, **kwargs):
10 10 |
11 11 | def reversed(*args, **kwargs):
12 12 | return None

View File

@@ -40,18 +40,17 @@ C416.py:7:1: C416 [*] Unnecessary `set` comprehension (rewrite using `set()`)
7 |+set(x)
8 8 | {k: v for k, v in y}
9 9 | {k: v for k, v in d.items()}
10 10 | [(k, v) for k, v in d.items()]
10 10 |
C416.py:8:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
6 | [i for i in x]
7 | {i for i in x}
8 | {k: v for k, v in y}
| ^^^^^^^^^^^^^^^^^^^^ C416
9 | {k: v for k, v in d.items()}
10 | [(k, v) for k, v in d.items()]
|
= help: Rewrite using `dict()`
|
6 | [i for i in x]
7 | {i for i in x}
8 | {k: v for k, v in y}
| ^^^^^^^^^^^^^^^^^^^^ C416
9 | {k: v for k, v in d.items()}
|
= help: Rewrite using `dict()`
Suggested fix
5 5 |
@@ -60,8 +59,8 @@ C416.py:8:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
8 |-{k: v for k, v in y}
8 |+dict(y)
9 9 | {k: v for k, v in d.items()}
10 10 | [(k, v) for k, v in d.items()]
11 11 | {k: (a, b) for k, (a, b) in d.items()}
10 10 |
11 11 | [i for i, in z]
C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
@@ -69,8 +68,8 @@ C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
8 | {k: v for k, v in y}
9 | {k: v for k, v in d.items()}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
10 | [(k, v) for k, v in d.items()]
11 | {k: (a, b) for k, (a, b) in d.items()}
10 |
11 | [i for i, in z]
|
= help: Rewrite using `dict()`
@@ -80,64 +79,23 @@ C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
8 8 | {k: v for k, v in y}
9 |-{k: v for k, v in d.items()}
9 |+dict(d.items())
10 10 | [(k, v) for k, v in d.items()]
11 11 | {k: (a, b) for k, (a, b) in d.items()}
12 12 |
10 10 |
11 11 | [i for i, in z]
12 12 | [i for i, j in y]
C416.py:10:1: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
C416.py:22:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
8 | {k: v for k, v in y}
9 | {k: v for k, v in d.items()}
10 | [(k, v) for k, v in d.items()]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
11 | {k: (a, b) for k, (a, b) in d.items()}
|
= help: Rewrite using `list()`
Suggested fix
7 7 | {i for i in x}
8 8 | {k: v for k, v in y}
9 9 | {k: v for k, v in d.items()}
10 |-[(k, v) for k, v in d.items()]
10 |+list(d.items())
11 11 | {k: (a, b) for k, (a, b) in d.items()}
12 12 |
13 13 | [i for i, in z]
C416.py:11:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
9 | {k: v for k, v in d.items()}
10 | [(k, v) for k, v in d.items()]
11 | {k: (a, b) for k, (a, b) in d.items()}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
12 |
13 | [i for i, in z]
|
= help: Rewrite using `dict()`
Suggested fix
8 8 | {k: v for k, v in y}
9 9 | {k: v for k, v in d.items()}
10 10 | [(k, v) for k, v in d.items()]
11 |-{k: (a, b) for k, (a, b) in d.items()}
11 |+dict(d.items())
12 12 |
13 13 | [i for i, in z]
14 14 | [i for i, j in y]
C416.py:24:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
24 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
22 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
| ^^^^^^^^^^^^^^^^^^^^^^^ C416
|
= help: Rewrite using `list()`
Suggested fix
21 21 | {k: v if v else None for k, v in y}
22 22 |
23 23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
24 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
24 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))
19 19 | {k: v if v else None for k, v in y}
20 20 |
21 21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
22 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
22 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))

View File

@@ -1,27 +0,0 @@
//! Rules from [flake8-logging](https://pypi.org/project/flake8-logging/).
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::Settings;
use crate::test::test_path;
#[test_case(Rule::DirectLoggerInstantiation, Path::new("LOG001.py"))]
#[test_case(Rule::UndocumentedWarn, Path::new("LOG009.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_logging").join(path).as_path(),
&Settings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,77 +0,0 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does
/// Checks for direct instantiation of `logging.Logger`, as opposed to using
/// `logging.getLogger()`.
///
/// ## Why is this bad?
/// The [Logger Objects] documentation states that:
///
/// > Note that Loggers should NEVER be instantiated directly, but always
/// > through the module-level function `logging.getLogger(name)`.
///
/// If a logger is directly instantiated, it won't be added to the logger
/// tree, and will bypass all configuration. Messages logged to it will
/// only be sent to the "handler of last resort", skipping any filtering
/// or formatting.
///
/// ## Example
/// ```python
/// import logging
///
/// logger = logging.Logger(__name__)
/// ```
///
/// Use instead:
/// ```python
/// import logging
///
/// logger = logging.getLogger(__name__)
/// ```
///
/// [Logger Objects]: https://docs.python.org/3/library/logging.html#logger-objects
#[violation]
pub struct DirectLoggerInstantiation;
impl Violation for DirectLoggerInstantiation {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `logging.getLogger()` to instantiate loggers")
}
fn autofix_title(&self) -> Option<String> {
Some(format!("Replace with `logging.getLogger()`"))
}
}
/// LOG001
pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"]))
{
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "getLogger"),
call.func.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, call.func.range());
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -1,5 +0,0 @@
pub(crate) use direct_logger_instantiation::*;
pub(crate) use undocumented_warn::*;
mod direct_logger_instantiation;
mod undocumented_warn;

View File

@@ -1,71 +0,0 @@
use ruff_python_ast::Expr;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `logging.WARN`.
///
/// ## Why is this bad?
/// The `logging.WARN` constant is an undocumented alias for `logging.WARNING`.
///
/// Although its not explicitly deprecated, `logging.WARN` is not mentioned
/// in the `logging` documentation. Prefer `logging.WARNING` instead.
///
/// ## Example
/// ```python
/// import logging
///
///
/// logging.basicConfig(level=logging.WARN)
/// ```
///
/// Use instead:
/// ```python
/// import logging
///
///
/// logging.basicConfig(level=logging.WARNING)
/// ```
#[violation]
pub struct UndocumentedWarn;
impl Violation for UndocumentedWarn {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Use of undocumented `logging.WARN` constant")
}
fn autofix_title(&self) -> Option<String> {
Some(format!("Replace `logging.WARN` with `logging.WARNING`"))
}
}
/// LOG009
pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
if checker
.semantic()
.resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"]))
{
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "WARNING"),
expr.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, expr.range());
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -1,40 +0,0 @@
---
source: crates/ruff/src/rules/flake8_logging/mod.rs
---
LOG001.py:3:1: LOG001 [*] Use `logging.getLogger()` to instantiate loggers
|
1 | import logging
2 |
3 | logging.Logger(__name__)
| ^^^^^^^^^^^^^^ LOG001
4 | logging.Logger()
5 | logging.getLogger(__name__)
|
= help: Replace with `logging.getLogger()`
Suggested fix
1 1 | import logging
2 2 |
3 |-logging.Logger(__name__)
3 |+logging.getLogger(__name__)
4 4 | logging.Logger()
5 5 | logging.getLogger(__name__)
LOG001.py:4:1: LOG001 [*] Use `logging.getLogger()` to instantiate loggers
|
3 | logging.Logger(__name__)
4 | logging.Logger()
| ^^^^^^^^^^^^^^ LOG001
5 | logging.getLogger(__name__)
|
= help: Replace with `logging.getLogger()`
Suggested fix
1 1 | import logging
2 2 |
3 3 | logging.Logger(__name__)
4 |-logging.Logger()
4 |+logging.getLogger()
5 5 | logging.getLogger(__name__)

View File

@@ -1,41 +0,0 @@
---
source: crates/ruff/src/rules/flake8_logging/mod.rs
---
LOG009.py:3:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
1 | import logging
2 |
3 | logging.WARN # LOG009
| ^^^^^^^^^^^^ LOG009
4 | logging.WARNING # OK
|
= help: Replace `logging.WARN` with `logging.WARNING`
Suggested fix
1 1 | import logging
2 2 |
3 |-logging.WARN # LOG009
3 |+logging.WARNING # LOG009
4 4 | logging.WARNING # OK
5 5 |
6 6 | from logging import WARN, WARNING
LOG009.py:8:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
6 | from logging import WARN, WARNING
7 |
8 | WARN # LOG009
| ^^^^ LOG009
9 | WARNING # OK
|
= help: Replace `logging.WARN` with `logging.WARNING`
Suggested fix
5 5 |
6 6 | from logging import WARN, WARNING
7 7 |
8 |-WARN # LOG009
8 |+logging.WARNING # LOG009
9 9 | WARNING # OK

View File

@@ -4,7 +4,7 @@ use anyhow::Result;
use anyhow::{bail, Context};
use libcst_native::{
self, Assert, BooleanOp, CompoundStatement, Expression, ParenthesizedNode, SimpleStatementLine,
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace,
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace, UnaryOperation,
};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
@@ -21,7 +21,7 @@ use ruff_text_size::Ranged;
use crate::autofix::codemods::CodegenStylist;
use crate::checkers::ast::Checker;
use crate::cst::helpers::negate;
use crate::cst::helpers::space;
use crate::cst::matchers::match_indented_block;
use crate::cst::matchers::match_module;
use crate::importer::ImportRequest;
@@ -567,6 +567,23 @@ fn is_composite_condition(test: &Expr) -> CompositionKind {
CompositionKind::None
}
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
if let Expression::UnaryOperation(ref expression) = expression {
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
return *expression.expression.clone();
}
}
Expression::UnaryOperation(Box::new(UnaryOperation {
operator: libcst_native::UnaryOp::Not {
whitespace_after: space(),
},
expression: Box::new(expression.clone()),
lpar: vec![],
rpar: vec![],
}))
}
/// Propagate parentheses from a parent to a child expression, if necessary.
///
/// For example, when splitting:

View File

@@ -12,10 +12,10 @@ use crate::registry::AsRule;
use crate::rules::flynt::helpers;
/// ## What it does
/// Checks for `str.join` calls that can be replaced with f-strings.
/// Checks for `str#join` calls that can be replaced with f-strings.
///
/// ## Why is this bad?
/// f-strings are more readable and generally preferred over `str.join` calls.
/// f-strings are more readable and generally preferred over `str#join` calls.
///
/// ## Example
/// ```python

View File

@@ -22,7 +22,6 @@ pub mod flake8_future_annotations;
pub mod flake8_gettext;
pub mod flake8_implicit_str_concat;
pub mod flake8_import_conventions;
pub mod flake8_logging;
pub mod flake8_logging_format;
pub mod flake8_no_pep420;
pub mod flake8_pie;

View File

@@ -19,7 +19,6 @@ mod tests {
#[test_case(Rule::TryExceptInLoop, Path::new("PERF203.py"))]
#[test_case(Rule::ManualListComprehension, Path::new("PERF401.py"))]
#[test_case(Rule::ManualListCopy, Path::new("PERF402.py"))]
#[test_case(Rule::ManualDictComprehension, Path::new("PERF403.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,178 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_dict;
use ruff_python_semantic::Binding;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `for` loops that can be replaced by a dictionary comprehension.
///
/// ## Why is this bad?
/// When creating or extending a dictionary in a for-loop, prefer a dictionary
/// comprehension. Comprehensions are more readable and more performant.
///
/// For example, when comparing `{x: x for x in list(range(1000))}` to the `for`
/// loop version, the comprehension is ~10% faster on Python 3.11.
///
/// Note that, as with all `perflint` rules, this is only intended as a
/// micro-optimization, and will have a negligible impact on performance in
/// most cases.
///
/// ## Example
/// ```python
/// pairs = (("a", 1), ("b", 2))
/// result = {}
/// for x, y in pairs:
/// if y % 2:
/// result[x] = y
/// ```
///
/// Use instead:
/// ```python
/// pairs = (("a", 1), ("b", 2))
/// result = {x: y for x, y in pairs if y % 2}
/// ```
///
/// If you're appending to an existing dictionary, use the `update` method instead:
/// ```python
/// pairs = (("a", 1), ("b", 2))
/// result.update({x: y for x, y in pairs if y % 2})
/// ```
#[violation]
pub struct ManualDictComprehension;
impl Violation for ManualDictComprehension {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use a dictionary comprehension instead of a for-loop")
}
}
/// PERF403
pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
let (stmt, if_test) = match body {
// ```python
// for idx, name in enumerate(names):
// if idx % 2 == 0:
// result[name] = idx
// ```
[Stmt::If(ast::StmtIf {
body,
elif_else_clauses,
test,
..
})] => {
// TODO(charlie): If there's an `else` clause, verify that the `else` has the
// same structure.
if !elif_else_clauses.is_empty() {
return;
}
let [stmt] = body.as_slice() else {
return;
};
(stmt, Some(test))
}
// ```python
// for idx, name in enumerate(names):
// result[name] = idx
// ```
[stmt] => (stmt, None),
_ => return,
};
let Stmt::Assign(ast::StmtAssign {
targets,
value,
range,
}) = stmt
else {
return;
};
let [Expr::Subscript(ast::ExprSubscript {
value: subscript_value,
slice,
..
})] = targets.as_slice()
else {
return;
};
match target {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
if !elts
.iter()
.any(|elt| ComparableExpr::from(slice) == ComparableExpr::from(elt))
{
return;
}
if !elts
.iter()
.any(|elt| ComparableExpr::from(value) == ComparableExpr::from(elt))
{
return;
}
}
Expr::Name(_) => {
if ComparableExpr::from(slice) != ComparableExpr::from(target) {
return;
}
if ComparableExpr::from(value) != ComparableExpr::from(target) {
return;
}
}
_ => return,
}
// Exclude non-dictionary value.
let Expr::Name(ast::ExprName {
id: subscript_name, ..
}) = subscript_value.as_ref()
else {
return;
};
let scope = checker.semantic().current_scope();
let bindings: Vec<&Binding> = scope
.get_all(subscript_name)
.map(|binding_id| checker.semantic().binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return;
};
if !is_dict(binding, checker.semantic()) {
return;
}
// Avoid if the value is used in the conditional test, e.g.,
//
// ```python
// for x in y:
// if x in filtered:
// filtered[x] = y
// ```
//
// Converting this to a dictionary comprehension would raise a `NameError` as
// `filtered` is not defined yet:
//
// ```python
// filtered = {x: y for x in y if x in filtered}
// ```
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr()
.is_some_and(|expr| expr.id == *subscript_name)
})
}) {
return;
}
checker
.diagnostics
.push(Diagnostic::new(ManualDictComprehension, *range));
}

View File

@@ -1,12 +1,10 @@
pub(crate) use incorrect_dict_iterator::*;
pub(crate) use manual_dict_comprehension::*;
pub(crate) use manual_list_comprehension::*;
pub(crate) use manual_list_copy::*;
pub(crate) use try_except_in_loop::*;
pub(crate) use unnecessary_list_cast::*;
mod incorrect_dict_iterator;
mod manual_dict_comprehension;
mod manual_list_comprehension;
mod manual_list_copy;
mod try_except_in_loop;

View File

@@ -1,52 +0,0 @@
---
source: crates/ruff/src/rules/perflint/mod.rs
---
PERF403.py:5:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
3 | result = {}
4 | for idx, name in enumerate(fruit):
5 | result[idx] = name # PERF403
| ^^^^^^^^^^^^^^^^^^ PERF403
|
PERF403.py:13:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
11 | for idx, name in enumerate(fruit):
12 | if idx % 2:
13 | result[idx] = name # PERF403
| ^^^^^^^^^^^^^^^^^^ PERF403
|
PERF403.py:31:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
29 | for idx, name in enumerate(fruit):
30 | if idx % 2:
31 | result[idx] = name # PERF403
| ^^^^^^^^^^^^^^^^^^ PERF403
|
PERF403.py:61:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
59 | for idx, name in enumerate(fruit):
60 | if idx % 2:
61 | result[idx] = name # PERF403
| ^^^^^^^^^^^^^^^^^^ PERF403
|
PERF403.py:76:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
74 | result = {}
75 | for name in fruit:
76 | result[name] = name # PERF403
| ^^^^^^^^^^^^^^^^^^^ PERF403
|
PERF403.py:83:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
81 | result = {}
82 | for idx, name in enumerate(fruit):
83 | result[name] = idx # PERF403
| ^^^^^^^^^^^^^^^^^^ PERF403
|

View File

@@ -70,10 +70,6 @@ impl Violation for BlankLineAfterSummary {
pub(crate) fn blank_after_summary(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
if !docstring.triple_quoted() {
return;
}
let mut lines_count: usize = 1;
let mut blanks_count = 0;
for line in body.trim().universal_newlines().skip(1) {

View File

@@ -63,10 +63,6 @@ pub(crate) fn newline_after_last_paragraph(checker: &mut Checker, docstring: &Do
let contents = docstring.contents;
let body = docstring.body();
if !docstring.triple_quoted() {
return;
}
let mut line_count = 0;
for line in NewlineWithTrailingNewline::from(body.as_str()) {
if !line.trim().is_empty() {

View File

@@ -1691,10 +1691,7 @@ fn common_section(
}
if checker.enabled(Rule::NoBlankLineBeforeSection) {
if !context
.previous_line()
.is_some_and(|line| line.trim().is_empty())
{
if !context.previous_line().is_some_and(str::is_empty) {
let mut diagnostic = Diagnostic::new(
NoBlankLineBeforeSection {
name: context.section_name().to_string(),

View File

@@ -97,6 +97,5 @@ D.py:658:5: D204 [*] 1 blank line required after class docstring
659 |+
659 660 | def sort_services(self):
660 661 | pass
661 662 |

View File

@@ -77,13 +77,4 @@ D.py:658:5: D300 Use triple double quotes `"""`
660 | pass
|
D.py:664:5: D300 Use triple double quotes `"""`
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D300
|

View File

@@ -310,21 +310,4 @@ D.py:641:18: D400 [*] First line should end with a period
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
D.py:664:5: D400 [*] First line should end with a period
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D400
|
= help: Add period
Suggested fix
662 662 |
663 663 | def newline_after_closing_quote(self):
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
665 |+ but continuations shouldn't be considered multi-line."

View File

@@ -496,6 +496,5 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters"
530 |+ ----------
530 531 | ===========
531 532 | """
532 533 |

View File

@@ -292,21 +292,4 @@ D.py:641:18: D415 [*] First line should end with a period, question mark, or exc
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
D.py:664:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D415
|
= help: Add closing punctuation
Suggested fix
662 662 |
663 663 | def newline_after_closing_quote(self):
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
665 |+ but continuations shouldn't be considered multi-line."

View File

@@ -256,20 +256,4 @@ mod tests {
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn too_many_public_methods() -> Result<()> {
let diagnostics = test_path(
Path::new("pylint/too_many_public_methods.py"),
&Settings {
pylint: pylint::settings::Settings {
max_public_methods: 7,
..pylint::settings::Settings::default()
},
..Settings::for_rules(vec![Rule::TooManyPublicMethods])
},
)?;
assert_messages!(diagnostics);
Ok(())
}
}

View File

@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
use crate::settings::types::PythonVersion;
/// ## What it does
/// Checks duplicate characters in `str.strip` calls.
/// Checks duplicate characters in `str#strip` calls.
///
/// ## Why is this bad?
/// All characters in `str.strip` calls are removed from both the leading and
/// All characters in `str#strip` calls are removed from both the leading and
/// trailing ends of the string. Including duplicate characters in the call
/// is redundant and often indicative of a mistake.
///
/// In Python 3.9 and later, you can use `str.removeprefix` and
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
/// In Python 3.9 and later, you can use `str#removeprefix` and
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
/// respectively, which should be preferred when possible.
///
/// ## Example

View File

@@ -43,7 +43,6 @@ pub(crate) use subprocess_run_without_check::*;
pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*;
pub(crate) use too_many_branches::*;
pub(crate) use too_many_public_methods::*;
pub(crate) use too_many_return_statements::*;
pub(crate) use too_many_statements::*;
pub(crate) use type_bivariance::*;
@@ -102,7 +101,6 @@ mod subprocess_run_without_check;
mod sys_exit_alias;
mod too_many_arguments;
mod too_many_branches;
mod too_many_public_methods;
mod too_many_return_statements;
mod too_many_statements;
mod type_bivariance;

View File

@@ -10,7 +10,7 @@ use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::hashable::HashableExpr;
use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr};
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::Ranged;
use crate::autofix::snippet::SourceCodeSnippet;
use crate::checkers::ast::Checker;
@@ -74,8 +74,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
return;
}
// Map from expression hash to (starting offset, number of comparisons, list
let mut value_to_comparators: FxHashMap<HashableExpr, (TextSize, Vec<&Expr>)> =
let mut value_to_comparators: FxHashMap<HashableExpr, (usize, Vec<&Expr>)> =
FxHashMap::with_capacity_and_hasher(
bool_op.values.len() * 2,
BuildHasherDefault::default(),
@@ -96,31 +95,30 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
};
if matches!(left.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
let (_, left_matches) = value_to_comparators
let (left_count, left_matches) = value_to_comparators
.entry(left.deref().into())
.or_insert_with(|| (left.start(), Vec::new()));
.or_insert_with(|| (0, Vec::new()));
*left_count += 1;
left_matches.push(right);
}
if matches!(right, Expr::Name(_) | Expr::Attribute(_)) {
let (_, right_matches) = value_to_comparators
let (right_count, right_matches) = value_to_comparators
.entry(right.into())
.or_insert_with(|| (right.start(), Vec::new()));
.or_insert_with(|| (0, Vec::new()));
*right_count += 1;
right_matches.push(left);
}
}
for (value, (_, comparators)) in value_to_comparators
.iter()
.sorted_by_key(|(_, (start, _))| *start)
{
if comparators.len() > 1 {
for (value, (count, comparators)) in value_to_comparators {
if count > 1 {
checker.diagnostics.push(Diagnostic::new(
RepeatedEqualityComparison {
expression: SourceCodeSnippet::new(merged_membership_test(
value.as_expr(),
bool_op.op,
comparators,
&comparators,
checker.locator(),
)),
},

View File

@@ -1,126 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_semantic::analyze::visibility::{self, Visibility::Public};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for classes with too many public methods
///
/// By default, this rule allows up to 20 statements, as configured by the
/// [`pylint.max-public-methods`] option.
///
/// ## Why is this bad?
/// Classes with many public methods are harder to understand
/// and maintain.
///
/// Instead, consider refactoring the class into separate classes.
///
/// ## Example
/// Assuming that `pylint.max-public-settings` is set to 5:
/// ```python
/// class Linter:
/// def __init__(self):
/// pass
///
/// def pylint(self):
/// pass
///
/// def pylint_settings(self):
/// pass
///
/// def flake8(self):
/// pass
///
/// def flake8_settings(self):
/// pass
///
/// def pydocstyle(self):
/// pass
///
/// def pydocstyle_settings(self):
/// pass
/// ```
///
/// Use instead:
/// ```python
/// class Linter:
/// def __init__(self):
/// self.pylint = Pylint()
/// self.flake8 = Flake8()
/// self.pydocstyle = Pydocstyle()
///
/// def lint(self):
/// pass
///
///
/// class Pylint:
/// def lint(self):
/// pass
///
/// def settings(self):
/// pass
///
///
/// class Flake8:
/// def lint(self):
/// pass
///
/// def settings(self):
/// pass
///
///
/// class Pydocstyle:
/// def lint(self):
/// pass
///
/// def settings(self):
/// pass
/// ```
///
/// ## Options
/// - `pylint.max-public-methods`
#[violation]
pub struct TooManyPublicMethods {
methods: usize,
max_methods: usize,
}
impl Violation for TooManyPublicMethods {
#[derive_message_formats]
fn message(&self) -> String {
let TooManyPublicMethods {
methods,
max_methods,
} = self;
format!("Too many public methods ({methods} > {max_methods})")
}
}
/// R0904
pub(crate) fn too_many_public_methods(
checker: &mut Checker,
class_def: &ast::StmtClassDef,
max_methods: usize,
) {
let methods = class_def
.body
.iter()
.filter(|stmt| {
stmt.as_function_def_stmt()
.is_some_and(|node| matches!(visibility::method_visibility(node), Public))
})
.count();
if methods > max_methods {
checker.diagnostics.push(Diagnostic::new(
TooManyPublicMethods {
methods,
max_methods,
},
class_def.range(),
));
}
}

View File

@@ -42,7 +42,6 @@ pub struct Settings {
pub max_returns: usize,
pub max_branches: usize,
pub max_statements: usize,
pub max_public_methods: usize,
}
impl Default for Settings {
@@ -53,7 +52,6 @@ impl Default for Settings {
max_returns: 6,
max_branches: 12,
max_statements: 50,
max_public_methods: 20,
}
}
}

View File

@@ -1,46 +0,0 @@
---
source: crates/ruff/src/rules/pylint/mod.rs
---
too_many_public_methods.py:1:1: PLR0904 Too many public methods (10 > 7)
|
1 | / class Everything:
2 | | foo = 1
3 | |
4 | | def __init__(self):
5 | | pass
6 | |
7 | | def _private(self):
8 | | pass
9 | |
10 | | def method1(self):
11 | | pass
12 | |
13 | | def method2(self):
14 | | pass
15 | |
16 | | def method3(self):
17 | | pass
18 | |
19 | | def method4(self):
20 | | pass
21 | |
22 | | def method5(self):
23 | | pass
24 | |
25 | | def method6(self):
26 | | pass
27 | |
28 | | def method7(self):
29 | | pass
30 | |
31 | | def method8(self):
32 | | pass
33 | |
34 | | def method9(self):
35 | | pass
| |____________^ PLR0904
36 |
37 | class Small:
|

View File

@@ -138,52 +138,16 @@ const PIPES_TO_SHLEX: &[&str] = &["quote"];
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
"AbstractSet",
"AnyStr",
"AsyncIterable",
"AsyncIterator",
"Awaitable",
"BinaryIO",
"Callable",
"ClassVar",
"Collection",
"Container",
"ContextManager",
"Coroutine",
"DefaultDict",
"Dict",
"FrozenSet",
"Generator",
"Generic",
"Hashable",
"IO",
"ItemsView",
"Iterable",
"Iterator",
"KeysView",
"List",
"Mapping",
"MappingView",
"Match",
"MutableMapping",
"MutableSequence",
"MutableSet",
"Optional",
"Pattern",
"Reversible",
"Sequence",
"Set",
"Sized",
"TYPE_CHECKING",
"Text",
"TextIO",
"Tuple",
"Type",
"Union",
"ValuesView",
"cast",
"no_type_check",
"no_type_check_decorator",
// Introduced in Python 3.5.2, but `typing_extensions` contains backported bugfixes and
// optimizations,
// "NewType",
@@ -201,7 +165,6 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
"ChainMap",
"Counter",
"Deque",
"ForwardRef",
"NoReturn",
];
@@ -324,18 +287,6 @@ const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_312: &[&str] = &[
"NamedTuple",
// Introduced in Python 3.8, but `typing_extensions` backports a ton of optimizations that were
// added in Python 3.12.
"Protocol",
"SupportsAbs",
"SupportsBytes",
"SupportsComplex",
"SupportsFloat",
"SupportsInt",
"SupportsRound",
"TypedDict",
"Unpack",
// Introduced in Python 3.11, but `typing_extensions` backports the `frozen_default` argument,
// which was introduced in Python 3.12.
"dataclass_transform",

View File

@@ -22,10 +22,10 @@ use crate::rules::pyflakes::format::FormatSummary;
use crate::rules::pyupgrade::helpers::curly_escape;
/// ## What it does
/// Checks for `str.format` calls that can be replaced with f-strings.
/// Checks for `str#format` calls that can be replaced with f-strings.
///
/// ## Why is this bad?
/// f-strings are more readable and generally preferred over `str.format`
/// f-strings are more readable and generally preferred over `str#format`
/// calls.
///
/// ## Example

View File

@@ -42,16 +42,13 @@ pub struct UnnecessaryEncodeUTF8 {
impl AlwaysAutofixableViolation for UnnecessaryEncodeUTF8 {
#[derive_message_formats]
fn message(&self) -> String {
match self.reason {
Reason::BytesLiteral => format!("Unnecessary call to `encode` as UTF-8"),
Reason::DefaultArgument => format!("Unnecessary UTF-8 `encoding` argument to `encode`"),
}
format!("Unnecessary call to `encode` as UTF-8")
}
fn autofix_title(&self) -> String {
match self.reason {
Reason::BytesLiteral => "Rewrite as bytes literal".to_string(),
Reason::DefaultArgument => "Remove unnecessary `encoding` argument".to_string(),
Reason::DefaultArgument => "Remove unnecessary encoding argument".to_string(),
}
}
}

View File

@@ -1,6 +1,4 @@
use ast::{Constant, ExprCall, ExprConstant};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{
self as ast,
visitor::{self, Visitor},
@@ -8,29 +6,20 @@ use ruff_python_ast::{
TypeParam, TypeParamTypeVar,
};
use ruff_python_semantic::SemanticModel;
use crate::{registry::AsRule, settings::types::PythonVersion};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::{registry::AsRule, settings::types::PythonVersion};
/// ## What it does
/// Checks for use of `TypeAlias` annotation for declaring type aliases.
///
/// ## Why is this bad?
/// The `type` keyword was introduced in Python 3.12 by [PEP 695] for defining
/// type aliases. The `type` keyword is easier to read and provides cleaner
/// support for generics.
///
/// ## Known problems
/// [PEP 695] uses inferred variance for type parameters, instead of the
/// `covariant` and `contravariant` keywords used by `TypeParam` variables. As
/// such, rewriting a `TypeParam` variable to a `type` alias may change its
/// variance.
///
/// Unlike `TypeParam` variables, [PEP 695]-style `type` aliases cannot be used
/// at runtime. For example, calling `isinstance` on a `type` alias will throw
/// a `TypeError`. As such, rewriting a `TypeParam` via the `type` keyword will
/// cause issues for parameters that are used for such runtime checks.
/// The `type` keyword was introduced in Python 3.12 by PEP-695 for defining type aliases.
/// The type keyword is easier to read and provides cleaner support for generics.
///
/// ## Example
/// ```python
@@ -41,8 +30,6 @@ use crate::{registry::AsRule, settings::types::PythonVersion};
/// ```python
/// type ListOfInt = list[int]
/// ```
///
/// [PEP 695]: https://peps.python.org/pep-0695/
#[violation]
pub struct NonPEP695TypeAlias {
name: String,
@@ -96,36 +83,24 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
let mut diagnostic = Diagnostic::new(NonPEP695TypeAlias { name: name.clone() }, stmt.range());
if checker.patch(diagnostic.kind.rule()) {
let mut visitor = TypeVarReferenceVisitor {
vars: vec![],
names: vec![],
semantic: checker.semantic(),
};
visitor.visit_expr(value);
let type_params = if visitor.vars.is_empty() {
let type_params = if visitor.names.is_empty() {
None
} else {
Some(ast::TypeParams {
range: TextRange::default(),
type_params: visitor
.vars
.into_iter()
.map(|TypeVar { name, restriction }| {
.names
.iter()
.map(|name| {
TypeParam::TypeVar(TypeParamTypeVar {
range: TextRange::default(),
name: Identifier::new(name.id.clone(), TextRange::default()),
bound: match restriction {
Some(TypeVarRestriction::Bound(bound)) => {
Some(Box::new(bound.clone()))
}
Some(TypeVarRestriction::Constraint(constraints)) => {
Some(Box::new(Expr::Tuple(ast::ExprTuple {
range: TextRange::default(),
elts: constraints.into_iter().cloned().collect(),
ctx: ast::ExprContext::Load,
})))
}
None => None,
},
bound: None,
})
})
.collect(),
@@ -145,22 +120,8 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
checker.diagnostics.push(diagnostic);
}
#[derive(Debug)]
enum TypeVarRestriction<'a> {
/// A type variable with a bound, e.g., `TypeVar("T", bound=int)`.
Bound(&'a Expr),
/// A type variable with constraints, e.g., `TypeVar("T", int, str)`.
Constraint(Vec<&'a Expr>),
}
#[derive(Debug)]
struct TypeVar<'a> {
name: &'a ExprName,
restriction: Option<TypeVarRestriction<'a>>,
}
struct TypeVarReferenceVisitor<'a> {
vars: Vec<TypeVar<'a>>,
names: Vec<&'a ExprName>,
semantic: &'a SemanticModel<'a>,
}
@@ -188,16 +149,16 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
..
}) => {
if self.semantic.match_typing_expr(subscript_value, "TypeVar") {
self.vars.push(TypeVar {
name,
restriction: None,
});
self.names.push(name);
}
}
Expr::Call(ExprCall {
func, arguments, ..
}) => {
// TODO(zanieb): Add support for bounds and variance declarations
// for now this only supports `TypeVar("...")`
if self.semantic.match_typing_expr(func, "TypeVar")
&& arguments.args.len() == 1
&& arguments.args.first().is_some_and(|arg| {
matches!(
arg,
@@ -207,18 +168,9 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
})
)
})
&& arguments.keywords.is_empty()
{
let restriction = if let Some(bound) = arguments.find_keyword("bound") {
Some(TypeVarRestriction::Bound(&bound.value))
} else if arguments.args.len() > 1 {
Some(TypeVarRestriction::Constraint(
arguments.args.iter().skip(1).collect(),
))
} else {
None
};
self.vars.push(TypeVar { name, restriction });
self.names.push(name);
}
}
_ => {}

View File

@@ -227,7 +227,7 @@ UP012.py:24:5: UP012 [*] Unnecessary call to `encode` as UTF-8
26 26 |
27 27 | # `encode` on variables should not be processed.
UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
31 | bar = "bar"
32 | f"foo{bar}".encode("utf-8")
@@ -235,7 +235,7 @@ UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
33 | encoding = "latin"
34 | "foo".encode(encoding)
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
29 29 | string.encode("utf-8")
@@ -247,7 +247,7 @@ UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
34 34 | "foo".encode(encoding)
35 35 | f"foo{bar}".encode(encoding)
UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
34 | "foo".encode(encoding)
35 | f"foo{bar}".encode(encoding)
@@ -258,7 +258,7 @@ UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
39 |
40 | # `encode` with custom args and kwargs should not be processed.
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
33 33 | encoding = "latin"
@@ -272,7 +272,7 @@ UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
40 38 | # `encode` with custom args and kwargs should not be processed.
41 39 | "foo".encode("utf-8", errors="replace")
UP012.py:53:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:53:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
52 | # Unicode literals should only be stripped of default encoding.
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
@@ -280,7 +280,7 @@ UP012.py:53:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
54 | "unicode text©".encode()
55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
50 50 | "unicode text©".encode(encoding="utf-8", errors="replace")
@@ -292,7 +292,7 @@ UP012.py:53:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
55 55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
56 56 |
UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
54 | "unicode text©".encode()
@@ -301,7 +301,7 @@ UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
56 |
57 | r"foo\o".encode("utf-8") # br"foo\o"
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
52 52 | # Unicode literals should only be stripped of default encoding.
@@ -471,7 +471,7 @@ UP012.py:69:1: UP012 [*] Unnecessary call to `encode` as UTF-8
74 74 | (f"foo{bar}").encode("utf-8")
75 75 | (f"foo{bar}").encode(encoding="utf-8")
UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
72 | )).encode()
73 |
@@ -480,7 +480,7 @@ UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
75 | (f"foo{bar}").encode(encoding="utf-8")
76 | ("unicode text©").encode("utf-8")
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
71 71 | "def"
@@ -492,7 +492,7 @@ UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
76 76 | ("unicode text©").encode("utf-8")
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
74 | (f"foo{bar}").encode("utf-8")
75 | (f"foo{bar}").encode(encoding="utf-8")
@@ -500,7 +500,7 @@ UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
76 | ("unicode text©").encode("utf-8")
77 | ("unicode text©").encode(encoding="utf-8")
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
72 72 | )).encode()
@@ -511,7 +511,7 @@ UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
76 76 | ("unicode text©").encode("utf-8")
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
74 | (f"foo{bar}").encode("utf-8")
75 | (f"foo{bar}").encode(encoding="utf-8")
@@ -519,7 +519,7 @@ UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP012
77 | ("unicode text©").encode(encoding="utf-8")
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
73 73 |
@@ -529,14 +529,14 @@ UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
76 |+("unicode text©").encode()
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:77:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
75 | (f"foo{bar}").encode(encoding="utf-8")
76 | ("unicode text©").encode("utf-8")
77 | ("unicode text©").encode(encoding="utf-8")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP012
|
= help: Remove unnecessary `encoding` argument
= help: Remove unnecessary encoding argument
Fix
74 74 | (f"foo{bar}").encode("utf-8")

View File

@@ -931,7 +931,7 @@ UP035.py:74:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
74 |+from collections.abc import AsyncGenerator
75 75 | from typing import Reversible
76 76 | from typing import Generator
77 77 | from typing import Callable
77 77 |
UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
|
@@ -940,7 +940,6 @@ UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
75 | from typing import Reversible
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
76 | from typing import Generator
77 | from typing import Callable
|
= help: Import from `collections.abc`
@@ -951,8 +950,8 @@ UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
75 |-from typing import Reversible
75 |+from collections.abc import Reversible
76 76 | from typing import Generator
77 77 | from typing import Callable
78 78 | from typing import cast
77 77 |
78 78 | # OK
UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
|
@@ -960,8 +959,8 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
75 | from typing import Reversible
76 | from typing import Generator
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
77 | from typing import Callable
78 | from typing import cast
77 |
78 | # OK
|
= help: Import from `collections.abc`
@@ -971,63 +970,23 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
75 75 | from typing import Reversible
76 |-from typing import Generator
76 |+from collections.abc import Generator
77 77 | from typing import Callable
78 78 | from typing import cast
79 79 |
77 77 |
78 78 | # OK
79 79 | from a import b
UP035.py:77:1: UP035 [*] Import from `collections.abc` instead: `Callable`
UP035.py:88:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
75 | from typing import Reversible
76 | from typing import Generator
77 | from typing import Callable
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
78 | from typing import cast
|
= help: Import from `collections.abc`
Suggested fix
74 74 | from typing import AsyncGenerator
75 75 | from typing import Reversible
76 76 | from typing import Generator
77 |-from typing import Callable
77 |+from collections.abc import Callable
78 78 | from typing import cast
79 79 |
80 80 | # OK
UP035.py:87:1: UP035 [*] Import from `typing` instead: `NamedTuple`
|
86 | # Ok: `typing_extensions` contains backported improvements.
87 | from typing_extensions import NamedTuple
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
88 |
89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
= help: Import from `typing`
Suggested fix
84 84 | from typing_extensions import SupportsIndex
85 85 |
86 86 | # Ok: `typing_extensions` contains backported improvements.
87 |-from typing_extensions import NamedTuple
87 |+from typing import NamedTuple
88 88 |
89 89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 90 | from typing_extensions import dataclass_transform
UP035.py:90:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 | from typing_extensions import dataclass_transform
87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
88 | from typing_extensions import dataclass_transform
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
|
= help: Import from `typing`
Suggested fix
87 87 | from typing_extensions import NamedTuple
88 88 |
89 89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 |-from typing_extensions import dataclass_transform
90 |+from typing import dataclass_transform
85 85 | from typing_extensions import NamedTuple
86 86 |
87 87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
88 |-from typing_extensions import dataclass_transform
88 |+from typing import dataclass_transform

View File

@@ -69,7 +69,7 @@ UP040.py:14:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of t
14 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
15 |
16 | # UP040 bounded generic
16 | # UP040 bounded generic (todo)
|
= help: Use the `type` keyword
@@ -80,154 +80,153 @@ UP040.py:14:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of t
14 |-x: typing.TypeAlias = list[T]
14 |+type x[T] = list[T]
15 15 |
16 16 | # UP040 bounded generic
16 16 | # UP040 bounded generic (todo)
17 17 | T = typing.TypeVar("T", bound=int)
UP040.py:18:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
16 | # UP040 bounded generic
16 | # UP040 bounded generic (todo)
17 | T = typing.TypeVar("T", bound=int)
18 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
19 |
20 | # UP040 constrained generic
20 | T = typing.TypeVar("T", int, str)
|
= help: Use the `type` keyword
Fix
15 15 |
16 16 | # UP040 bounded generic
16 16 | # UP040 bounded generic (todo)
17 17 | T = typing.TypeVar("T", bound=int)
18 |-x: typing.TypeAlias = list[T]
18 |+type x[T: int] = list[T]
18 |+type x = list[T]
19 19 |
20 20 | # UP040 constrained generic
21 21 | T = typing.TypeVar("T", int, str)
20 20 | T = typing.TypeVar("T", int, str)
21 21 | x: typing.TypeAlias = list[T]
UP040.py:22:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:21:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
20 | # UP040 constrained generic
21 | T = typing.TypeVar("T", int, str)
22 | x: typing.TypeAlias = list[T]
20 | T = typing.TypeVar("T", int, str)
21 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
23 |
24 | # UP040 contravariant generic
22 |
23 | # UP040 contravariant generic (todo)
|
= help: Use the `type` keyword
Fix
18 18 | x: typing.TypeAlias = list[T]
19 19 |
20 20 | # UP040 constrained generic
21 21 | T = typing.TypeVar("T", int, str)
22 |-x: typing.TypeAlias = list[T]
22 |+type x[T: (int, str)] = list[T]
23 23 |
24 24 | # UP040 contravariant generic
25 25 | T = typing.TypeVar("T", contravariant=True)
20 20 | T = typing.TypeVar("T", int, str)
21 |-x: typing.TypeAlias = list[T]
21 |+type x = list[T]
22 22 |
23 23 | # UP040 contravariant generic (todo)
24 24 | T = typing.TypeVar("T", contravariant=True)
UP040.py:26:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:25:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
24 | # UP040 contravariant generic
25 | T = typing.TypeVar("T", contravariant=True)
26 | x: typing.TypeAlias = list[T]
23 | # UP040 contravariant generic (todo)
24 | T = typing.TypeVar("T", contravariant=True)
25 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
27 |
28 | # UP040 covariant generic
26 |
27 | # UP040 covariant generic (todo)
|
= help: Use the `type` keyword
Fix
23 23 |
24 24 | # UP040 contravariant generic
25 25 | T = typing.TypeVar("T", contravariant=True)
26 |-x: typing.TypeAlias = list[T]
26 |+type x[T] = list[T]
27 27 |
28 28 | # UP040 covariant generic
29 29 | T = typing.TypeVar("T", covariant=True)
22 22 |
23 23 | # UP040 contravariant generic (todo)
24 24 | T = typing.TypeVar("T", contravariant=True)
25 |-x: typing.TypeAlias = list[T]
25 |+type x = list[T]
26 26 |
27 27 | # UP040 covariant generic (todo)
28 28 | T = typing.TypeVar("T", covariant=True)
UP040.py:30:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:29:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
28 | # UP040 covariant generic
29 | T = typing.TypeVar("T", covariant=True)
30 | x: typing.TypeAlias = list[T]
27 | # UP040 covariant generic (todo)
28 | T = typing.TypeVar("T", covariant=True)
29 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
31 |
32 | # UP040 in class scope
30 |
31 | # UP040 in class scope
|
= help: Use the `type` keyword
Fix
27 27 |
28 28 | # UP040 covariant generic
29 29 | T = typing.TypeVar("T", covariant=True)
30 |-x: typing.TypeAlias = list[T]
30 |+type x[T] = list[T]
31 31 |
32 32 | # UP040 in class scope
33 33 | T = typing.TypeVar["T"]
26 26 |
27 27 | # UP040 covariant generic (todo)
28 28 | T = typing.TypeVar("T", covariant=True)
29 |-x: typing.TypeAlias = list[T]
29 |+type x = list[T]
30 30 |
31 31 | # UP040 in class scope
32 32 | T = typing.TypeVar["T"]
UP040.py:36:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:35:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
34 | class Foo:
35 | # reference to global variable
36 | x: typing.TypeAlias = list[T]
33 | class Foo:
34 | # reference to global variable
35 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
37 |
38 | # reference to class variable
36 |
37 | # reference to class variable
|
= help: Use the `type` keyword
Fix
33 33 | T = typing.TypeVar["T"]
34 34 | class Foo:
35 35 | # reference to global variable
36 |- x: typing.TypeAlias = list[T]
36 |+ type x[T] = list[T]
37 37 |
38 38 | # reference to class variable
39 39 | TCLS = typing.TypeVar["TCLS"]
32 32 | T = typing.TypeVar["T"]
33 33 | class Foo:
34 34 | # reference to global variable
35 |- x: typing.TypeAlias = list[T]
35 |+ type x[T] = list[T]
36 36 |
37 37 | # reference to class variable
38 38 | TCLS = typing.TypeVar["TCLS"]
UP040.py:40:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:39:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
38 | # reference to class variable
39 | TCLS = typing.TypeVar["TCLS"]
40 | y: typing.TypeAlias = list[TCLS]
37 | # reference to class variable
38 | TCLS = typing.TypeVar["TCLS"]
39 | y: typing.TypeAlias = list[TCLS]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
41 |
42 | # UP040 wont add generics in fix
40 |
41 | # UP040 wont add generics in fix
|
= help: Use the `type` keyword
Fix
37 37 |
38 38 | # reference to class variable
39 39 | TCLS = typing.TypeVar["TCLS"]
40 |- y: typing.TypeAlias = list[TCLS]
40 |+ type y[TCLS] = list[TCLS]
41 41 |
42 42 | # UP040 wont add generics in fix
43 43 | T = typing.TypeVar(*args)
36 36 |
37 37 | # reference to class variable
38 38 | TCLS = typing.TypeVar["TCLS"]
39 |- y: typing.TypeAlias = list[TCLS]
39 |+ type y[TCLS] = list[TCLS]
40 40 |
41 41 | # UP040 wont add generics in fix
42 42 | T = typing.TypeVar(*args)
UP040.py:44:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:43:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
42 | # UP040 wont add generics in fix
43 | T = typing.TypeVar(*args)
44 | x: typing.TypeAlias = list[T]
41 | # UP040 wont add generics in fix
42 | T = typing.TypeVar(*args)
43 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
45 |
46 | # OK
44 |
45 | # OK
|
= help: Use the `type` keyword
Fix
41 41 |
42 42 | # UP040 wont add generics in fix
43 43 | T = typing.TypeVar(*args)
44 |-x: typing.TypeAlias = list[T]
44 |+type x = list[T]
45 45 |
46 46 | # OK
47 47 | x: TypeAlias
40 40 |
41 41 | # UP040 wont add generics in fix
42 42 | T = typing.TypeVar(*args)
43 |-x: typing.TypeAlias = list[T]
43 |+type x = list[T]
44 44 |
45 45 | # OK
46 46 | x: TypeAlias

View File

@@ -1,36 +0,0 @@
use ruff_python_ast as ast;
use ruff_python_codegen::Generator;
use ruff_text_size::TextRange;
/// Format a code snippet to call `name.method()`.
pub(super) fn generate_method_call(name: &str, method: &str, generator: Generator) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name.method`.
let attr = ast::ExprAttribute {
value: Box::new(var.into()),
attr: ast::Identifier::new(method.to_string(), TextRange::default()),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make it into a call `name.method()`
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
// And finally, turn it into a statement.
let stmt = ast::StmtExpr {
value: Box::new(call.into()),
range: TextRange::default(),
};
generator.stmt(&stmt.into())
}

View File

@@ -1,6 +1,5 @@
//! Rules from [refurb](https://pypi.org/project/refurb/)/
mod helpers;
pub(crate) mod rules;
#[cfg(test)]
@@ -17,7 +16,6 @@ mod tests {
#[test_case(Rule::RepeatedAppend, Path::new("FURB113.py"))]
#[test_case(Rule::DeleteFullSlice, Path::new("FURB131.py"))]
#[test_case(Rule::CheckAndRemoveFromSet, Path::new("FURB132.py"))]
#[test_case(Rule::SliceCopy, Path::new("FURB145.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -12,17 +12,12 @@ use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `set.remove` that can be replaced with `set.discard`.
/// Checks for uses of `set#remove` that can be replaced with `set#discard`.
///
/// ## Why is this bad?
/// If an element should be removed from a set if it is present, it is more
/// succinct and idiomatic to use `discard`.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect sets that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// nums = {123, 456}

View File

@@ -1,13 +1,13 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_codegen::Generator;
use ruff_python_semantic::analyze::typing::{is_dict, is_list};
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::refurb::helpers::generate_method_call;
/// ## What it does
/// Checks for `del` statements that delete the entire slice of a list or
@@ -17,11 +17,6 @@ use crate::rules::refurb::helpers::generate_method_call;
/// It's is faster and more succinct to remove all items via the `clear()`
/// method.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists and dictionaries that are instantiated as
/// literals or annotated with a type annotation.
///
/// ## Example
/// ```python
/// names = {"key": "value"}
@@ -70,7 +65,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
// Fix is only supported for single-target deletions.
if checker.patch(diagnostic.kind.rule()) && delete.targets.len() == 1 {
let replacement = generate_method_call(name, "clear", checker.generator());
let replacement = make_suggestion(name, checker.generator());
diagnostic.set_fix(Fix::suggested(Edit::replacement(
replacement,
delete.start(),
@@ -123,3 +118,38 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a
// Name is needed for the fix suggestion.
Some(name)
}
/// Make fix suggestion for the given name, ie `name.clear()`.
fn make_suggestion(name: &str, generator: Generator) -> String {
// Here we construct `var.clear()`
//
// And start with construction of `var`
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make `var.clear`.
let attr = ast::ExprAttribute {
value: Box::new(var.into()),
attr: ast::Identifier::new("clear".to_string(), TextRange::default()),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make it into a call `var.clear()`
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
// And finally, turn it into a statement.
let stmt = ast::StmtExpr {
value: Box::new(call.into()),
range: TextRange::default(),
};
generator.stmt(&stmt.into())
}

View File

@@ -1,9 +1,7 @@
pub(crate) use check_and_remove_from_set::*;
pub(crate) use delete_full_slice::*;
pub(crate) use repeated_append::*;
pub(crate) use slice_copy::*;
mod check_and_remove_from_set;
mod delete_full_slice;
mod repeated_append;
mod slice_copy;

View File

@@ -21,11 +21,6 @@ use crate::registry::AsRule;
/// a single `extend`. Each `append` resizes the list individually, whereas an
/// `extend` can resize the list once for all elements.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// nums = [1, 2, 3]

View File

@@ -1,109 +0,0 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::refurb::helpers::generate_method_call;
/// ## What it does
/// Checks for unbounded slice expressions to copy a list.
///
/// ## Why is this bad?
/// The `list.copy` method is more readable and consistent with copying other
/// types.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// a = [1, 2, 3]
/// b = a[:]
/// ```
///
/// Use instead:
/// ```python
/// a = [1, 2, 3]
/// b = a.copy()
/// ```
///
/// ## References
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types)
#[violation]
pub struct SliceCopy;
impl Violation for SliceCopy {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Prefer `copy` method over slicing")
}
fn autofix_title(&self) -> Option<String> {
Some("Replace with `copy()`".to_string())
}
}
/// FURB145
pub(crate) fn slice_copy(checker: &mut Checker, subscript: &ast::ExprSubscript) {
if subscript.ctx.is_store() || subscript.ctx.is_del() {
return;
}
let Some(name) = match_list_full_slice(subscript, checker.semantic()) else {
return;
};
let mut diagnostic = Diagnostic::new(SliceCopy, subscript.range());
if checker.patch(diagnostic.kind.rule()) {
let replacement = generate_method_call(name, "copy", checker.generator());
diagnostic.set_fix(Fix::suggested(Edit::replacement(
replacement,
subscript.start(),
subscript.end(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// Matches `obj[:]` where `obj` is a list.
fn match_list_full_slice<'a>(
subscript: &'a ast::ExprSubscript,
semantic: &SemanticModel,
) -> Option<&'a str> {
// Check that it is `obj[:]`.
if !matches!(
subscript.slice.as_ref(),
Expr::Slice(ast::ExprSlice {
lower: None,
upper: None,
step: None,
range: _,
})
) {
return None;
}
let ast::ExprName { id, .. } = subscript.value.as_name_expr()?;
// Check that `obj` is a list.
let scope = semantic.current_scope();
let bindings: Vec<&Binding> = scope
.get_all(id)
.map(|binding_id| semantic.binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return None;
};
if !is_list(binding, semantic) {
return None;
}
Some(id)
}

View File

@@ -1,128 +0,0 @@
---
source: crates/ruff/src/rules/refurb/mod.rs
---
FURB145.py:4:5: FURB145 [*] Prefer `copy` method over slicing
|
3 | # Errors.
4 | a = l[:]
| ^^^^ FURB145
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
|
= help: Replace with `copy()`
Suggested fix
1 1 | l = [1, 2, 3, 4, 5]
2 2 |
3 3 | # Errors.
4 |-a = l[:]
4 |+a = l.copy()
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 7 | m = l[::]
FURB145.py:5:11: FURB145 [*] Prefer `copy` method over slicing
|
3 | # Errors.
4 | a = l[:]
5 | b, c = 1, l[:]
| ^^^^ FURB145
6 | d, e = l[:], 1
7 | m = l[::]
|
= help: Replace with `copy()`
Suggested fix
2 2 |
3 3 | # Errors.
4 4 | a = l[:]
5 |-b, c = 1, l[:]
5 |+b, c = 1, l.copy()
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 8 | l[:]
FURB145.py:6:8: FURB145 [*] Prefer `copy` method over slicing
|
4 | a = l[:]
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
| ^^^^ FURB145
7 | m = l[::]
8 | l[:]
|
= help: Replace with `copy()`
Suggested fix
3 3 | # Errors.
4 4 | a = l[:]
5 5 | b, c = 1, l[:]
6 |-d, e = l[:], 1
6 |+d, e = l.copy(), 1
7 7 | m = l[::]
8 8 | l[:]
9 9 | print(l[:])
FURB145.py:7:5: FURB145 [*] Prefer `copy` method over slicing
|
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
7 | m = l[::]
| ^^^^^ FURB145
8 | l[:]
9 | print(l[:])
|
= help: Replace with `copy()`
Suggested fix
4 4 | a = l[:]
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 |-m = l[::]
7 |+m = l.copy()
8 8 | l[:]
9 9 | print(l[:])
10 10 |
FURB145.py:8:1: FURB145 [*] Prefer `copy` method over slicing
|
6 | d, e = l[:], 1
7 | m = l[::]
8 | l[:]
| ^^^^ FURB145
9 | print(l[:])
|
= help: Replace with `copy()`
Suggested fix
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 |-l[:]
8 |+l.copy()
9 9 | print(l[:])
10 10 |
11 11 | # False negatives.
FURB145.py:9:7: FURB145 [*] Prefer `copy` method over slicing
|
7 | m = l[::]
8 | l[:]
9 | print(l[:])
| ^^^^ FURB145
10 |
11 | # False negatives.
|
= help: Replace with `copy()`
Suggested fix
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 8 | l[:]
9 |-print(l[:])
9 |+print(l.copy())
10 10 |
11 11 | # False negatives.
12 12 | aa = a[:] # Type inference.

View File

@@ -50,7 +50,7 @@ ruff_python_parser = { path = "../ruff_python_parser" }
codspeed = ["codspeed-criterion-compat"]
[target.'cfg(target_os = "windows")'.dev-dependencies]
mimalloc = "0.1.39"
mimalloc = "0.1.34"
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dev-dependencies]
tikv-jemallocator = "0.5.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.290"
version = "0.0.289"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -36,7 +36,7 @@ ruff_text_size = { path = "../ruff_text_size" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
argfile = { version = "0.1.6" }
argfile = { version = "0.1.5" }
bincode = { version = "1.3.3" }
bitflags = { workspace = true }
cachedir = { version = "0.3.0" }
@@ -52,7 +52,7 @@ is-macro = { workspace = true }
itertools = { workspace = true }
itoa = { version = "1.0.6" }
log = { workspace = true }
notify = { version = "6.1.1" }
notify = { version = "5.1.0" }
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
rayon = { version = "1.7.0" }
regex = { workspace = true }
@@ -79,7 +79,7 @@ test-case = { workspace = true }
ureq = { version = "2.6.2", features = [] }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = "0.1.39"
mimalloc = "0.1.34"
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
tikv-jemallocator = "0.5.0"

View File

@@ -1,16 +1,17 @@
use std::path::PathBuf;
use std::str::FromStr;
use clap::{command, Parser};
use regex::Regex;
use ruff::line_width::LineLength;
use rustc_hash::FxHashMap;
use ruff::line_width::LineLength;
use ruff::logging::LogLevel;
use ruff::registry::Rule;
use ruff::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
};
use ruff::{RuleSelector, RuleSelectorParser};
use ruff::RuleSelector;
use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::resolver::ConfigProcessor;
@@ -128,7 +129,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -138,7 +139,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -148,7 +149,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -158,7 +159,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide = true
)]
@@ -190,7 +191,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -200,7 +201,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -210,7 +211,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -220,7 +221,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide = true
)]
@@ -510,6 +511,11 @@ impl FormatCommand {
}
}
fn parse_rule_selector(env: &str) -> Result<RuleSelector, std::io::Error> {
RuleSelector::from_str(env)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),

View File

@@ -211,16 +211,16 @@ fn lint_path(
match result {
Ok(inner) => inner,
Err(error) => {
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
let message = r#"This indicates a bug in `ruff`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
"#;
error!(
warn!(
"{}{}{} {message}\n{error}",
"Panicked while linting ".bold(),
"Linting panicked ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);

View File

@@ -6,7 +6,6 @@ use std::time::Instant;
use anyhow::Result;
use colored::Colorize;
use log::error;
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use thiserror::Error;
@@ -23,7 +22,6 @@ use ruff_source_file::{find_newline, LineEnding};
use ruff_workspace::resolver::python_files_in_path;
use crate::args::{FormatArguments, Overrides};
use crate::panic::{catch_unwind, PanicError};
use crate::resolve::resolve;
use crate::ExitStatus;
@@ -87,13 +85,7 @@ pub(crate) fn format(
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)))
.with_preview(preview);
debug!("Formatting {} with {:?}", path.display(), options);
Some(match catch_unwind(|| format_path(path, options, mode)) {
Ok(inner) => inner,
Err(error) => {
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
}
})
Some(format_path(path, options, mode))
}
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
}
@@ -103,20 +95,22 @@ pub(crate) fn format(
Err(err) => Right(err),
});
let duration = start.elapsed();
debug!(
"Formatted {} files in {:.2?}",
results.len() + errors.len(),
duration
);
// Report on any errors.
for error in &errors {
error!("{error}");
}
let summary = FormatResultSummary::new(results, mode);
// Report on any errors.
if !errors.is_empty() {
warn!("Encountered {} errors while formatting:", errors.len());
for error in &errors {
warn!("{error}");
}
}
// Report on the formatting changes.
if log_level >= LogLevel::Default {
#[allow(clippy::print_stdout)]
@@ -261,7 +255,6 @@ pub(crate) enum FormatCommandError {
Read(Option<PathBuf>, io::Error),
Write(Option<PathBuf>, io::Error),
FormatModule(Option<PathBuf>, FormatModuleError),
Panic(Option<PathBuf>, PanicError),
}
impl Display for FormatCommandError {
@@ -327,29 +320,6 @@ impl Display for FormatCommandError {
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
}
}
Self::Panic(path, err) => {
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BFormatter%20panic%5D
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
"#;
if let Some(path) = path {
write!(
f,
"{}{}{} {message}\n{err}",
"Panicked while formatting ".bold(),
fs::relativize_path(path).bold(),
":".bold()
)
} else {
write!(
f,
"{} {message}\n{err}",
"Panicked while formatting.".bold()
)
}
}
}
}
}

View File

@@ -111,15 +111,13 @@ pub fn run(
{
eprintln!(
r#"
{}{} {} If you could open an issue at:
{}: `ruff` crashed. This indicates a bug in `ruff`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BPanic%5D
https://github.com/astral-sh/ruff/issues/new?title=%5BPanic%5D
...quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative!
quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative!
"#,
"error".red().bold(),
":".bold(),
"Ruff crashed.".bold(),
);
}
default_panic_hook(info);

View File

@@ -301,178 +301,6 @@ fn nursery_direct() {
Found 1 error.
----- stderr -----
warning: Selection of nursery rule `E225` without the `--preview` flag is deprecated.
"###);
}
#[test]
fn nursery_group_selector() {
// Only nursery rules should be detected e.g. E225 and a warning should be displayed
let args = ["--select", "NURSERY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated. Use the `--preview` flag instead.
"###);
}
#[test]
fn nursery_group_selector_preview_enabled() {
// Only nursery rules should be detected e.g. E225 and a warning should be displayed
let args = ["--select", "NURSERY", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated.
"###);
}
#[test]
fn preview_enabled_prefix() {
// E741 and E225 (preview) should both be detected
let args = ["--select", "E", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
----- stderr -----
"###);
}
#[test]
fn preview_enabled_all() {
let args = ["--select", "ALL", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:1: D100 Missing docstring in public module
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
Found 4 errors.
----- stderr -----
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
"###);
}
#[test]
fn preview_enabled_direct() {
// E225 should be detected without warning
let args = ["--select", "E225", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:2: E225 Missing whitespace around operator
Found 1 error.
----- stderr -----
"###);
}
#[test]
fn preview_disabled_direct() {
// FURB145 is preview not nursery so selecting should be empty
let args = ["--select", "FURB145"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("a = l[:]\n"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `FURB145` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_disabled_prefix_empty() {
// Warns that the selection is empty since all of the CPY rules are in preview
let args = ["--select", "CPY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `CPY` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_group_selector() {
// `--select PREVIEW` should error (selector was removed)
let args = ["--select", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'PREVIEW' for '--select <RULE_CODE>'
For more information, try '--help'.
"###);
}
#[test]
fn preview_enabled_group_ignore() {
// `--select E --ignore PREVIEW` should detect E741 and E225, which is in preview but "E" is more specific.
let args = ["--select", "E", "--ignore", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'PREVIEW' for '--ignore <RULE_CODE>'
For more information, try '--help'.
"###);
}

View File

@@ -25,7 +25,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
}
AutofixKind::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
};
let preview_token = if rule.is_preview() || rule.is_nursery() {
let preview_token = if rule.is_preview() {
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
} else {
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")

View File

@@ -361,7 +361,7 @@ where
f.write_element(FormatElement::Text {
text: self.text.to_string().into_boxed_str(),
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
text_width: TextWidth::from_text(self.text, f.options().tab_width()),
});
Ok(())
@@ -393,10 +393,8 @@ where
let slice = source_code.slice(self.range);
debug_assert_no_newlines(slice.text(source_code));
let text_width = TextWidth::from_text(
slice.text(source_code),
f.context().options().indent_width(),
);
let text_width =
TextWidth::from_text(slice.text(source_code), f.context().options().tab_width());
f.write_element(FormatElement::SourceCodeSlice { slice, text_width });
@@ -919,10 +917,8 @@ where
/// use ruff_formatter::prelude::*;
///
/// # fn main() -> FormatResult<()> {
/// use ruff_formatter::IndentWidth;
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
/// indent_style: IndentStyle::Space,
/// indent_width: IndentWidth::try_from(4).unwrap(),
/// indent_style: IndentStyle::Space(4),
/// ..SimpleFormatOptions::default()
/// });
///

View File

@@ -10,7 +10,7 @@ use unicode_width::UnicodeWidthChar;
use crate::format_element::tag::{GroupMode, LabelId, Tag};
use crate::source_code::SourceCodeSlice;
use crate::{IndentWidth, TagKind};
use crate::{TabWidth, TagKind};
use ruff_text_size::TextSize;
/// Language agnostic IR for formatting source code.
@@ -432,12 +432,12 @@ pub enum TextWidth {
}
impl TextWidth {
pub fn from_text(text: &str, indent_width: IndentWidth) -> TextWidth {
pub fn from_text(text: &str, tab_width: TabWidth) -> TextWidth {
let mut width = 0u32;
for c in text.chars() {
let char_width = match c {
'\t' => indent_width.value(),
'\t' => tab_width.value(),
'\n' => return TextWidth::Multiline,
#[allow(clippy::cast_possible_truncation)]
c => c.width().unwrap_or(0) as u32,

View File

@@ -9,7 +9,7 @@ use crate::prelude::*;
use crate::source_code::SourceCode;
use crate::{
format, write, BufferExtensions, Format, FormatContext, FormatElement, FormatOptions,
FormatResult, Formatter, IndentStyle, IndentWidth, LineWidth, PrinterOptions,
FormatResult, Formatter, IndentStyle, LineWidth, PrinterOptions, TabWidth,
};
use super::tag::Tag;
@@ -213,11 +213,11 @@ struct IrFormatOptions;
impl FormatOptions for IrFormatOptions {
fn indent_style(&self) -> IndentStyle {
IndentStyle::Space
IndentStyle::Space(2)
}
fn indent_width(&self) -> IndentWidth {
IndentWidth::default()
fn tab_width(&self) -> TabWidth {
TabWidth::default()
}
fn line_width(&self) -> LineWidth {
@@ -227,7 +227,7 @@ impl FormatOptions for IrFormatOptions {
fn as_print_options(&self) -> PrinterOptions {
PrinterOptions {
line_width: self.line_width(),
indent_style: IndentStyle::Space,
indent_style: IndentStyle::Space(2),
..PrinterOptions::default()
}
}

View File

@@ -52,20 +52,23 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
pub use group_id::GroupId;
use ruff_text_size::{TextRange, TextSize};
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum IndentStyle {
/// Use tabs to indent code.
/// Tab
#[default]
Tab,
/// Use [`IndentWidth`] spaces to indent code.
Space,
/// Space, with its quantity
Space(u8),
}
impl IndentStyle {
pub const DEFAULT_SPACES: u8 = 2;
/// Returns `true` if this is an [`IndentStyle::Tab`].
pub const fn is_tab(&self) -> bool {
matches!(self, IndentStyle::Tab)
@@ -73,42 +76,58 @@ impl IndentStyle {
/// Returns `true` if this is an [`IndentStyle::Space`].
pub const fn is_space(&self) -> bool {
matches!(self, IndentStyle::Space)
matches!(self, IndentStyle::Space(_))
}
}
impl FromStr for IndentStyle {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"tab" | "Tabs" => Ok(Self::Tab),
"space" | "Spaces" => Ok(Self::Space(IndentStyle::DEFAULT_SPACES)),
// TODO: replace this error with a diagnostic
v => {
let v = v.strip_prefix("Spaces, size: ").unwrap_or(v);
u8::from_str(v)
.map(Self::Space)
.map_err(|_| "Value not supported for IndentStyle")
}
}
}
}
impl std::fmt::Display for IndentStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IndentStyle::Tab => std::write!(f, "tab"),
IndentStyle::Space => std::write!(f, "space"),
IndentStyle::Tab => std::write!(f, "Tab"),
IndentStyle::Space(size) => std::write!(f, "Spaces, size: {size}"),
}
}
}
/// The visual width of a indentation.
///
/// Determines the visual width of a tab character (`\t`) and the number of
/// spaces per indent when using [`IndentStyle::Space`].
/// The visual width of a `\t` character.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndentWidth(NonZeroU8);
pub struct TabWidth(NonZeroU8);
impl IndentWidth {
impl TabWidth {
/// Return the numeric value for this [`LineWidth`]
pub const fn value(&self) -> u32 {
self.0.get() as u32
}
}
impl Default for IndentWidth {
impl Default for TabWidth {
fn default() -> Self {
Self(NonZeroU8::new(2).unwrap())
}
}
impl TryFrom<u8> for IndentWidth {
impl TryFrom<u8> for TabWidth {
type Error = TryFromIntError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
@@ -177,8 +196,16 @@ pub trait FormatOptions {
/// The indent style.
fn indent_style(&self) -> IndentStyle;
/// The visual width of a tab character.
fn tab_width(&self) -> TabWidth;
/// The visual width of an indent
fn indent_width(&self) -> IndentWidth;
fn indent_width(&self) -> u32 {
match self.indent_style() {
IndentStyle::Tab => self.tab_width().value(),
IndentStyle::Space(spaces) => u32::from(spaces),
}
}
/// What's the max width of a line. Defaults to 80.
fn line_width(&self) -> LineWidth;
@@ -223,7 +250,6 @@ impl FormatContext for SimpleFormatContext {
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct SimpleFormatOptions {
pub indent_style: IndentStyle,
pub indent_width: IndentWidth,
pub line_width: LineWidth,
}
@@ -232,8 +258,8 @@ impl FormatOptions for SimpleFormatOptions {
self.indent_style
}
fn indent_width(&self) -> IndentWidth {
self.indent_width
fn tab_width(&self) -> TabWidth {
TabWidth::default()
}
fn line_width(&self) -> LineWidth {
@@ -244,7 +270,6 @@ impl FormatOptions for SimpleFormatOptions {
PrinterOptions {
line_width: self.line_width,
indent_style: self.indent_style,
indent_width: self.indent_width,
source_map_generation: SourceMapGeneration::Enabled,
..PrinterOptions::default()
}

View File

@@ -367,7 +367,7 @@ impl<'a> Printer<'a> {
if !self.state.pending_indent.is_empty() {
let (indent_char, repeat_count) = match self.options.indent_style() {
IndentStyle::Tab => ('\t', 1),
IndentStyle::Space => (' ', self.options.indent_width()),
IndentStyle::Space(count) => (' ', count),
};
let indent = std::mem::take(&mut self.state.pending_indent);
@@ -764,7 +764,7 @@ impl<'a> Printer<'a> {
#[allow(clippy::cast_possible_truncation)]
let char_width = if char == '\t' {
self.options.indent_width.value()
self.options.tab_width.value()
} else {
// SAFETY: A u32 is sufficient to represent the width of a file <= 4GB
char.width().unwrap_or(0) as u32
@@ -1347,7 +1347,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
} else {
for c in text.chars() {
let char_width = match c {
'\t' => self.options().indent_width.value(),
'\t' => self.options().tab_width.value(),
'\n' => {
if self.must_be_flat {
return Fits::No;
@@ -1501,7 +1501,7 @@ mod tests {
use crate::printer::{LineEnding, Printer, PrinterOptions};
use crate::source_code::SourceCode;
use crate::{
format_args, write, Document, FormatState, IndentStyle, IndentWidth, LineWidth, Printed,
format_args, write, Document, FormatState, IndentStyle, LineWidth, Printed, TabWidth,
VecBuffer,
};
@@ -1509,7 +1509,7 @@ mod tests {
format_with_options(
root,
PrinterOptions {
indent_style: IndentStyle::Space,
indent_style: IndentStyle::Space(2),
..PrinterOptions::default()
},
)
@@ -1653,7 +1653,7 @@ two lines`,
fn it_use_the_indent_character_specified_in_the_options() {
let options = PrinterOptions {
indent_style: IndentStyle::Tab,
indent_width: IndentWidth::try_from(4).unwrap(),
tab_width: TabWidth::try_from(4).unwrap(),
line_width: LineWidth::try_from(19).unwrap(),
..PrinterOptions::default()
};

View File

@@ -1,13 +1,10 @@
use crate::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
use crate::{FormatOptions, IndentStyle, LineWidth, TabWidth};
/// Options that affect how the [`crate::Printer`] prints the format tokens
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct PrinterOptions {
/// Width of a single tab character (does it equal 2, 4, ... spaces?)
pub indent_width: IndentWidth,
/// Whether the printer should use tabs or spaces to indent code.
pub indent_style: IndentStyle,
pub tab_width: TabWidth,
/// What's the max width of a line. Defaults to 80
pub line_width: LineWidth,
@@ -15,6 +12,9 @@ pub struct PrinterOptions {
/// The type of line ending to apply to the printed input
pub line_ending: LineEnding,
/// Whether the printer should use tabs or spaces to indent code and if spaces, by how many.
pub indent_style: IndentStyle,
/// Whether the printer should build a source map that allows mapping positions in the source document
/// to positions in the formatted document.
pub source_map_generation: SourceMapGeneration,
@@ -46,8 +46,8 @@ impl PrinterOptions {
}
#[must_use]
pub fn with_tab_width(mut self, width: IndentWidth) -> Self {
self.indent_width = width;
pub fn with_tab_width(mut self, width: TabWidth) -> Self {
self.tab_width = width;
self
}
@@ -58,7 +58,10 @@ impl PrinterOptions {
/// Width of an indent in characters.
pub(super) const fn indent_width(&self) -> u32 {
self.indent_width.value()
match self.indent_style {
IndentStyle::Tab => self.tab_width.value(),
IndentStyle::Space(count) => count as u32,
}
}
}

View File

@@ -22,7 +22,7 @@ itertools = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { version = "3.0.0", default-features = false, features = ["macros"] }
serde_with = { version = "3.0.0" }
thiserror = { workspace = true }
uuid = { workspace = true }

View File

@@ -1,23 +1,25 @@
//! An equivalent object hierarchy to the `RustPython` AST hierarchy, but with the
//! ability to compare expressions for equality (via [`Eq`] and [`Hash`]).
//!
//! Two [`ComparableExpr`]s are considered equal if the underlying AST nodes have the
//! same shape, ignoring trivia (e.g., parentheses, comments, and whitespace), the
//! location in the source code, and other contextual information (e.g., whether they
//! represent reads or writes, which is typically encoded in the Python AST).
//!
//! For example, in `[(a, b) for a, b in c]`, the `(a, b)` and `a, b` expressions are
//! considered equal, despite the former being parenthesized, and despite the former
//! being a write ([`ast::ExprContext::Store`]) and the latter being a read
//! ([`ast::ExprContext::Load`]).
//!
//! Similarly, `"a" "b"` and `"ab"` would be considered equal, despite the former being
//! an implicit concatenation of string literals, as these expressions are considered to
//! have the same shape in that they evaluate to the same value.
use num_bigint::BigInt;
use crate as ast;
use num_bigint::BigInt;
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum ComparableExprContext {
Load,
Store,
Del,
}
impl From<&ast::ExprContext> for ComparableExprContext {
fn from(ctx: &ast::ExprContext) -> Self {
match ctx {
ast::ExprContext::Load => Self::Load,
ast::ExprContext::Store => Self::Store,
ast::ExprContext::Del => Self::Del,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum ComparableBoolOp {
@@ -663,32 +665,38 @@ pub struct ExprConstant<'a> {
pub struct ExprAttribute<'a> {
value: Box<ComparableExpr<'a>>,
attr: &'a str,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprSubscript<'a> {
value: Box<ComparableExpr<'a>>,
slice: Box<ComparableExpr<'a>>,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprStarred<'a> {
value: Box<ComparableExpr<'a>>,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprName<'a> {
id: &'a str,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprList<'a> {
elts: Vec<ComparableExpr<'a>>,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprTuple<'a> {
elts: Vec<ComparableExpr<'a>>,
ctx: ComparableExprContext,
}
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -907,46 +915,50 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
ast::Expr::Attribute(ast::ExprAttribute {
value,
attr,
ctx: _,
ctx,
range: _,
}) => Self::Attribute(ExprAttribute {
value: value.into(),
attr: attr.as_str(),
ctx: ctx.into(),
}),
ast::Expr::Subscript(ast::ExprSubscript {
value,
slice,
ctx: _,
ctx,
range: _,
}) => Self::Subscript(ExprSubscript {
value: value.into(),
slice: slice.into(),
ctx: ctx.into(),
}),
ast::Expr::Starred(ast::ExprStarred {
value,
ctx: _,
ctx,
range: _,
}) => Self::Starred(ExprStarred {
value: value.into(),
ctx: ctx.into(),
}),
ast::Expr::Name(ast::ExprName { id, ctx, range: _ }) => Self::Name(ExprName {
id: id.as_str(),
ctx: ctx.into(),
}),
ast::Expr::Name(ast::ExprName {
id,
ctx: _,
range: _,
}) => Self::Name(ExprName { id: id.as_str() }),
ast::Expr::List(ast::ExprList {
elts,
ctx: _,
ctx,
range: _,
}) => Self::List(ExprList {
elts: elts.iter().map(Into::into).collect(),
ctx: ctx.into(),
}),
ast::Expr::Tuple(ast::ExprTuple {
elts,
ctx: _,
ctx,
range: _,
}) => Self::Tuple(ExprTuple {
elts: elts.iter().map(Into::into).collect(),
ctx: ctx.into(),
}),
ast::Expr::Slice(ast::ExprSlice {
lower,

View File

@@ -1,3 +1,3 @@
{
"indent_width": 4
"tab_width": 8
}

View File

@@ -1,18 +1,22 @@
[
{
"indent_style": "Space",
"indent_width": 4
"indent_style": {
"Space": 4
},
"tab_width": 8
},
{
"indent_style": "Space",
"indent_width": 2
"indent_style": {
"Space": 2
},
"tab_width": 8
},
{
"indent_style": "Tab",
"indent_width": 8
"tab_width": 8
},
{
"indent_style": "Tab",
"indent_width": 4
"tab_width": 4
}
]

View File

@@ -36,7 +36,7 @@ a.aaaaaaaaaaaaaaaaaaaaa.lllllllllllllllllllllllllllloooooooooong.chaaaaaaaaaaaaa
# Test that we add parentheses around the outermost attribute access in an attribute
# chain if and only if we need them, that is if there are own line comments inside
# the chain.
x10 = (
x1 = (
a
.b
# comment 1
@@ -46,27 +46,6 @@ x10 = (
.d
)
x11 = (
a
.b
# comment 1
. # comment 2
# comment 3
c
).d
x12 = (
(
a
.b
# comment 1
. # comment 2
# comment 3
c
)
.d
)
x20 = (
a
.b
@@ -127,7 +106,7 @@ x6 = (
a.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
# regression: https://github.com/astral-sh/ruff/issues/6181
(#
()).a
@@ -146,9 +125,3 @@ x6 = (
# dangling before dot own-line
.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
).bit_length()

View File

@@ -206,11 +206,6 @@ if (
for user_id in set(target_user_ids) - {u.user_id for u in updates}:
updates.append(UserPresenceState.default(user_id))
# If either operator is parenthesized, use non-simple formatting.
# See: https://github.com/astral-sh/ruff/issues/7318.
10**(2)
10**2
# Keeps parenthesized left hand sides
(
log(self.price / self.strike)

View File

@@ -265,8 +265,3 @@ f( # a
kwargs,
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
)()

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