Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c030b5bf3 | ||
|
|
1b082ce67e | ||
|
|
f936d319cc | ||
|
|
85d8b6228f | ||
|
|
7594dadc1d | ||
|
|
de37fbfac9 | ||
|
|
4e2769a16c | ||
|
|
75b5c314e3 | ||
|
|
450fb9b99a | ||
|
|
6163c99551 | ||
|
|
3112202a5b | ||
|
|
64ea00048b | ||
|
|
067a4acd54 | ||
|
|
c88376f468 | ||
|
|
9b7c29853d | ||
|
|
3e21d32b79 | ||
|
|
f9e3ea23ba | ||
|
|
6856d0b44b | ||
|
|
21539f1663 | ||
|
|
b9bb6bf780 | ||
|
|
d39eae2713 | ||
|
|
5d21b9c22e | ||
|
|
ec2f229a45 | ||
|
|
34c1cb7d11 | ||
|
|
2ddea7c657 | ||
|
|
45eabdd2c3 | ||
|
|
675c86c175 | ||
|
|
1f8e2b8f14 | ||
|
|
58b3040342 | ||
|
|
6a9b8aede1 | ||
|
|
f48126ad00 | ||
|
|
11287f944f | ||
|
|
a65efcf459 | ||
|
|
04183b0299 | ||
|
|
36fa1fe359 | ||
|
|
6e625bd93d | ||
|
|
ebd1b296fd | ||
|
|
1373e1c395 | ||
|
|
4bff397318 | ||
|
|
5347df4728 | ||
|
|
ebe9c03545 | ||
|
|
b0cbcd3dfa | ||
|
|
4df9e07a79 | ||
|
|
f0f7ea7502 | ||
|
|
4f26002dd5 | ||
|
|
d1a9c198e3 | ||
|
|
7a4f699fba | ||
|
|
3fb5418c2c | ||
|
|
9fcc009a0c | ||
|
|
bf8e5a167b | ||
|
|
8a001dfc3d | ||
|
|
0823394525 | ||
|
|
e15047815c | ||
|
|
7531bb3b21 | ||
|
|
2d9b39871f | ||
|
|
e122a96d27 | ||
|
|
f4c7bff36b | ||
|
|
56440ad835 | ||
|
|
179128dc54 | ||
|
|
e7a2779402 | ||
|
|
008da95b29 | ||
|
|
5d4dd3e38e | ||
|
|
e561f5783b | ||
|
|
ee0f1270cf | ||
|
|
e7b7e4a18d | ||
|
|
b4419c34ea | ||
|
|
08f19226b9 | ||
|
|
1e6df19a35 | ||
|
|
c21b960fc7 | ||
|
|
73ad2affa1 | ||
|
|
40c936922e | ||
|
|
874db4fb86 | ||
|
|
a41bb2733f | ||
|
|
24b848a4ea | ||
|
|
773ba5f816 | ||
|
|
f5701fcc63 | ||
|
|
ff0feb191c | ||
|
|
6566d00295 |
3
.github/release.yml
vendored
3
.github/release.yml
vendored
@@ -20,6 +20,9 @@ changelog:
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Preview
|
||||
labels:
|
||||
- preview
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
10
.github/workflows/docs.yaml
vendored
10
.github/workflows/docs.yaml
vendored
@@ -2,6 +2,11 @@ name: mkdocs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
|
||||
default: ""
|
||||
type: string
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@@ -13,6 +18,8 @@ jobs:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -44,4 +51,5 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages publish site --project-name=ruff-docs --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||
command: pages deploy site --project-name=ruff-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
34
.github/workflows/release.yaml
vendored
34
.github/workflows/release.yaml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
|
||||
type: string
|
||||
sha:
|
||||
description: "Optionally, the full sha of the commit to be released"
|
||||
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
|
||||
default: ""
|
||||
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 }}
|
||||
@@ -31,6 +34,8 @@ 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 }}
|
||||
@@ -57,6 +62,8 @@ 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 }}
|
||||
@@ -95,6 +102,8 @@ 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 }}
|
||||
@@ -141,6 +150,8 @@ jobs:
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -187,6 +198,8 @@ 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 }}
|
||||
@@ -244,6 +257,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -297,6 +312,8 @@ 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 }}
|
||||
@@ -351,6 +368,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -399,6 +418,8 @@ 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')
|
||||
@@ -410,6 +431,15 @@ 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: |
|
||||
@@ -465,6 +495,8 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- name: git tag
|
||||
run: |
|
||||
git config user.email "hey@astral.sh"
|
||||
|
||||
261
Cargo.lock
generated
261
Cargo.lock
generated
@@ -128,10 +128,11 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638"
|
||||
checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6"
|
||||
dependencies = [
|
||||
"fs-err",
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
@@ -220,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.3.7",
|
||||
"regex-automata 0.3.8",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -271,16 +272,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.28"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
@@ -314,20 +313,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.1"
|
||||
version = "4.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.1"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -378,14 +376,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.0"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
|
||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -610,7 +608,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -621,16 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"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",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -821,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -892,7 +881,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -945,12 +934,6 @@ 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"
|
||||
@@ -1039,17 +1022,6 @@ 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"
|
||||
@@ -1140,15 +1112,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-macro"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
|
||||
checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"pmutil",
|
||||
"pmutil 0.6.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1303,9 +1275,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d"
|
||||
checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -1356,9 +1328,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.2"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -1371,9 +1343,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.38"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127"
|
||||
checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
@@ -1401,7 +1373,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -1446,20 +1418,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.2.0"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1582,18 +1555,18 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43eb3595c63a214e1b37b44f44b0a84900ef7ae0b4c5efce59e123d246d7a0de"
|
||||
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
|
||||
dependencies = [
|
||||
"path-dedot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-dedot"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d55e486337acb9973cdea3ec5638c1b3bcb22e573b2b7b41969e0c744d5a15e"
|
||||
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
@@ -1672,7 +1645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.0.0",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1739,6 +1712,17 @@ 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"
|
||||
@@ -1821,20 +1805,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.6.1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee79feaa9d31e1c417e34219e610b67db4e786ce9b49d77dda549640abb9dc5f"
|
||||
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
@@ -1848,7 +1832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap 2.0.0",
|
||||
"indexmap",
|
||||
"nextest-workspace-hack",
|
||||
"quick-xml",
|
||||
"thiserror",
|
||||
@@ -1956,13 +1940,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.4"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.7",
|
||||
"regex-automata 0.3.8",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
@@ -1977,9 +1961,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2013,7 +1997,7 @@ version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"pmutil 0.5.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@@ -2037,7 +2021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2135,7 +2119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2183,6 +2167,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"tikv-jemallocator",
|
||||
"tracing",
|
||||
@@ -2274,7 +2259,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2303,6 +2288,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -2400,7 +2386,6 @@ dependencies = [
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"static_assertions",
|
||||
"test-case",
|
||||
"tiny-keccak",
|
||||
"unicode-ident",
|
||||
"unicode_names2",
|
||||
@@ -2688,9 +2673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
|
||||
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
@@ -2705,7 +2690,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2721,9 +2706,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2754,15 +2739,8 @@ 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]]
|
||||
@@ -2774,7 +2752,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2797,9 +2775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
@@ -2852,24 +2830,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2885,9 +2863,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.29"
|
||||
version = "2.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2992,22 +2970,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3040,45 +3018,6 @@ 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"
|
||||
@@ -3115,9 +3054,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.6"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3136,11 +3075,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.14"
|
||||
version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -3168,7 +3107,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3371,7 +3310,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3430,20 +3369,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
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"
|
||||
@@ -3471,7 +3404,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3505,7 +3438,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@@ -14,39 +14,39 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.3", 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.2.2" }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
memchr = "2.6.3"
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
regex = { version = "1.9.5" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93" }
|
||||
serde_json = { version = "1.0.106" }
|
||||
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.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
syn = { version = "2.0.15" }
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.33" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
thiserror = { version = "1.0.48" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -1224,6 +1224,31 @@ 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
|
||||
|
||||
@@ -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.288
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -274,6 +274,7 @@ 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/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::registry::Linter;
|
||||
use ruff::settings::types::PreviewMode;
|
||||
use ruff::RuleSelector;
|
||||
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
@@ -331,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
|
||||
.filter(|plugin| {
|
||||
for selector in selectors {
|
||||
if selector
|
||||
.into_iter()
|
||||
.rules(PreviewMode::Disabled)
|
||||
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
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.6.0" }
|
||||
pyproject-toml = { version = "0.7.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
|
||||
@@ -7,6 +7,13 @@ 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
|
||||
|
||||
@@ -7,6 +7,8 @@ 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]
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import logging
|
||||
|
||||
logging.Logger(__name__)
|
||||
logging.Logger()
|
||||
logging.getLogger(__name__)
|
||||
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import logging
|
||||
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
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
|
||||
@@ -658,3 +658,8 @@ 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"
|
||||
|
||||
@@ -529,3 +529,16 @@ 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.
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from typing import override
|
||||
|
||||
|
||||
class Apples:
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
pass
|
||||
@@ -21,6 +24,11 @@ class Apples:
|
||||
# author likely meant to call the invert dunder method
|
||||
pass
|
||||
|
||||
@override
|
||||
def _ignore__(self): # [bad-dunder-name]
|
||||
# overridden dunder methods should be ignored
|
||||
pass
|
||||
|
||||
def hello(self):
|
||||
print("hello")
|
||||
|
||||
|
||||
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
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
|
||||
@@ -74,6 +74,8 @@ 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
|
||||
|
||||
@@ -178,3 +178,9 @@ if True:
|
||||
if True:
|
||||
if sys.version_info > (3, 0): \
|
||||
expected_error = []
|
||||
|
||||
if sys.version_info < (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info <= (3,12):
|
||||
print("py3")
|
||||
|
||||
@@ -13,18 +13,19 @@ x: typing.TypeAlias = list[T]
|
||||
T = typing.TypeVar("T")
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 bounded generic (todo)
|
||||
# UP040 bounded generic
|
||||
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 (todo)
|
||||
# UP040 contravariant generic
|
||||
T = typing.TypeVar("T", contravariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 covariant generic (todo)
|
||||
# UP040 covariant generic
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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]
|
||||
@@ -3,7 +3,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
@@ -92,10 +92,8 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::range)
|
||||
.chain(arguments.keywords.iter().map(Keyword::range))
|
||||
.arguments_source_order()
|
||||
.map(|arg| arg.range())
|
||||
.filter(|range| argument.range() != *range)
|
||||
.partition(|range| range.start() < argument.start());
|
||||
|
||||
|
||||
@@ -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_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,
|
||||
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,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -113,10 +113,12 @@ 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);
|
||||
}
|
||||
@@ -258,6 +260,9 @@ 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);
|
||||
}
|
||||
@@ -324,6 +329,9 @@ 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(
|
||||
@@ -884,6 +892,9 @@ 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,
|
||||
|
||||
@@ -411,6 +411,13 @@ 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);
|
||||
}
|
||||
@@ -1177,7 +1184,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
is_async,
|
||||
..
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -1211,6 +1218,9 @@ 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);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use strum_macros::{AsRefStr, EnumIter};
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use crate::registry::{AsRule, Linter};
|
||||
use crate::rule_selector::is_single_rule_selector;
|
||||
use crate::rules;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -51,7 +52,10 @@ impl PartialEq<&str> for NoqaCode {
|
||||
pub enum RuleGroup {
|
||||
/// The rule has not been assigned to any specific group.
|
||||
Unspecified,
|
||||
/// The rule is still under development, and must be enabled explicitly.
|
||||
/// The rule is unstable, and preview mode must be enabled for usage.
|
||||
Preview,
|
||||
/// Legacy category for unstable rules, supports backwards compatible selection.
|
||||
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
|
||||
Nursery,
|
||||
}
|
||||
|
||||
@@ -64,38 +68,71 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
Some(match (linter, code) {
|
||||
// pycodestyle errors
|
||||
(Pycodestyle, "E101") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MixedSpacesAndTabs),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||
(Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||
@@ -176,6 +213,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
|
||||
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
|
||||
@@ -216,6 +254,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
||||
@@ -228,8 +267,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(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),
|
||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
@@ -403,6 +445,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
|
||||
|
||||
// flake8-copyright
|
||||
#[allow(deprecated)]
|
||||
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
|
||||
// pyupgrade
|
||||
@@ -815,9 +858,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
|
||||
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
|
||||
#[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
|
||||
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
|
||||
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
|
||||
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
||||
@@ -853,6 +898,7 @@ 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),
|
||||
@@ -866,9 +912,17 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
|
||||
|
||||
// refurb
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
#[allow(deprecated)]
|
||||
(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,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use libcst_native::{Expression, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace};
|
||||
use libcst_native::{
|
||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
||||
};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
match expr {
|
||||
@@ -50,3 +52,41 @@ 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![],
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ 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<'_> {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
//!
|
||||
//! [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};
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ 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.
|
||||
@@ -543,8 +540,9 @@ 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,
|
||||
@@ -553,18 +551,17 @@ 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 `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BInfinite%20loop%5D
|
||||
https://github.com/astral-sh/ruff/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
|
||||
);
|
||||
@@ -581,8 +578,9 @@ 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,
|
||||
@@ -591,17 +589,16 @@ 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 `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BAutofix%20error%5D
|
||||
https://github.com/astral-sh/ruff/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(),
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
@@ -65,7 +65,7 @@ impl Emitter for GroupedEmitter {
|
||||
writer,
|
||||
"{}",
|
||||
DisplayGroupedMessage {
|
||||
jupyter_index: context.notebook(message.filename()).map(Notebook::index),
|
||||
notebook_index: context.notebook_index(message.filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
show_source: self.show_source,
|
||||
@@ -92,7 +92,7 @@ struct DisplayGroupedMessage<'a> {
|
||||
show_source: bool,
|
||||
row_length: NonZeroUsize,
|
||||
column_length: NonZeroUsize,
|
||||
jupyter_index: Option<&'a NotebookIndex>,
|
||||
notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for DisplayGroupedMessage<'_> {
|
||||
@@ -110,7 +110,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
)?;
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let (row, col) = if let Some(jupyter_index) = self.jupyter_index {
|
||||
let (row, col) = if let Some(jupyter_index) = self.notebook_index {
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{sep}",
|
||||
@@ -150,7 +150,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index: self.jupyter_index
|
||||
notebook_index: self.notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub use json_lines::JsonLinesEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use text::TextEmitter;
|
||||
@@ -127,21 +127,21 @@ pub trait Emitter {
|
||||
|
||||
/// Context passed to [`Emitter`].
|
||||
pub struct EmitterContext<'a> {
|
||||
notebooks: &'a FxHashMap<String, Notebook>,
|
||||
notebook_indexes: &'a FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl<'a> EmitterContext<'a> {
|
||||
pub fn new(notebooks: &'a FxHashMap<String, Notebook>) -> Self {
|
||||
Self { notebooks }
|
||||
pub fn new(notebook_indexes: &'a FxHashMap<String, NotebookIndex>) -> Self {
|
||||
Self { notebook_indexes }
|
||||
}
|
||||
|
||||
/// Tests if the file with `name` is a jupyter notebook.
|
||||
pub fn is_notebook(&self, name: &str) -> bool {
|
||||
self.notebooks.contains_key(name)
|
||||
self.notebook_indexes.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn notebook(&self, name: &str) -> Option<&Notebook> {
|
||||
self.notebooks.get(name)
|
||||
pub fn notebook_index(&self, name: &str) -> Option<&NotebookIndex> {
|
||||
self.notebook_indexes.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +225,8 @@ def fibonacci(n):
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
) -> String {
|
||||
let source_kinds = FxHashMap::default();
|
||||
let context = EmitterContext::new(&source_kinds);
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -71,14 +71,14 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
let start_location = message.compute_start_location();
|
||||
let jupyter_index = context.notebook(message.filename()).map(Notebook::index);
|
||||
let notebook_index = context.notebook_index(message.filename());
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = jupyter_index {
|
||||
let diagnostic_location = if let Some(notebook_index) = notebook_index {
|
||||
write!(
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
cell = jupyter_index
|
||||
cell = notebook_index
|
||||
.cell(start_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
sep = ":".cyan(),
|
||||
@@ -86,7 +86,7 @@ impl Emitter for TextEmitter {
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
notebook_index
|
||||
.cell_row(start_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
@@ -115,7 +115,7 @@ impl Emitter for TextEmitter {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index
|
||||
notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) jupyter_index: Option<&'a NotebookIndex>,
|
||||
pub(crate) notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
@@ -186,14 +186,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_start_cell = jupyter_index
|
||||
.cell(content_start_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_start_cell = index.cell(content_start_index.get()).unwrap_or_default();
|
||||
while start_index < content_start_index {
|
||||
if jupyter_index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
if index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
break;
|
||||
}
|
||||
start_index = start_index.saturating_add(1);
|
||||
@@ -213,14 +211,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
.saturating_add(2)
|
||||
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_end_cell = jupyter_index
|
||||
.cell(content_end_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_end_cell = index.cell(content_end_index.get()).unwrap_or_default();
|
||||
while end_index > content_end_index {
|
||||
if jupyter_index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
if index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
break;
|
||||
}
|
||||
end_index = end_index.saturating_sub(1);
|
||||
@@ -256,10 +252,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
title: None,
|
||||
slices: vec
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
|
||||
@@ -9,12 +9,14 @@ use crate::codes::RuleCodePrefix;
|
||||
use crate::codes::RuleIter;
|
||||
use crate::registry::{Linter, Rule, RuleNamespace};
|
||||
use crate::rule_redirects::get_redirect;
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum RuleSelector {
|
||||
/// Select all stable rules.
|
||||
/// Select all rules (includes rules in preview if enabled)
|
||||
All,
|
||||
/// Select all nursery rules.
|
||||
/// 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")]
|
||||
Nursery,
|
||||
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
|
||||
/// via a single selector.
|
||||
@@ -29,6 +31,11 @@ pub enum RuleSelector {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
/// Select an individual rule with a given prefix.
|
||||
Rule {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Linter> for RuleSelector {
|
||||
@@ -43,6 +50,7 @@ impl FromStr for RuleSelector {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
@@ -59,16 +67,43 @@ impl FromStr for RuleSelector {
|
||||
return Ok(Self::Linter(linter));
|
||||
}
|
||||
|
||||
Ok(Self::Prefix {
|
||||
prefix: RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?,
|
||||
redirected_from,
|
||||
})
|
||||
// Does the selector select a single rule?
|
||||
let prefix = RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?;
|
||||
|
||||
if is_single_rule_selector(&prefix) {
|
||||
Ok(Self::Rule {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
} else {
|
||||
Ok(Self::Prefix {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`RuleCodePrefix`] matches a single rule exactly
|
||||
/// (e.g., `E225`, as opposed to `E2`).
|
||||
pub(crate) fn is_single_rule_selector(prefix: &RuleCodePrefix) -> bool {
|
||||
let mut rules = prefix.rules();
|
||||
|
||||
// The selector must match a single rule.
|
||||
let Some(rule) = rules.next() else {
|
||||
return false;
|
||||
};
|
||||
if rules.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rule must match the selector exactly.
|
||||
rule.noqa_code().suffix() == prefix.short_code()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error("Unknown rule selector: `{0}`")]
|
||||
@@ -81,10 +116,11 @@ impl RuleSelector {
|
||||
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
RuleSelector::All => ("", "ALL"),
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => ("", "NURSERY"),
|
||||
RuleSelector::C => ("", "C"),
|
||||
RuleSelector::T => ("", "T"),
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
(prefix.linter().common_prefix(), prefix.short_code())
|
||||
}
|
||||
RuleSelector::Linter(l) => (l.common_prefix(), ""),
|
||||
@@ -135,24 +171,13 @@ impl Visitor<'_> for SelectorVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RuleCodePrefix> for RuleSelector {
|
||||
fn from(prefix: RuleCodePrefix) -> Self {
|
||||
Self::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &RuleSelector {
|
||||
type IntoIter = RuleSelectorIter;
|
||||
type Item = Rule;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
impl RuleSelector {
|
||||
/// Return all matching rules, regardless of whether they're in preview.
|
||||
pub fn all_rules(&self) -> impl Iterator<Item = Rule> + '_ {
|
||||
match self {
|
||||
RuleSelector::All => {
|
||||
RuleSelectorIter::All(Rule::iter().filter(|rule| !rule.is_nursery()))
|
||||
}
|
||||
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
|
||||
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => {
|
||||
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
|
||||
}
|
||||
@@ -167,13 +192,28 @@ impl IntoIterator for &RuleSelector {
|
||||
.chain(Linter::Flake8Print.rules()),
|
||||
),
|
||||
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.rules()),
|
||||
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.clone().rules()),
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
RuleSelectorIter::Vec(prefix.clone().rules())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns rules matching the selector, taking into account whether preview mode is enabled.
|
||||
pub fn rules(&self, preview: PreviewMode) -> impl Iterator<Item = Rule> + '_ {
|
||||
#[allow(deprecated)]
|
||||
self.all_rules().filter(move |rule| {
|
||||
// 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())
|
||||
// Enabling preview includes all preview or nursery rules
|
||||
|| preview.is_enabled()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RuleSelectorIter {
|
||||
All(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
All(RuleIter),
|
||||
Nursery(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
|
||||
Vec(std::vec::IntoIter<Rule>),
|
||||
@@ -192,18 +232,6 @@ impl Iterator for RuleSelectorIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
|
||||
/// to let us keep the fields of [`RuleSelector`] private.
|
||||
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
|
||||
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
|
||||
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
|
||||
pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
|
||||
RuleSelector::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use itertools::Itertools;
|
||||
@@ -226,8 +254,9 @@ mod schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
// Include the non-standard "ALL" and "NURSERY" selectors.
|
||||
"ALL".to_string(),
|
||||
"NURSERY".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
@@ -266,18 +295,19 @@ impl RuleSelector {
|
||||
pub fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => Specificity::All,
|
||||
RuleSelector::T => Specificity::LinterGroup,
|
||||
RuleSelector::C => Specificity::LinterGroup,
|
||||
RuleSelector::Linter(..) => Specificity::Linter,
|
||||
RuleSelector::Rule { .. } => Specificity::Rule,
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
let prefix: &'static str = prefix.short_code();
|
||||
match prefix.len() {
|
||||
1 => Specificity::Code1Char,
|
||||
2 => Specificity::Code2Chars,
|
||||
3 => Specificity::Code3Chars,
|
||||
4 => Specificity::Code4Chars,
|
||||
5 => Specificity::Code5Chars,
|
||||
1 => Specificity::Prefix1Char,
|
||||
2 => Specificity::Prefix2Chars,
|
||||
3 => Specificity::Prefix3Chars,
|
||||
4 => Specificity::Prefix4Chars,
|
||||
_ => panic!("RuleSelector::specificity doesn't yet support codes with so many characters"),
|
||||
}
|
||||
}
|
||||
@@ -285,26 +315,35 @@ impl RuleSelector {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||
pub enum Specificity {
|
||||
/// The specificity when selecting all rules (e.g., `--select ALL`).
|
||||
All,
|
||||
/// The specificity when selecting a legacy linter group (e.g., `--select C` or `--select T`).
|
||||
LinterGroup,
|
||||
/// The specificity when selecting a linter (e.g., `--select PLE` or `--select UP`).
|
||||
Linter,
|
||||
Code1Char,
|
||||
Code2Chars,
|
||||
Code3Chars,
|
||||
Code4Chars,
|
||||
Code5Chars,
|
||||
/// The specificity when selecting via a rule prefix with a one-character code (e.g., `--select PLE1`).
|
||||
Prefix1Char,
|
||||
/// The specificity when selecting via a rule prefix with a two-character code (e.g., `--select PLE12`).
|
||||
Prefix2Chars,
|
||||
/// The specificity when selecting via a rule prefix with a three-character code (e.g., `--select PLE123`).
|
||||
Prefix3Chars,
|
||||
/// The specificity when selecting via a rule prefix with a four-character code (e.g., `--select PLE1234`).
|
||||
Prefix4Chars,
|
||||
/// The specificity when selecting an individual rule (e.g., `--select PLE1205`).
|
||||
Rule,
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
mod clap_completion {
|
||||
pub 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,
|
||||
};
|
||||
|
||||
@@ -324,17 +363,29 @@ 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(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
@@ -349,27 +400,34 @@ mod clap_completion {
|
||||
RuleCodePrefix::iter()
|
||||
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
|
||||
// off-by-default
|
||||
.filter(|p| {
|
||||
format!("{}{}", p.linter().common_prefix(), p.short_code())
|
||||
!= "RUF014"
|
||||
.filter(|prefix| {
|
||||
format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
) != "RUF014"
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
|
||||
let mut rules_iter = p.rules();
|
||||
let rule1 = rules_iter.next();
|
||||
let rule2 = rules_iter.next();
|
||||
|
||||
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
|
||||
.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));
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Common Weakness Enumeration: CWE-426](https://cwe.mitre.org/data/definitions/426.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithPartialPath;
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ 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"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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::space;
|
||||
use crate::cst::helpers::{negate, 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(
|
||||
..
|
||||
})
|
||||
) {
|
||||
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.value = negate(&arg.value);
|
||||
}
|
||||
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",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -86,28 +87,16 @@ 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;
|
||||
};
|
||||
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 {
|
||||
if ComparableExpr::from(key) != ComparableExpr::from(target_key) {
|
||||
return;
|
||||
}
|
||||
if target_value.id != value.id {
|
||||
if ComparableExpr::from(value) != ComparableExpr::from(target_value) {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
@@ -126,13 +115,7 @@ pub(crate) fn unnecessary_list_set_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
let Some(elt) = elt.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(target) = generator.target.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if elt.id != target.id {
|
||||
if ComparableExpr::from(elt) != ComparableExpr::from(&generator.target) {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
|
||||
@@ -103,17 +103,18 @@ 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 |
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
|
||||
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))
|
||||
|
|
||||
= 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))
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 | reversed(sorted(x, key=lambda e: e))
|
||||
@@ -122,8 +123,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 |
|
||||
11 11 | def reversed(*args, **kwargs):
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
11 11 | reversed(sorted(x, reverse=not x))
|
||||
|
||||
C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
@@ -131,8 +132,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 |
|
||||
11 | def reversed(*args, **kwargs):
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
11 | reversed(sorted(x, reverse=not x))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
@@ -142,8 +143,87 @@ 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 |
|
||||
11 11 | def reversed(*args, **kwargs):
|
||||
12 12 | return None
|
||||
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):
|
||||
|
||||
|
||||
|
||||
@@ -40,17 +40,18 @@ 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 |
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
|
||||
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()}
|
||||
|
|
||||
= 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()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 |
|
||||
@@ -59,8 +60,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 |
|
||||
11 11 | [i for i, in z]
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
|
|
||||
@@ -68,8 +69,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 |
|
||||
11 | [i for i, in z]
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
@@ -79,23 +80,64 @@ 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 |
|
||||
11 11 | [i for i, in z]
|
||||
12 12 | [i for i, j in y]
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
12 12 |
|
||||
|
||||
C416.py:22:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
||||
C416.py:10:1: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
||||
|
|
||||
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])
|
||||
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])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Suggested fix
|
||||
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))
|
||||
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))
|
||||
|
||||
|
||||
|
||||
27
crates/ruff/src/rules/flake8_logging/mod.rs
Normal file
27
crates/ruff/src/rules/flake8_logging/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! 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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
5
crates/ruff/src/rules/flake8_logging/rules/mod.rs
Normal file
5
crates/ruff/src/rules/flake8_logging/rules/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub(crate) use direct_logger_instantiation::*;
|
||||
pub(crate) use undocumented_warn::*;
|
||||
|
||||
mod direct_logger_instantiation;
|
||||
mod undocumented_warn;
|
||||
@@ -0,0 +1,71 @@
|
||||
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 it’s 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
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__)
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
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
|
||||
|
||||
|
||||
@@ -10,6 +10,24 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate union members.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Duplicate union members are redundant and should be removed.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo: str | str
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// foo: str
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Union`](https://docs.python.org/3/library/typing.html#typing.Union)
|
||||
#[violation]
|
||||
pub struct DuplicateUnionMember {
|
||||
duplicate_name: String,
|
||||
|
||||
@@ -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, UnaryOperation,
|
||||
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace,
|
||||
};
|
||||
|
||||
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::space;
|
||||
use crate::cst::helpers::negate;
|
||||
use crate::cst::matchers::match_indented_block;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::importer::ImportRequest;
|
||||
@@ -567,23 +567,6 @@ 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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ 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;
|
||||
|
||||
@@ -19,6 +19,7 @@ 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(
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
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));
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
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
|
||||
|
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ 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) {
|
||||
|
||||
@@ -63,6 +63,10 @@ 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() {
|
||||
|
||||
@@ -1691,7 +1691,10 @@ fn common_section(
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::NoBlankLineBeforeSection) {
|
||||
if !context.previous_line().is_some_and(str::is_empty) {
|
||||
if !context
|
||||
.previous_line()
|
||||
.is_some_and(|line| line.trim().is_empty())
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NoBlankLineBeforeSection {
|
||||
name: context.section_name().to_string(),
|
||||
|
||||
@@ -97,5 +97,6 @@ D.py:658:5: D204 [*] 1 blank line required after class docstring
|
||||
659 |+
|
||||
659 660 | def sort_services(self):
|
||||
660 661 | pass
|
||||
661 662 |
|
||||
|
||||
|
||||
|
||||
@@ -77,4 +77,13 @@ 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
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -310,4 +310,21 @@ 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."
|
||||
|
||||
|
||||
|
||||
@@ -496,5 +496,6 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters"
|
||||
530 |+ ----------
|
||||
530 531 | ===========
|
||||
531 532 | """
|
||||
532 533 |
|
||||
|
||||
|
||||
|
||||
@@ -292,4 +292,21 @@ 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."
|
||||
|
||||
|
||||
|
||||
@@ -256,4 +256,20 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,12 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for any misspelled dunder name method and for any method
|
||||
/// defined with `_..._` that's not one of the pre-defined methods
|
||||
///
|
||||
/// The pre-defined methods encompass all of Python's standard dunder
|
||||
/// methods.
|
||||
///
|
||||
/// Note this includes all methods starting and ending with at least
|
||||
/// one underscore to detect mistakes.
|
||||
/// Checks for misspelled and unknown dunder names in method definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Misspelled dunder name methods may cause your code to not function
|
||||
@@ -24,6 +18,10 @@ use crate::checkers::ast::Checker;
|
||||
/// that diverges from standard Python dunder methods could potentially
|
||||
/// confuse someone reading the code.
|
||||
///
|
||||
/// This rule will detect all methods starting and ending with at least
|
||||
/// one underscore (e.g., `_str_`), but ignores known dunder methods (like
|
||||
/// `__init__`), as well as methods that are marked with `@override`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
@@ -62,6 +60,9 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt])
|
||||
method.name.starts_with('_') && method.name.ends_with('_')
|
||||
})
|
||||
{
|
||||
if visibility::is_override(&method.decorator_list, checker.semantic()) {
|
||||
continue;
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadDunderMethodName {
|
||||
name: method.name.to_string(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,6 +43,7 @@ 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::*;
|
||||
@@ -101,6 +102,7 @@ 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;
|
||||
|
||||
@@ -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;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::autofix::snippet::SourceCodeSnippet;
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -74,7 +74,8 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
return;
|
||||
}
|
||||
|
||||
let mut value_to_comparators: FxHashMap<HashableExpr, (usize, Vec<&Expr>)> =
|
||||
// Map from expression hash to (starting offset, number of comparisons, list
|
||||
let mut value_to_comparators: FxHashMap<HashableExpr, (TextSize, Vec<&Expr>)> =
|
||||
FxHashMap::with_capacity_and_hasher(
|
||||
bool_op.values.len() * 2,
|
||||
BuildHasherDefault::default(),
|
||||
@@ -95,30 +96,31 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
};
|
||||
|
||||
if matches!(left.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
|
||||
let (left_count, left_matches) = value_to_comparators
|
||||
let (_, left_matches) = value_to_comparators
|
||||
.entry(left.deref().into())
|
||||
.or_insert_with(|| (0, Vec::new()));
|
||||
*left_count += 1;
|
||||
.or_insert_with(|| (left.start(), Vec::new()));
|
||||
left_matches.push(right);
|
||||
}
|
||||
|
||||
if matches!(right, Expr::Name(_) | Expr::Attribute(_)) {
|
||||
let (right_count, right_matches) = value_to_comparators
|
||||
let (_, right_matches) = value_to_comparators
|
||||
.entry(right.into())
|
||||
.or_insert_with(|| (0, Vec::new()));
|
||||
*right_count += 1;
|
||||
.or_insert_with(|| (right.start(), Vec::new()));
|
||||
right_matches.push(left);
|
||||
}
|
||||
}
|
||||
|
||||
for (value, (count, comparators)) in value_to_comparators {
|
||||
if count > 1 {
|
||||
for (value, (_, comparators)) in value_to_comparators
|
||||
.iter()
|
||||
.sorted_by_key(|(_, (start, _))| *start)
|
||||
{
|
||||
if comparators.len() > 1 {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RepeatedEqualityComparison {
|
||||
expression: SourceCodeSnippet::new(merged_membership_test(
|
||||
value.as_expr(),
|
||||
bool_op.op,
|
||||
&comparators,
|
||||
comparators,
|
||||
checker.locator(),
|
||||
)),
|
||||
},
|
||||
|
||||
126
crates/ruff/src/rules/pylint/rules/too_many_public_methods.rs
Normal file
126
crates/ruff/src/rules/pylint/rules/too_many_public_methods.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ pub struct Settings {
|
||||
pub max_returns: usize,
|
||||
pub max_branches: usize,
|
||||
pub max_statements: usize,
|
||||
pub max_public_methods: usize,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -52,6 +53,7 @@ impl Default for Settings {
|
||||
max_returns: 6,
|
||||
max_branches: 12,
|
||||
max_statements: 50,
|
||||
max_public_methods: 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pylint/mod.rs
|
||||
---
|
||||
bad_dunder_method_name.py:2:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:5:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name)
|
||||
|
|
||||
1 | class Apples:
|
||||
2 | def _init_(self): # [bad-dunder-name]
|
||||
4 | class Apples:
|
||||
5 | def _init_(self): # [bad-dunder-name]
|
||||
| ^^^^^^ PLW3201
|
||||
3 | pass
|
||||
6 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:5:9: PLW3201 Bad or misspelled dunder method name `__hello__`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__hello__`. (bad-dunder-name)
|
||||
|
|
||||
3 | pass
|
||||
4 |
|
||||
5 | def __hello__(self): # [bad-dunder-name]
|
||||
6 | pass
|
||||
7 |
|
||||
8 | def __hello__(self): # [bad-dunder-name]
|
||||
| ^^^^^^^^^ PLW3201
|
||||
6 | print("hello")
|
||||
9 | print("hello")
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__init_`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:11:9: PLW3201 Bad or misspelled dunder method name `__init_`. (bad-dunder-name)
|
||||
|
|
||||
6 | print("hello")
|
||||
7 |
|
||||
8 | def __init_(self): # [bad-dunder-name]
|
||||
9 | print("hello")
|
||||
10 |
|
||||
11 | def __init_(self): # [bad-dunder-name]
|
||||
| ^^^^^^^ PLW3201
|
||||
9 | # author likely unintentionally misspelled the correct init dunder.
|
||||
10 | pass
|
||||
12 | # author likely unintentionally misspelled the correct init dunder.
|
||||
13 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:12:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:15:9: PLW3201 Bad or misspelled dunder method name `_init_`. (bad-dunder-name)
|
||||
|
|
||||
10 | pass
|
||||
11 |
|
||||
12 | def _init_(self): # [bad-dunder-name]
|
||||
13 | pass
|
||||
14 |
|
||||
15 | def _init_(self): # [bad-dunder-name]
|
||||
| ^^^^^^ PLW3201
|
||||
13 | # author likely unintentionally misspelled the correct init dunder.
|
||||
14 | pass
|
||||
16 | # author likely unintentionally misspelled the correct init dunder.
|
||||
17 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:16:9: PLW3201 Bad or misspelled dunder method name `___neg__`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:19:9: PLW3201 Bad or misspelled dunder method name `___neg__`. (bad-dunder-name)
|
||||
|
|
||||
14 | pass
|
||||
15 |
|
||||
16 | def ___neg__(self): # [bad-dunder-name]
|
||||
17 | pass
|
||||
18 |
|
||||
19 | def ___neg__(self): # [bad-dunder-name]
|
||||
| ^^^^^^^^ PLW3201
|
||||
17 | # author likely accidentally added an additional `_`
|
||||
18 | pass
|
||||
20 | # author likely accidentally added an additional `_`
|
||||
21 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:20:9: PLW3201 Bad or misspelled dunder method name `__inv__`. (bad-dunder-name)
|
||||
bad_dunder_method_name.py:23:9: PLW3201 Bad or misspelled dunder method name `__inv__`. (bad-dunder-name)
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | def __inv__(self): # [bad-dunder-name]
|
||||
21 | pass
|
||||
22 |
|
||||
23 | def __inv__(self): # [bad-dunder-name]
|
||||
| ^^^^^^^ PLW3201
|
||||
21 | # author likely meant to call the invert dunder method
|
||||
22 | pass
|
||||
24 | # author likely meant to call the invert dunder method
|
||||
25 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
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:
|
||||
|
|
||||
|
||||
|
||||
@@ -138,16 +138,52 @@ 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",
|
||||
@@ -165,6 +201,7 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
|
||||
"ChainMap",
|
||||
"Counter",
|
||||
"Deque",
|
||||
"ForwardRef",
|
||||
"NoReturn",
|
||||
];
|
||||
|
||||
@@ -287,6 +324,18 @@ 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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -60,66 +60,147 @@ impl AlwaysAutofixableViolation for OutdatedVersionBlock {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `BigInt` to a `u32`. If the number is negative, it will return 0.
|
||||
fn bigint_to_u32(number: &BigInt) -> u32 {
|
||||
let the_number = number.to_u32_digits();
|
||||
match the_number.0 {
|
||||
Sign::Minus | Sign::NoSign => 0,
|
||||
Sign::Plus => *the_number.1.first().unwrap(),
|
||||
}
|
||||
}
|
||||
/// UP036
|
||||
pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||
for branch in if_elif_branches(stmt_if) {
|
||||
let Expr::Compare(ast::ExprCompare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
range: _,
|
||||
}) = &branch.test
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
/// Gets the version from the tuple
|
||||
fn extract_version(elts: &[Expr]) -> Vec<u32> {
|
||||
let mut version: Vec<u32> = vec![];
|
||||
for elt in elts {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(item),
|
||||
..
|
||||
}) = &elt
|
||||
let ([op], [comparison]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(left)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "version_info"]))
|
||||
{
|
||||
let number = bigint_to_u32(item);
|
||||
version.push(number);
|
||||
} else {
|
||||
return version;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
version
|
||||
}
|
||||
|
||||
/// Returns true if the `if_version` is less than the `PythonVersion`
|
||||
fn compare_version(if_version: &[u32], py_version: PythonVersion, or_equal: bool) -> bool {
|
||||
let mut if_version_iter = if_version.iter();
|
||||
if let Some(if_major) = if_version_iter.next() {
|
||||
let (py_major, py_minor) = py_version.as_tuple();
|
||||
match if_major.cmp(&py_major) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Equal => {
|
||||
if let Some(if_minor) = if_version_iter.next() {
|
||||
// Check the if_minor number (the minor version).
|
||||
if or_equal {
|
||||
*if_minor <= py_minor
|
||||
} else {
|
||||
*if_minor < py_minor
|
||||
match comparison {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => match op {
|
||||
CmpOp::Lt | CmpOp::LtE => {
|
||||
let version = extract_version(elts);
|
||||
let target = checker.settings.target_version;
|
||||
if compare_version(&version, target, op == &CmpOp::LtE) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_always_false_branch(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
CmpOp::Gt | CmpOp::GtE => {
|
||||
let version = extract_version(elts);
|
||||
let target = checker.settings.target_version;
|
||||
if compare_version(&version, target, op == &CmpOp::GtE) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_always_true_branch(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(number),
|
||||
..
|
||||
}) => {
|
||||
if op == &CmpOp::Eq {
|
||||
match bigint_to_u32(number) {
|
||||
2 => {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) =
|
||||
fix_always_false_branch(checker, stmt_if, &branch)
|
||||
{
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
3 => {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_always_true_branch(checker, stmt_if, &branch)
|
||||
{
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// Assume Python 3.0.
|
||||
true
|
||||
}
|
||||
}
|
||||
Ordering::Greater => false,
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// For fixing, we have 4 cases:
|
||||
/// * Just an if: delete as statement (insert pass in parent if required)
|
||||
/// * If with an elif: delete, turn elif into if
|
||||
/// * If with an else: delete, dedent else
|
||||
/// * Just an elif: delete, `elif False` can always be removed
|
||||
fn fix_py2_block(checker: &Checker, stmt_if: &StmtIf, branch: &IfElifBranch) -> Option<Fix> {
|
||||
/// Returns true if the `target_version` is always less than the [`PythonVersion`].
|
||||
fn compare_version(target_version: &[u32], py_version: PythonVersion, or_equal: bool) -> bool {
|
||||
let mut target_version_iter = target_version.iter();
|
||||
|
||||
let Some(if_major) = target_version_iter.next() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let (py_major, py_minor) = py_version.as_tuple();
|
||||
|
||||
match if_major.cmp(&py_major) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Greater => false,
|
||||
Ordering::Equal => {
|
||||
let Some(if_minor) = target_version_iter.next() else {
|
||||
return true;
|
||||
};
|
||||
if or_equal {
|
||||
// Ex) `sys.version_info <= 3.8`. If Python 3.8 is the minimum supported version,
|
||||
// the condition won't always evaluate to `false`, so we want to return `false`.
|
||||
*if_minor < py_minor
|
||||
} else {
|
||||
// Ex) `sys.version_info < 3.8`. If Python 3.8 is the minimum supported version,
|
||||
// the condition _will_ always evaluate to `false`, so we want to return `true`.
|
||||
*if_minor <= py_minor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fix a branch that is known to always evaluate to `false`.
|
||||
///
|
||||
/// For example, when running with a minimum supported version of Python 3.8, the following branch
|
||||
/// would be considered redundant:
|
||||
/// ```python
|
||||
/// if sys.version_info < (3, 7): ...
|
||||
/// ```
|
||||
///
|
||||
/// In this case, the fix would involve removing the branch; however, there are multiple cases to
|
||||
/// consider. For example, if the `if` has an `else`, then the `if` should be removed, and the
|
||||
/// `else` should be inlined at the top level.
|
||||
fn fix_always_false_branch(
|
||||
checker: &Checker,
|
||||
stmt_if: &StmtIf,
|
||||
branch: &IfElifBranch,
|
||||
) -> Option<Fix> {
|
||||
match branch.kind {
|
||||
BranchKind::If => match stmt_if.elif_else_clauses.first() {
|
||||
// If we have a lone `if`, delete as statement (insert pass in parent if required)
|
||||
@@ -210,8 +291,18 @@ fn fix_py2_block(checker: &Checker, stmt_if: &StmtIf, branch: &IfElifBranch) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`Stmt::If`], removing the `else` block.
|
||||
fn fix_py3_block(checker: &mut Checker, stmt_if: &StmtIf, branch: &IfElifBranch) -> Option<Fix> {
|
||||
/// Fix a branch that is known to always evaluate to `true`.
|
||||
///
|
||||
/// For example, when running with a minimum supported version of Python 3.8, the following branch
|
||||
/// would be considered redundant, as it's known to always evaluate to `true`:
|
||||
/// ```python
|
||||
/// if sys.version_info >= (3, 8): ...
|
||||
/// ```
|
||||
fn fix_always_true_branch(
|
||||
checker: &mut Checker,
|
||||
stmt_if: &StmtIf,
|
||||
branch: &IfElifBranch,
|
||||
) -> Option<Fix> {
|
||||
match branch.kind {
|
||||
BranchKind::If => {
|
||||
// If the first statement is an `if`, use the body of this statement, and ignore
|
||||
@@ -262,85 +353,31 @@ fn fix_py3_block(checker: &mut Checker, stmt_if: &StmtIf, branch: &IfElifBranch)
|
||||
}
|
||||
}
|
||||
|
||||
/// UP036
|
||||
pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||
for branch in if_elif_branches(stmt_if) {
|
||||
let Expr::Compare(ast::ExprCompare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
range: _,
|
||||
}) = &branch.test
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
/// Converts a `BigInt` to a `u32`. If the number is negative, it will return 0.
|
||||
fn bigint_to_u32(number: &BigInt) -> u32 {
|
||||
let the_number = number.to_u32_digits();
|
||||
match the_number.0 {
|
||||
Sign::Minus | Sign::NoSign => 0,
|
||||
Sign::Plus => *the_number.1.first().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
let ([op], [comparison]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(left)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "version_info"]))
|
||||
/// Gets the version from the tuple
|
||||
fn extract_version(elts: &[Expr]) -> Vec<u32> {
|
||||
let mut version: Vec<u32> = vec![];
|
||||
for elt in elts {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(item),
|
||||
..
|
||||
}) = &elt
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match comparison {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
let version = extract_version(elts);
|
||||
let target = checker.settings.target_version;
|
||||
if op == &CmpOp::Lt || op == &CmpOp::LtE {
|
||||
if compare_version(&version, target, op == &CmpOp::LtE) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_py2_block(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
} else if op == &CmpOp::Gt || op == &CmpOp::GtE {
|
||||
if compare_version(&version, target, op == &CmpOp::GtE) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_py3_block(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(number),
|
||||
..
|
||||
}) => {
|
||||
let version_number = bigint_to_u32(number);
|
||||
if version_number == 2 && op == &CmpOp::Eq {
|
||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_py2_block(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
} else if version_number == 3 && op == &CmpOp::Eq {
|
||||
let mut diagnostic = Diagnostic::new(OutdatedVersionBlock, branch.test.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(fix) = fix_py3_block(checker, stmt_if, &branch) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
let number = bigint_to_u32(item);
|
||||
version.push(number);
|
||||
} else {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
version
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -355,8 +392,8 @@ mod tests {
|
||||
#[test_case(PythonVersion::Py37, &[3, 0], true, true; "compare-3.0-whole")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 1], true, true; "compare-3.1")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 5], true, true; "compare-3.5")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 7], true, true; "compare-3.7")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 7], false, false; "compare-3.7-not-equal")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 7], true, false; "compare-3.7")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 7], false, true; "compare-3.7-not-equal")]
|
||||
#[test_case(PythonVersion::Py37, &[3, 8], false , false; "compare-3.8")]
|
||||
#[test_case(PythonVersion::Py310, &[3,9], true, true; "compare-3.9")]
|
||||
#[test_case(PythonVersion::Py310, &[3, 11], true, false; "compare-3.11")]
|
||||
|
||||
@@ -42,13 +42,16 @@ pub struct UnnecessaryEncodeUTF8 {
|
||||
impl AlwaysAutofixableViolation for UnnecessaryEncodeUTF8 {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary call to `encode` as UTF-8")
|
||||
match self.reason {
|
||||
Reason::BytesLiteral => format!("Unnecessary call to `encode` as UTF-8"),
|
||||
Reason::DefaultArgument => format!("Unnecessary UTF-8 `encoding` argument to `encode`"),
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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},
|
||||
@@ -6,20 +8,29 @@ 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.
|
||||
/// 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.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -30,6 +41,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// type ListOfInt = list[int]
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 695]: https://peps.python.org/pep-0695/
|
||||
#[violation]
|
||||
pub struct NonPEP695TypeAlias {
|
||||
name: String,
|
||||
@@ -83,24 +96,36 @@ 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 {
|
||||
names: vec![],
|
||||
vars: vec![],
|
||||
semantic: checker.semantic(),
|
||||
};
|
||||
visitor.visit_expr(value);
|
||||
|
||||
let type_params = if visitor.names.is_empty() {
|
||||
let type_params = if visitor.vars.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ast::TypeParams {
|
||||
range: TextRange::default(),
|
||||
type_params: visitor
|
||||
.names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
.vars
|
||||
.into_iter()
|
||||
.map(|TypeVar { name, restriction }| {
|
||||
TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
name: Identifier::new(name.id.clone(), TextRange::default()),
|
||||
bound: None,
|
||||
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,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
@@ -120,8 +145,22 @@ 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> {
|
||||
names: Vec<&'a ExprName>,
|
||||
vars: Vec<TypeVar<'a>>,
|
||||
semantic: &'a SemanticModel<'a>,
|
||||
}
|
||||
|
||||
@@ -149,16 +188,16 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
|
||||
..
|
||||
}) => {
|
||||
if self.semantic.match_typing_expr(subscript_value, "TypeVar") {
|
||||
self.names.push(name);
|
||||
self.vars.push(TypeVar {
|
||||
name,
|
||||
restriction: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -168,9 +207,18 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
|
||||
})
|
||||
)
|
||||
})
|
||||
&& arguments.keywords.is_empty()
|
||||
{
|
||||
self.names.push(name);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -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 call to `encode` as UTF-8
|
||||
UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
31 | bar = "bar"
|
||||
32 | f"foo{bar}".encode("utf-8")
|
||||
@@ -235,7 +235,7 @@ UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
34 34 | "foo".encode(encoding)
|
||||
35 35 | f"foo{bar}".encode(encoding)
|
||||
|
||||
UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
34 | "foo".encode(encoding)
|
||||
35 | f"foo{bar}".encode(encoding)
|
||||
@@ -258,7 +258,7 @@ UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
UP012.py:53:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
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 call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
55 55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
|
||||
56 56 |
|
||||
|
||||
UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
|
||||
54 | "unicode text©".encode()
|
||||
@@ -301,7 +301,7 @@ UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
72 | )).encode()
|
||||
73 |
|
||||
@@ -480,7 +480,7 @@ UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
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 call to `encode` as UTF-8
|
||||
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 call to `encode` as UTF-8
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
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 call to `encode` as UTF-8
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 call to `encode` as UTF-8
|
||||
76 |+("unicode text©").encode()
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:77:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
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")
|
||||
|
||||
@@ -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 |
|
||||
77 77 | from typing import Callable
|
||||
|
||||
UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
|
||||
|
|
||||
@@ -940,6 +940,7 @@ 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`
|
||||
|
||||
@@ -950,8 +951,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 |
|
||||
78 78 | # OK
|
||||
77 77 | from typing import Callable
|
||||
78 78 | from typing import cast
|
||||
|
||||
UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
|
||||
|
|
||||
@@ -959,8 +960,8 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
|
||||
75 | from typing import Reversible
|
||||
76 | from typing import Generator
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
|
||||
77 |
|
||||
78 | # OK
|
||||
77 | from typing import Callable
|
||||
78 | from typing import cast
|
||||
|
|
||||
= help: Import from `collections.abc`
|
||||
|
||||
@@ -970,23 +971,63 @@ 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 |
|
||||
78 78 | # OK
|
||||
79 79 | from a import b
|
||||
77 77 | from typing import Callable
|
||||
78 78 | from typing import cast
|
||||
79 79 |
|
||||
|
||||
UP035.py:88:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
||||
UP035.py:77:1: UP035 [*] Import from `collections.abc` instead: `Callable`
|
||||
|
|
||||
87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
||||
88 | from typing_extensions import 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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
|
||||
|
|
||||
= help: Import from `typing`
|
||||
|
||||
ℹ Suggested fix
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -662,5 +662,27 @@ UP036_0.py:179:8: UP036 [*] Version block is outdated for minimum Python version
|
||||
178 178 | if True:
|
||||
179 |- if sys.version_info > (3, 0): \
|
||||
180 179 | expected_error = []
|
||||
181 180 |
|
||||
182 181 | if sys.version_info < (3,12):
|
||||
|
||||
UP036_0.py:182:4: UP036 [*] Version block is outdated for minimum Python version
|
||||
|
|
||||
180 | expected_error = []
|
||||
181 |
|
||||
182 | if sys.version_info < (3,12):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
|
||||
183 | print("py3")
|
||||
|
|
||||
= help: Remove outdated version block
|
||||
|
||||
ℹ Suggested fix
|
||||
179 179 | if sys.version_info > (3, 0): \
|
||||
180 180 | expected_error = []
|
||||
181 181 |
|
||||
182 |-if sys.version_info < (3,12):
|
||||
183 |- print("py3")
|
||||
184 182 |
|
||||
185 183 | if sys.version_info <= (3,12):
|
||||
186 184 | print("py3")
|
||||
|
||||
|
||||
|
||||
@@ -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 (todo)
|
||||
16 | # UP040 bounded generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
@@ -80,153 +80,154 @@ 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 (todo)
|
||||
16 16 | # UP040 bounded generic
|
||||
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 (todo)
|
||||
16 | # UP040 bounded generic
|
||||
17 | T = typing.TypeVar("T", bound=int)
|
||||
18 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
19 |
|
||||
20 | T = typing.TypeVar("T", int, str)
|
||||
20 | # UP040 constrained generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
15 15 |
|
||||
16 16 | # UP040 bounded generic (todo)
|
||||
16 16 | # UP040 bounded generic
|
||||
17 17 | T = typing.TypeVar("T", bound=int)
|
||||
18 |-x: typing.TypeAlias = list[T]
|
||||
18 |+type x = list[T]
|
||||
18 |+type x[T: int] = list[T]
|
||||
19 19 |
|
||||
20 20 | T = typing.TypeVar("T", int, str)
|
||||
21 21 | x: typing.TypeAlias = list[T]
|
||||
20 20 | # UP040 constrained generic
|
||||
21 21 | T = typing.TypeVar("T", int, str)
|
||||
|
||||
UP040.py:21:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:22:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
20 | T = typing.TypeVar("T", int, str)
|
||||
21 | x: typing.TypeAlias = list[T]
|
||||
20 | # UP040 constrained generic
|
||||
21 | T = typing.TypeVar("T", int, str)
|
||||
22 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
22 |
|
||||
23 | # UP040 contravariant generic (todo)
|
||||
23 |
|
||||
24 | # UP040 contravariant generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
18 18 | x: typing.TypeAlias = list[T]
|
||||
19 19 |
|
||||
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)
|
||||
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)
|
||||
|
||||
UP040.py:25:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:26:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
23 | # UP040 contravariant generic (todo)
|
||||
24 | T = typing.TypeVar("T", contravariant=True)
|
||||
25 | x: typing.TypeAlias = list[T]
|
||||
24 | # UP040 contravariant generic
|
||||
25 | T = typing.TypeVar("T", contravariant=True)
|
||||
26 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
26 |
|
||||
27 | # UP040 covariant generic (todo)
|
||||
27 |
|
||||
28 | # UP040 covariant generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
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)
|
||||
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)
|
||||
|
||||
UP040.py:29:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:30:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
27 | # UP040 covariant generic (todo)
|
||||
28 | T = typing.TypeVar("T", covariant=True)
|
||||
29 | x: typing.TypeAlias = list[T]
|
||||
28 | # UP040 covariant generic
|
||||
29 | T = typing.TypeVar("T", covariant=True)
|
||||
30 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
30 |
|
||||
31 | # UP040 in class scope
|
||||
31 |
|
||||
32 | # UP040 in class scope
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
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"]
|
||||
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"]
|
||||
|
||||
UP040.py:35:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:36:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
33 | class Foo:
|
||||
34 | # reference to global variable
|
||||
35 | x: typing.TypeAlias = list[T]
|
||||
34 | class Foo:
|
||||
35 | # reference to global variable
|
||||
36 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
36 |
|
||||
37 | # reference to class variable
|
||||
37 |
|
||||
38 | # reference to class variable
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
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"]
|
||||
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"]
|
||||
|
||||
UP040.py:39:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:40:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
37 | # reference to class variable
|
||||
38 | TCLS = typing.TypeVar["TCLS"]
|
||||
39 | y: typing.TypeAlias = list[TCLS]
|
||||
38 | # reference to class variable
|
||||
39 | TCLS = typing.TypeVar["TCLS"]
|
||||
40 | y: typing.TypeAlias = list[TCLS]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
40 |
|
||||
41 | # UP040 wont add generics in fix
|
||||
41 |
|
||||
42 | # UP040 wont add generics in fix
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
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)
|
||||
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)
|
||||
|
||||
UP040.py:43:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:44:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
41 | # UP040 wont add generics in fix
|
||||
42 | T = typing.TypeVar(*args)
|
||||
43 | x: typing.TypeAlias = list[T]
|
||||
42 | # UP040 wont add generics in fix
|
||||
43 | T = typing.TypeVar(*args)
|
||||
44 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
44 |
|
||||
45 | # OK
|
||||
45 |
|
||||
46 | # OK
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
|
||||
36
crates/ruff/src/rules/refurb/helpers.rs
Normal file
36
crates/ruff/src/rules/refurb/helpers.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
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())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Rules from [refurb](https://pypi.org/project/refurb/)/
|
||||
|
||||
mod helpers;
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -16,6 +17,7 @@ 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(
|
||||
|
||||
@@ -12,12 +12,17 @@ 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}
|
||||
|
||||
@@ -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, TextRange};
|
||||
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 `del` statements that delete the entire slice of a list or
|
||||
@@ -17,6 +17,11 @@ use crate::registry::AsRule;
|
||||
/// 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"}
|
||||
@@ -65,7 +70,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 = make_suggestion(name, checker.generator());
|
||||
let replacement = generate_method_call(name, "clear", checker.generator());
|
||||
diagnostic.set_fix(Fix::suggested(Edit::replacement(
|
||||
replacement,
|
||||
delete.start(),
|
||||
@@ -118,38 +123,3 @@ 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())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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;
|
||||
|
||||
@@ -21,6 +21,11 @@ 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]
|
||||
|
||||
109
crates/ruff/src/rules/refurb/rules/slice_copy.rs
Normal file
109
crates/ruff/src/rules/refurb/rules/slice_copy.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::Settings;
|
||||
use crate::codes::{self, RuleCodePrefix};
|
||||
use crate::line_width::{LineLength, TabSize};
|
||||
use crate::registry::Linter;
|
||||
use crate::rule_selector::{prefix_to_selector, RuleSelector};
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::rules::{
|
||||
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
|
||||
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
|
||||
@@ -20,7 +20,10 @@ use crate::rules::{
|
||||
use crate::settings::types::FilePatternSet;
|
||||
|
||||
pub const PREFIXES: &[RuleSelector] = &[
|
||||
prefix_to_selector(RuleCodePrefix::Pycodestyle(codes::Pycodestyle::E)),
|
||||
RuleSelector::Prefix {
|
||||
prefix: RuleCodePrefix::Pycodestyle(codes::Pycodestyle::E),
|
||||
redirected_from: None,
|
||||
},
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
];
|
||||
|
||||
@@ -70,7 +73,10 @@ pub static INCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rules: PREFIXES.iter().flat_map(IntoIterator::into_iter).collect(),
|
||||
rules: PREFIXES
|
||||
.iter()
|
||||
.flat_map(|selector| selector.rules(PreviewMode::default()))
|
||||
.collect(),
|
||||
allowed_confusables: FxHashSet::from_iter([]),
|
||||
builtins: vec![],
|
||||
dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone(),
|
||||
|
||||
@@ -194,7 +194,8 @@ pub struct PerFileIgnore {
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(pattern: String, prefixes: &[RuleSelector], project_root: Option<&Path>) -> Self {
|
||||
let rules: RuleSet = prefixes.iter().flat_map(IntoIterator::into_iter).collect();
|
||||
// Rules in preview are included here even if preview mode is disabled; it's safe to ignore disabled rules
|
||||
let rules: RuleSet = prefixes.iter().flat_map(RuleSelector::all_rules).collect();
|
||||
let path = Path::new(&pattern);
|
||||
let absolute = match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
|
||||
@@ -297,7 +297,7 @@ pub(crate) fn print_jupyter_messages(
|
||||
messages,
|
||||
&EmitterContext::new(&FxHashMap::from_iter([(
|
||||
path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
notebook.clone(),
|
||||
notebook.index().clone(),
|
||||
)])),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -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.34"
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
[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"
|
||||
|
||||
@@ -79,7 +79,7 @@ fn benchmark_default_rules(criterion: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn benchmark_all_rules(criterion: &mut Criterion) {
|
||||
let mut rules: RuleTable = RuleSelector::All.into_iter().collect();
|
||||
let mut rules: RuleTable = RuleSelector::All.all_rules().collect();
|
||||
|
||||
// Disable IO based rules because it is a source of flakiness
|
||||
rules.disable(Rule::ShebangMissingExecutableFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.288"
|
||||
version = "0.0.290"
|
||||
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.5" }
|
||||
argfile = { version = "0.1.6" }
|
||||
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 = "5.1.0" }
|
||||
notify = { version = "6.1.1" }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.7.0" }
|
||||
regex = { workspace = true }
|
||||
@@ -75,10 +75,11 @@ colored = { workspace = true, features = ["no-color"]}
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
tempfile = "3.6.0"
|
||||
test-case = { workspace = true }
|
||||
ureq = { version = "2.6.2", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = "0.1.34"
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
[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"
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
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;
|
||||
use ruff::{RuleSelector, RuleSelectorParser};
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::resolver::ConfigProcessor;
|
||||
|
||||
@@ -116,7 +115,7 @@ pub struct CheckCommand {
|
||||
#[arg(long, value_enum)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Enable preview mode; checks will include unstable rules and fixes.
|
||||
#[arg(long, overrides_with("no_preview"), hide = true)]
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
@@ -129,7 +128,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -139,7 +138,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -149,7 +148,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -159,7 +158,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
@@ -191,7 +190,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -201,7 +200,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -211,7 +210,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -221,7 +220,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
@@ -511,11 +510,6 @@ 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),
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::sync::Mutex;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff::message::Message;
|
||||
@@ -15,6 +16,7 @@ use ruff::settings::Settings;
|
||||
use ruff::warn_user;
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
@@ -193,6 +195,7 @@ impl Cache {
|
||||
key: T,
|
||||
messages: &[Message],
|
||||
imports: &ImportMap,
|
||||
notebook_index: Option<&NotebookIndex>,
|
||||
) {
|
||||
let source = if let Some(msg) = messages.first() {
|
||||
msg.file.source_text().to_owned()
|
||||
@@ -226,6 +229,7 @@ impl Cache {
|
||||
imports: imports.clone(),
|
||||
messages,
|
||||
source,
|
||||
notebook_index: notebook_index.cloned(),
|
||||
};
|
||||
self.new_files.lock().unwrap().insert(path, file);
|
||||
}
|
||||
@@ -263,6 +267,8 @@ pub(crate) struct FileCache {
|
||||
///
|
||||
/// This will be empty if `messages` is empty.
|
||||
source: String,
|
||||
/// Notebook index if this file is a Jupyter Notebook.
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
}
|
||||
|
||||
impl FileCache {
|
||||
@@ -283,7 +289,12 @@ impl FileCache {
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
Diagnostics::new(messages, self.imports.clone())
|
||||
let notebook_indexes = if let Some(notebook_index) = self.notebook_index.as_ref() {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
Diagnostics::new(messages, self.imports.clone(), notebook_indexes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,16 +361,19 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
|
||||
#[test]
|
||||
fn same_results() {
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case("../ruff/resources/test/fixtures", "ruff_tests/cache_same_results_ruff"; "ruff_fixtures")]
|
||||
#[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")]
|
||||
fn same_results(package_root: &str, cache_dir_path: &str) {
|
||||
let mut cache_dir = temp_dir();
|
||||
cache_dir.push("ruff_tests/cache_same_results");
|
||||
cache_dir.push(cache_dir_path);
|
||||
let _ = fs::remove_dir_all(&cache_dir);
|
||||
cache::init(&cache_dir).unwrap();
|
||||
|
||||
let settings = AllSettings::default();
|
||||
|
||||
let package_root = fs::canonicalize("../ruff/resources/test/fixtures").unwrap();
|
||||
let package_root = fs::canonicalize(package_root).unwrap();
|
||||
let cache = Cache::open(&cache_dir, package_root.clone(), &settings.lib);
|
||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
||||
|
||||
@@ -444,9 +458,6 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Not stored in the cache.
|
||||
expected_diagnostics.notebooks.clear();
|
||||
got_diagnostics.notebooks.clear();
|
||||
assert_eq!(expected_diagnostics, got_diagnostics);
|
||||
}
|
||||
|
||||
@@ -614,6 +625,7 @@ mod tests {
|
||||
imports: ImportMap::new(),
|
||||
messages: Vec::new(),
|
||||
source: String::new(),
|
||||
notebook_index: None,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user