Compare commits
49 Commits
zanie/debu
...
malachite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bd83d36ee | ||
|
|
94b68f201b | ||
|
|
ef34c5cbec | ||
|
|
fdbefd777c | ||
|
|
078547adbb | ||
|
|
42a0bec146 | ||
|
|
37b7d0f921 | ||
|
|
6a4dbd622b | ||
|
|
28b48ab902 | ||
|
|
4123d074bd | ||
|
|
c6ba7dfbc6 | ||
|
|
40f6456add | ||
|
|
3e1dffab20 | ||
|
|
3336d23f48 | ||
|
|
2421805033 | ||
|
|
359f50e6dc | ||
|
|
bccba5d73f | ||
|
|
0bfdb15ecf | ||
|
|
a902d14c31 | ||
|
|
728539291f | ||
|
|
c2bd8af59a | ||
|
|
c946bf157e | ||
|
|
8ab2519717 | ||
|
|
c4d85d6fb6 | ||
|
|
70ea49bf72 | ||
|
|
8e255974bc | ||
|
|
d358604464 | ||
|
|
8243db74fe | ||
|
|
0346e781d4 | ||
|
|
b66bfa6570 | ||
|
|
28273eb00b | ||
|
|
12acd191e1 | ||
|
|
64b929bc29 | ||
|
|
26ae0a6e8d | ||
|
|
959338d39d | ||
|
|
422ff82f4a | ||
|
|
8d0a5e01bd | ||
|
|
aae02cf275 | ||
|
|
7e2eba2592 | ||
|
|
22770fb4be | ||
|
|
1880cceac1 | ||
|
|
0d1fb823d6 | ||
|
|
c907317199 | ||
|
|
916dd5b7fa | ||
|
|
2cbe1733c8 | ||
|
|
1f6e1485f9 | ||
|
|
9b43162cc4 | ||
|
|
cc9e84c144 | ||
|
|
f4d50a2aec |
28
.github/workflows/ci.yaml
vendored
28
.github/workflows/ci.yaml
vendored
@@ -366,31 +366,3 @@ jobs:
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
tmp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main # We checkout the main branch to check for the commit
|
||||
- name: Check main branch
|
||||
run: |
|
||||
# Fetch the main branch since a shallow checkout is used by default
|
||||
git fetch origin main --unshallow
|
||||
if ! git branch --contains 0c030b5bf31e425cb6070db7386243eca6dbd8f1 | grep -E '(^|\s)main$'; then
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
# Switch to the commit we want to release
|
||||
git checkout 0c030b5bf31e425cb6070db7386243eca6dbd8f1
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "0.0.290" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "0.0.290" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -52,4 +52,4 @@ jobs:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
# `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}
|
||||
command: pages deploy site --project-name=astral-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -44,4 +44,4 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages publish playground/dist --project-name=ruff --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
command: pages deploy playground/dist --project-name=ruff-playground --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
30
.github/workflows/release.yaml
vendored
30
.github/workflows/release.yaml
vendored
@@ -419,18 +419,7 @@ jobs:
|
||||
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')
|
||||
if [ "${{ inputs.tag }}" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "${{ inputs.tag }}" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
ref: main # We checkout the main branch to check for the commit
|
||||
- name: Check main branch
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
@@ -440,17 +429,18 @@ jobs:
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check SHA consistency
|
||||
if: ${{ inputs.sha }}
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
git_sha=$(git rev-parse HEAD)
|
||||
if [ "${{ inputs.sha }}" != "${git_sha}" ]; then
|
||||
echo "The specified sha does not match the git checkout" >&2
|
||||
echo "${{ inputs.sha }}" >&2
|
||||
echo "${git_sha}" >&2
|
||||
# Switch to the commit we want to release
|
||||
git checkout ${{ inputs.sha }}
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "${{ inputs.tag }}" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "${{ inputs.tag }}" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${git_sha}"
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
|
||||
upload-release:
|
||||
|
||||
@@ -299,4 +299,4 @@ default.
|
||||
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
|
||||
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
|
||||
|
||||
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.
|
||||
See the [documentation](https://docs.astral.sh/ruff/configuration/#python-file-discovery) for more.
|
||||
|
||||
@@ -719,8 +719,8 @@ Module {
|
||||
- `cargo dev generate-cli-help`, `cargo dev generate-docs` and `cargo dev generate-json-schema`:
|
||||
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
|
||||
- `cargo dev generate-options`: Generate a markdown-compatible table of all `pyproject.toml`
|
||||
options. Used for <https://beta.ruff.rs/docs/settings/>
|
||||
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://beta.ruff.rs/docs/rules/>
|
||||
options. Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://docs.astral.sh/ruff/rules/>.
|
||||
- `cargo dev round-trip <python file or jupyter notebook>`: Read a Python file or Jupyter Notebook,
|
||||
parse it, serialize the parsed representation and write it back. Used to check how good our
|
||||
representation is so that fixes don't rewrite irrelevant parts of a file.
|
||||
@@ -778,7 +778,7 @@ To understand Ruff's import categorization system, we first need to define two c
|
||||
- "Package root": The top-most directory defining the Python package that includes a given Python
|
||||
file. To find the package root for a given Python file, traverse up its parent directories until
|
||||
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
|
||||
a [namespace package](https://beta.ruff.rs/docs/settings/#namespace-packages)); take the directory
|
||||
a [namespace package](https://docs.astral.sh/ruff/settings/#namespace-packages)); take the directory
|
||||
just before that, i.e., the first directory in the package.
|
||||
|
||||
For example, given:
|
||||
@@ -867,7 +867,7 @@ There are three ways in which an import can be categorized as "first-party":
|
||||
package (e.g., `from foo import bar` or `import foo.bar`), they'll be classified as first-party
|
||||
automatically. This check is as simple as comparing the first segment of the current file's
|
||||
module path to the first segment of the import.
|
||||
1. **Source roots**: Ruff supports a `[src](https://beta.ruff.rs/docs/settings/#src)` setting, which
|
||||
1. **Source roots**: Ruff supports a `[src](https://docs.astral.sh/ruff/settings/#src)` setting, which
|
||||
sets the directories to scan when identifying first-party imports. The algorithm is
|
||||
straightforward: given an import, like `import foo`, iterate over the directories enumerated in
|
||||
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
|
||||
|
||||
285
Cargo.lock
generated
285
Cargo.lock
generated
@@ -172,6 +172,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.3"
|
||||
@@ -214,6 +220,15 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.6.2"
|
||||
@@ -272,9 +287,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.30"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -313,9 +328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.3"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -323,9 +338,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.2"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -383,7 +398,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -495,6 +510,15 @@ version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
@@ -516,7 +540,7 @@ dependencies = [
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
@@ -535,7 +559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -587,6 +611,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
@@ -608,7 +642,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -619,7 +653,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -634,6 +668,16 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
@@ -720,6 +764,18 @@ version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "embed-doc-image"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af36f591236d9d822425cb6896595658fa558fcebf5ee8accac1d4b92c47166e"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.2"
|
||||
@@ -816,7 +872,7 @@ dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"configparser",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
@@ -872,6 +928,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
@@ -1049,9 +1115,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.3"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4"
|
||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
@@ -1120,7 +1186,7 @@ dependencies = [
|
||||
"pmutil 0.6.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1143,6 +1209,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
@@ -1158,6 +1233,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
@@ -1189,7 +1273,7 @@ dependencies = [
|
||||
"diff",
|
||||
"ena",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
@@ -1311,6 +1395,41 @@ version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "malachite"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9fa232412d927f518cd873073911726943f432bac1bbc1288316d240635dc51"
|
||||
dependencies = [
|
||||
"malachite-base",
|
||||
"malachite-nz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malachite-base"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "606a61b226dc58b8b283399b74754460433c193b193f26eaaad92f7966abd72b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"itertools 0.11.0",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"ryu",
|
||||
"sha3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malachite-nz"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "863d06218c83cc2269c425186cc15094d6284b1333a1184d0890aecfddb8916b"
|
||||
dependencies = [
|
||||
"embed-doc-image",
|
||||
"itertools 0.11.0",
|
||||
"malachite-base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -1445,27 +1564,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
@@ -1720,7 +1818,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1749,7 +1847,7 @@ checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
@@ -2035,12 +2133,12 @@ dependencies = [
|
||||
"imperative",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"libcst",
|
||||
"log",
|
||||
"malachite",
|
||||
"memchr",
|
||||
"natord",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
@@ -2112,7 +2210,7 @@ dependencies = [
|
||||
"filetime",
|
||||
"glob",
|
||||
"globset",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"regex",
|
||||
"ruff_macros",
|
||||
]
|
||||
@@ -2139,7 +2237,7 @@ dependencies = [
|
||||
"insta",
|
||||
"insta-cmd",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"itoa",
|
||||
"log",
|
||||
"mimalloc",
|
||||
@@ -2186,7 +2284,7 @@ dependencies = [
|
||||
"imara-diff",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"libcst",
|
||||
"once_cell",
|
||||
"pretty_assertions",
|
||||
@@ -2255,11 +2353,11 @@ dependencies = [
|
||||
name = "ruff_macros"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2268,7 +2366,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"once_cell",
|
||||
"ruff_diagnostics",
|
||||
"ruff_source_file",
|
||||
@@ -2288,10 +2386,9 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"malachite",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
@@ -2323,7 +2420,7 @@ dependencies = [
|
||||
"clap",
|
||||
"countme",
|
||||
"insta",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"ruff_formatter",
|
||||
@@ -2348,7 +2445,7 @@ dependencies = [
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
@@ -2363,7 +2460,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"lexical-parse-float",
|
||||
"num-traits",
|
||||
"rand",
|
||||
@@ -2377,11 +2474,10 @@ dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"malachite",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -2407,7 +2503,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"is-macro",
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -2430,12 +2525,11 @@ name = "ruff_python_trivia"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"memchr",
|
||||
"itertools 0.11.0",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"smallvec",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -2492,6 +2586,7 @@ dependencies = [
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
@@ -2511,7 +2606,7 @@ dependencies = [
|
||||
"glob",
|
||||
"globset",
|
||||
"ignore",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"path-absolutize",
|
||||
"pep440_rs",
|
||||
@@ -2612,9 +2707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.13"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -2624,9 +2719,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.13"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2690,7 +2785,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2706,9 +2801,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2752,7 +2847,17 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2847,7 +2952,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2863,9 +2968,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.33"
|
||||
version = "2.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2935,36 +3040,36 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "3.1.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a1d6e7bde536b0412f20765b76e921028059adfd1b90d8974d33fd3c91b25df"
|
||||
checksum = "c8f1e820b7f1d95a0cdbf97a5df9de10e1be731983ab943e56703ac1b8e9d425"
|
||||
dependencies = [
|
||||
"test-case-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case-core"
|
||||
version = "3.1.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d10394d5d1e27794f772b6fc854c7e91a2dc26e2cbf807ad523370c2a59c0cee"
|
||||
checksum = "54c25e2cb8f5fcd7318157634e8838aa6f7e4715c96637f969fabaccd1ef5462"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case-macros"
|
||||
version = "3.1.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeb9a44b1c6a54c1ba58b152797739dba2a83ca74e18168a68c980eb142f9404"
|
||||
checksum = "37cfd7bbc88a0104e304229fba519bdc45501a30b760fb72240342f1289ad257"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.37",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2985,7 +3090,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3107,7 +3212,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3167,6 +3272,12 @@ version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
@@ -3217,9 +3328,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -3262,7 +3373,7 @@ version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.21.3",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -3310,7 +3421,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3404,7 +3515,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3438,7 +3549,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
22
Cargo.toml
22
Cargo.toml
@@ -5,8 +5,8 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.71"
|
||||
homepage = "https://beta.ruff.rs/docs"
|
||||
documentation = "https://beta.ruff.rs/docs"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
@@ -14,8 +14,8 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.3", features = ["derive"] }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
@@ -23,10 +23,10 @@ globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.31.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
itertools = { version = "0.11.0" }
|
||||
log = { version = "0.4.17" }
|
||||
malachite = { version = "0.4.0", default-features = false, features = ["naturals_and_integers"] }
|
||||
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.1.1" }
|
||||
@@ -34,23 +34,23 @@ proc-macro2 = { version = "1.0.67" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.9.5" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.12" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.106" }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1", features = ["inline"] }
|
||||
smallvec = { version = "1.10.0" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.33" }
|
||||
test-case = { version = "3.0.0" }
|
||||
syn = { version = "2.0.37" }
|
||||
test-case = { version = "3.2.1" }
|
||||
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"] }
|
||||
unicode-ident = "1.0.11"
|
||||
unicode-ident = "1.0.12"
|
||||
unicode-width = "0.1.10"
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
24
README.md
24
README.md
@@ -8,7 +8,7 @@
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/astral-sh/ruff/actions)
|
||||
|
||||
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
|
||||
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||
|
||||
An extremely fast Python linter, written in Rust.
|
||||
|
||||
@@ -30,13 +30,13 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [600 built-in rules](https://beta.ruff.rs/docs/rules/)
|
||||
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
- 📏 Over [600 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party [editor integrations](https://beta.ruff.rs/docs/editor-integrations/) for
|
||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
functionality behind a single, common interface.
|
||||
@@ -98,7 +98,7 @@ developer of [Zulip](https://github.com/zulip/zulip):
|
||||
|
||||
## Table of Contents
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
For more, see the [documentation](https://docs.astral.sh/ruff/).
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
1. [Configuration](#configuration)
|
||||
@@ -111,7 +111,7 @@ For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
For more, see the [documentation](https://beta.ruff.rs/docs/).
|
||||
For more, see the [documentation](https://docs.astral.sh/ruff/).
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -122,7 +122,7 @@ pip install ruff
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
and with [a variety of other package managers](https://beta.ruff.rs/docs/installation/).
|
||||
and with [a variety of other package managers](https://docs.astral.sh/ruff/installation/).
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
### Configuration
|
||||
|
||||
Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (see:
|
||||
[_Configuration_](https://beta.ruff.rs/docs/configuration/), or [_Settings_](https://beta.ruff.rs/docs/settings/)
|
||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||
for a complete list of all configuration options).
|
||||
|
||||
If left unspecified, the default configuration is equivalent to:
|
||||
@@ -238,7 +238,7 @@ isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implement
|
||||
Rust as a first-party feature.
|
||||
|
||||
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
|
||||
and a [subset](https://beta.ruff.rs/docs/rules/#error-e) of the `E` category, omitting those
|
||||
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
|
||||
stylistic rules made obsolete by the use of an autoformatter, like
|
||||
[Black](https://github.com/psf/black).
|
||||
|
||||
@@ -304,12 +304,12 @@ quality tools, including:
|
||||
- [tryceratops](https://pypi.org/project/tryceratops/)
|
||||
- [yesqa](https://pypi.org/project/yesqa/)
|
||||
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
|
||||
For a complete enumeration of the supported rules, see [_Rules_](https://docs.astral.sh/ruff/rules/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and highly appreciated. To get started, check out the
|
||||
[**contributing guidelines**](https://beta.ruff.rs/docs/contributing/).
|
||||
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
|
||||
|
||||
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
configuration options that don't exist in Flake8.)
|
||||
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
|
||||
codes from unsupported plugins. (See the
|
||||
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
[documentation](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) for the complete
|
||||
list of supported plugins.)
|
||||
|
||||
## License
|
||||
|
||||
@@ -45,9 +45,9 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
malachite = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
|
||||
22
crates/ruff/resources/test/fixtures/flake8_bandit/S201.py
vendored
Normal file
22
crates/ruff/resources/test/fixtures/flake8_bandit/S201.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def main():
|
||||
raise
|
||||
|
||||
# OK
|
||||
app.run(debug=True)
|
||||
|
||||
# Errors
|
||||
app.run()
|
||||
app.run(debug=False)
|
||||
|
||||
# Unrelated
|
||||
run()
|
||||
run(debug=True)
|
||||
run(debug)
|
||||
foo.run(debug=True)
|
||||
app = 1
|
||||
app.run(debug=True)
|
||||
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_4.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# formfeed indent
|
||||
# https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458825
|
||||
# This is technically a stylist bug (and has a test there), but it surfaced in B006
|
||||
|
||||
|
||||
class FormFeedIndent:
|
||||
def __init__(self, a=[]):
|
||||
print(a)
|
||||
|
||||
@@ -53,3 +53,6 @@ setattr(foo, "__123abc__", None)
|
||||
setattr(foo, "abc123", None)
|
||||
setattr(foo, r"abc123", None)
|
||||
setattr(foo.bar, r"baz", None)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
assert getattr(func, '_rpc')is True
|
||||
|
||||
@@ -19,3 +19,6 @@ print(f'Hello {dict((x,f(x)) for x in "abc")} World')
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7086
|
||||
dict((k,v)for k,v in d.iteritems() if k in only_args)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458940
|
||||
dict((*v, k) for k, v in enumerate(calendar.month_abbr))
|
||||
|
||||
24
crates/ruff/resources/test/fixtures/flake8_logging/LOG002.py
vendored
Normal file
24
crates/ruff/resources/test/fixtures/flake8_logging/LOG002.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
from logging import getLogger
|
||||
|
||||
# Ok
|
||||
logging.getLogger(__name__)
|
||||
logging.getLogger(name=__name__)
|
||||
logging.getLogger("custom")
|
||||
logging.getLogger(name="custom")
|
||||
|
||||
# LOG002
|
||||
getLogger(__file__)
|
||||
logging.getLogger(name=__file__)
|
||||
|
||||
logging.getLogger(__cached__)
|
||||
getLogger(name=__cached__)
|
||||
|
||||
|
||||
# Override `logging.getLogger`
|
||||
class logging:
|
||||
def getLogger(self):
|
||||
pass
|
||||
|
||||
|
||||
logging.getLogger(__file__)
|
||||
16
crates/ruff/resources/test/fixtures/flake8_logging/LOG007.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/flake8_logging/LOG007.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logging.exception("foo") # OK
|
||||
logging.exception("foo", exc_info=False) # LOG007
|
||||
logging.exception("foo", exc_info=[]) # LOG007
|
||||
logger.exception("foo") # OK
|
||||
logger.exception("foo", exc_info=False) # LOG007
|
||||
logger.exception("foo", exc_info=[]) # LOG007
|
||||
|
||||
|
||||
from logging import exception
|
||||
|
||||
exception("foo", exc_info=False) # LOG007
|
||||
exception("foo", exc_info=True) # OK
|
||||
@@ -92,3 +92,9 @@ class Test(unittest.TestCase):
|
||||
|
||||
def test_fail_if_equal(self):
|
||||
self.failIfEqual(1, 2) # Error
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459517
|
||||
(self.assertTrue(
|
||||
"piAx_piAy_beta[r][x][y] = {17}".format(
|
||||
self.model.piAx_piAy_beta[r][x][y])))
|
||||
|
||||
@@ -41,9 +41,13 @@ class Test(unittest.TestCase):
|
||||
assert True
|
||||
|
||||
|
||||
from typing import override
|
||||
from typing import override, overload
|
||||
|
||||
|
||||
@override
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
@overload
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
@@ -37,3 +37,4 @@ assert (not foo) in bar
|
||||
assert {"x": not foo} in bar
|
||||
assert [42, not foo] in bar
|
||||
assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
assert not('name' in request)or not request['name']
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
'''File starts with a tab
|
||||
multiline string with tab in it'''
|
||||
|
||||
#: W191
|
||||
if False:
|
||||
print # indented with 1 tab
|
||||
|
||||
@@ -49,6 +49,20 @@ class Apples:
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
# Allow dunder methods recommended by attrs.
|
||||
def __attrs_post_init__(self):
|
||||
pass
|
||||
|
||||
def __attrs_pre_init__(self):
|
||||
pass
|
||||
|
||||
def __attrs_init__(self):
|
||||
pass
|
||||
|
||||
# Allow __html__, used by Jinja2 and Django.
|
||||
def __html__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -36,3 +36,8 @@ def isinstances():
|
||||
result = isinstance(var[6], int) or isinstance(var[7], int)
|
||||
result = isinstance(var[6], (float, int)) or False
|
||||
return result
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
...
|
||||
|
||||
@@ -10,6 +10,5 @@ type(arg)(" ")
|
||||
# OK
|
||||
y = x.dtype.type(0.0)
|
||||
|
||||
# OK
|
||||
type = lambda *args, **kwargs: None
|
||||
type("")
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459841
|
||||
assert isinstance(fullname, type("")is not True)
|
||||
|
||||
@@ -108,3 +108,9 @@ class ServiceRefOrValue:
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7201
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[str]is not True = None
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7452
|
||||
class Collection(Protocol[*_B0]):
|
||||
def __iter__(self) -> Iterator[Union[*_B0]]:
|
||||
...
|
||||
|
||||
@@ -75,3 +75,8 @@ print("foo".encode()) # print(b"foo")
|
||||
(f"foo{bar}").encode(encoding="utf-8")
|
||||
("unicode text©").encode("utf-8")
|
||||
("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459882
|
||||
def _match_ignore(line):
|
||||
input=stdin and'\n'.encode()or None
|
||||
|
||||
43
crates/ruff/resources/test/fixtures/refurb/FURB140.py
vendored
Normal file
43
crates/ruff/resources/test/fixtures/refurb/FURB140.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
def zipped():
|
||||
return zip([1, 2, 3], "ABC")
|
||||
|
||||
# Errors.
|
||||
|
||||
# FURB140
|
||||
[print(x, y) for x, y in zipped()]
|
||||
|
||||
# FURB140
|
||||
(print(x, y) for x, y in zipped())
|
||||
|
||||
# FURB140
|
||||
{print(x, y) for x, y in zipped()}
|
||||
|
||||
|
||||
from itertools import starmap as sm
|
||||
|
||||
# FURB140
|
||||
[print(x, y) for x, y in zipped()]
|
||||
|
||||
# FURB140
|
||||
(print(x, y) for x, y in zipped())
|
||||
|
||||
# FURB140
|
||||
{print(x, y) for x, y in zipped()}
|
||||
|
||||
# Non-errors.
|
||||
|
||||
[print(x, int) for x, _ in zipped()]
|
||||
|
||||
[print(x, *y) for x, y in zipped()]
|
||||
|
||||
[print(x, y, 1) for x, y in zipped()]
|
||||
|
||||
[print(y, x) for x, y in zipped()]
|
||||
|
||||
[print(x + 1, y) for x, y in zipped()]
|
||||
|
||||
[print(x) for x in range(100)]
|
||||
|
||||
[print() for x, y in zipped()]
|
||||
|
||||
[print(x, end=y) for x, y in zipped()]
|
||||
@@ -586,6 +586,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::LoggingConfigInsecureListen) {
|
||||
flake8_bandit::rules::logging_config_insecure_listen(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::FlaskDebugTrue) {
|
||||
flake8_bandit::rules::flask_debug_true(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::SubprocessWithoutShellEqualsTrue,
|
||||
Rule::SubprocessPopenWithShellEqualsTrue,
|
||||
@@ -895,6 +898,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DirectLoggerInstantiation) {
|
||||
flake8_logging::rules::direct_logger_instantiation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidGetLoggerArgument) {
|
||||
flake8_logging::rules::invalid_get_logger_argument(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ExceptionWithoutExcInfo) {
|
||||
flake8_logging::rules::exception_without_exc_info(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(ast::ExprDict {
|
||||
keys,
|
||||
@@ -1270,16 +1279,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_simplify::rules::twisted_arms_in_ifexpr(checker, expr, test, body, orelse);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(ast::ExprListComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
})
|
||||
| Expr::SetComp(ast::ExprSetComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1293,6 +1299,33 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &comp.into());
|
||||
}
|
||||
}
|
||||
Expr::SetComp(
|
||||
comp @ ast::ExprSetComp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
if checker.enabled(Rule::IterationOverSet) {
|
||||
for generator in generators {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &comp.into());
|
||||
}
|
||||
}
|
||||
Expr::DictComp(ast::ExprDictComp {
|
||||
key,
|
||||
@@ -1317,11 +1350,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
ruff::rules::static_key_dict_comprehension(checker, key);
|
||||
}
|
||||
}
|
||||
Expr::GeneratorExp(ast::ExprGeneratorExp {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::GeneratorExp(
|
||||
generator @ ast::ExprGeneratorExp {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
@@ -1330,6 +1365,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::iteration_over_set(checker, &generator.iter);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedStarmap) {
|
||||
refurb::rules::reimplemented_starmap(checker, &generator.into());
|
||||
}
|
||||
}
|
||||
Expr::BoolOp(
|
||||
bool_op @ ast::ExprBoolOp {
|
||||
|
||||
@@ -6,6 +6,7 @@ use itertools::Itertools;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::noqa;
|
||||
@@ -19,7 +20,7 @@ pub(crate) fn check_noqa(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
analyze_directives: bool,
|
||||
settings: &Settings,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::registry::Rule;
|
||||
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
||||
use crate::rules::pycodestyle::rules::{
|
||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||
tab_indentation, trailing_whitespace,
|
||||
trailing_whitespace,
|
||||
};
|
||||
use crate::rules::pylint;
|
||||
use crate::settings::Settings;
|
||||
@@ -31,7 +31,6 @@ pub(crate) fn check_physical_lines(
|
||||
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
|
||||
let enforce_blank_line_contains_whitespace =
|
||||
settings.rules.enabled(Rule::BlankLineWithWhitespace);
|
||||
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
|
||||
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
|
||||
|
||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||
@@ -69,12 +68,6 @@ pub(crate) fn check_physical_lines(
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_tab_indentation {
|
||||
if let Some(diagnostic) = tab_indentation(&line, indexer) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_no_newline_at_end_of_file {
|
||||
|
||||
@@ -86,6 +86,10 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::TabIndentation) {
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::InvalidCharacterBackspace,
|
||||
Rule::InvalidCharacterSub,
|
||||
|
||||
@@ -573,6 +573,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "110") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptPass),
|
||||
(Flake8Bandit, "112") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::TryExceptContinue),
|
||||
(Flake8Bandit, "113") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::RequestWithoutTimeout),
|
||||
(Flake8Bandit, "201") => (RuleGroup::Preview, rules::flake8_bandit::rules::FlaskDebugTrue),
|
||||
(Flake8Bandit, "301") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousPickleUsage),
|
||||
(Flake8Bandit, "302") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousMarshalUsage),
|
||||
(Flake8Bandit, "303") => (RuleGroup::Unspecified, rules::flake8_bandit::rules::SuspiciousInsecureHashUsage),
|
||||
@@ -918,10 +919,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
(Flake8Logging, "002") => (RuleGroup::Preview, rules::flake8_logging::rules::InvalidGetLoggerArgument),
|
||||
(Flake8Logging, "007") => (RuleGroup::Preview, rules::flake8_logging::rules::ExceptionWithoutExcInfo),
|
||||
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
|
||||
|
||||
_ => return None,
|
||||
|
||||
@@ -33,7 +33,7 @@ expression: content
|
||||
},
|
||||
"message": "`os` imported but unused",
|
||||
"noqa_row": 1,
|
||||
"url": "https://beta.ruff.rs/docs/rules/unused-import"
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
},
|
||||
{
|
||||
"code": "F841",
|
||||
@@ -65,7 +65,7 @@ expression: content
|
||||
},
|
||||
"message": "Local variable `x` is assigned to but never used",
|
||||
"noqa_row": 6,
|
||||
"url": "https://beta.ruff.rs/docs/rules/unused-variable"
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
|
||||
},
|
||||
{
|
||||
"code": "F821",
|
||||
@@ -81,6 +81,6 @@ expression: content
|
||||
},
|
||||
"message": "Undefined name `a`",
|
||||
"noqa_row": 1,
|
||||
"url": "https://beta.ruff.rs/docs/rules/undefined-name"
|
||||
"url": "https://docs.astral.sh/ruff/rules/undefined-name"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff/src/message/json_lines.rs
|
||||
expression: content
|
||||
---
|
||||
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/unused-import"}
|
||||
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://beta.ruff.rs/docs/rules/unused-variable"}
|
||||
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://beta.ruff.rs/docs/rules/undefined-name"}
|
||||
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use log::warn;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_trivia::indentation_at_offset;
|
||||
use ruff_python_trivia::{indentation_at_offset, CommentRanges};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::codes::NoqaCode;
|
||||
@@ -234,7 +234,7 @@ impl FileExemption {
|
||||
/// globally ignored within the file.
|
||||
pub(crate) fn try_extract(
|
||||
contents: &str,
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
) -> Option<Self> {
|
||||
@@ -457,7 +457,7 @@ pub(crate) fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
commented_lines: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> Result<usize> {
|
||||
@@ -465,7 +465,7 @@ pub(crate) fn add_noqa(
|
||||
path,
|
||||
diagnostics,
|
||||
locator,
|
||||
commented_lines,
|
||||
comment_ranges,
|
||||
noqa_line_for,
|
||||
line_ending,
|
||||
);
|
||||
@@ -477,7 +477,7 @@ fn add_noqa_inner(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
commented_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> (usize, String) {
|
||||
@@ -487,8 +487,8 @@ fn add_noqa_inner(
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
// Codes that are globally exempted (within the current file).
|
||||
let exemption = FileExemption::try_extract(locator.contents(), commented_ranges, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(commented_ranges, path, locator);
|
||||
let exemption = FileExemption::try_extract(locator.contents(), comment_ranges, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for diagnostic in diagnostics {
|
||||
@@ -658,7 +658,7 @@ pub(crate) struct NoqaDirectives<'a> {
|
||||
|
||||
impl<'a> NoqaDirectives<'a> {
|
||||
pub(crate) fn from_commented_ranges(
|
||||
comment_ranges: &[TextRange],
|
||||
comment_ranges: &CommentRanges,
|
||||
path: &Path,
|
||||
locator: &'a Locator<'a>,
|
||||
) -> Self {
|
||||
@@ -800,6 +800,7 @@ mod tests {
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption};
|
||||
@@ -997,7 +998,7 @@ mod tests {
|
||||
path,
|
||||
&[],
|
||||
&Locator::new(contents),
|
||||
&[],
|
||||
&CommentRanges::default(),
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1017,7 +1018,7 @@ mod tests {
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[],
|
||||
&CommentRanges::default(),
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1038,11 +1039,13 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741\n";
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges =
|
||||
CommentRanges::new(vec![TextRange::new(TextSize::from(7), TextSize::from(19))]);
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[TextRange::new(TextSize::from(7), TextSize::from(19))],
|
||||
&comment_ranges,
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
@@ -1063,11 +1066,13 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges =
|
||||
CommentRanges::new(vec![TextRange::new(TextSize::from(7), TextSize::from(13))]);
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(contents),
|
||||
&[TextRange::new(TextSize::from(7), TextSize::from(13))],
|
||||
&comment_ranges,
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
|
||||
@@ -251,7 +251,6 @@ impl Rule {
|
||||
| Rule::MissingCopyrightNotice
|
||||
| Rule::MissingNewlineAtEndOfFile
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::TabIndentation
|
||||
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
||||
Rule::AmbiguousUnicodeCharacterComment
|
||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||
@@ -292,6 +291,7 @@ impl Rule {
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNotFirstLine
|
||||
| Rule::SingleLineImplicitStringConcatenation
|
||||
| Rule::TabIndentation
|
||||
| Rule::TrailingCommaOnBareTuple
|
||||
| Rule::TypeCommentInStub
|
||||
| Rule::UselessSemicolon
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -237,7 +235,7 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
..
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
if *i == BigInt::from(0) {
|
||||
if *i == 0 {
|
||||
if let (
|
||||
[CmpOp::Eq | CmpOp::NotEq],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
@@ -246,13 +244,13 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
})],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if *n == BigInt::from(3) && checker.enabled(Rule::SysVersionInfo0Eq3) {
|
||||
if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionInfo0Eq3, left.range()));
|
||||
}
|
||||
}
|
||||
} else if *i == BigInt::from(1) {
|
||||
} else if *i == 1 {
|
||||
if let (
|
||||
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -184,11 +183,11 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
..
|
||||
}) = upper.as_ref()
|
||||
{
|
||||
if *i == BigInt::from(1) && checker.enabled(Rule::SysVersionSlice1) {
|
||||
if *i == 1 && checker.enabled(Rule::SysVersionSlice1) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionSlice1, value.range()));
|
||||
} else if *i == BigInt::from(3) && checker.enabled(Rule::SysVersionSlice3) {
|
||||
} else if *i == 3 && checker.enabled(Rule::SysVersionSlice3) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersionSlice3, value.range()));
|
||||
@@ -200,11 +199,11 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
}) => {
|
||||
if *i == BigInt::from(2) && checker.enabled(Rule::SysVersion2) {
|
||||
if *i == 2 && checker.enabled(Rule::SysVersion2) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersion2, value.range()));
|
||||
} else if *i == BigInt::from(0) && checker.enabled(Rule::SysVersion0) {
|
||||
} else if *i == 0 && checker.enabled(Rule::SysVersion0) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SysVersion0, value.range()));
|
||||
|
||||
@@ -19,6 +19,7 @@ mod tests {
|
||||
#[test_case(Rule::BadFilePermissions, Path::new("S103.py"))]
|
||||
#[test_case(Rule::CallWithShellEqualsTrue, Path::new("S604.py"))]
|
||||
#[test_case(Rule::ExecBuiltin, Path::new("S102.py"))]
|
||||
#[test_case(Rule::FlaskDebugTrue, Path::new("S201.py"))]
|
||||
#[test_case(Rule::HardcodedBindAllInterfaces, Path::new("S104.py"))]
|
||||
#[test_case(Rule::HardcodedPasswordDefault, Path::new("S107.py"))]
|
||||
#[test_case(Rule::HardcodedPasswordFuncArg, Path::new("S106.py"))]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use num_traits::ToPrimitive;
|
||||
use malachite::Integer;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -36,7 +36,7 @@ use crate::checkers::ast::Checker;
|
||||
/// - [Common Weakness Enumeration: CWE-732](https://cwe.mitre.org/data/definitions/732.html)
|
||||
#[violation]
|
||||
pub struct BadFilePermissions {
|
||||
mask: u16,
|
||||
mask: Integer,
|
||||
}
|
||||
|
||||
impl Violation for BadFilePermissions {
|
||||
@@ -56,7 +56,9 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
|
||||
{
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||
if let Some(int_value) = int_value(mode_arg, checker.semantic()) {
|
||||
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
|
||||
if (int_value.clone() & Integer::from(WRITE_WORLD) > 0)
|
||||
|| (int_value.clone() & Integer::from(EXECUTE_GROUP) > 0)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BadFilePermissions { mask: int_value },
|
||||
mode_arg.range(),
|
||||
@@ -113,13 +115,17 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<u16> {
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<Integer> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) => value.to_u16(),
|
||||
Expr::Attribute(_) => semantic.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
}) => Some(value.clone()),
|
||||
Expr::Attribute(_) => semantic
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.and_then(py_stat)
|
||||
.map(Integer::from),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::{Expr, ExprAttribute, ExprCall, Stmt, StmtAssign};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `debug=True` in Flask.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Enabling debug mode shows an interactive debugger in the browser if an
|
||||
/// error occurs, and allows running arbitrary Python code from the browser.
|
||||
/// This could leak sensitive information, or allow an attacker to run
|
||||
/// arbitrary code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
/// app.run(debug=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
/// app.run(debug=os.environ["ENV"] == "dev")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Flask documentation: Debug Mode](https://flask.palletsprojects.com/en/latest/quickstart/#debug-mode)
|
||||
#[violation]
|
||||
pub struct FlaskDebugTrue;
|
||||
|
||||
impl Violation for FlaskDebugTrue {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `debug=True` in Flask app detected")
|
||||
}
|
||||
}
|
||||
|
||||
/// S201
|
||||
pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
|
||||
let Expr::Attribute(ExprAttribute { attr, value, .. }) = call.func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if attr.as_str() != "run" {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(debug_argument) = call.arguments.find_keyword("debug") else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_const_true(&debug_argument.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Name(name) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(binding_id) = checker.semantic().resolve_name(name) {
|
||||
if let Some(Stmt::Assign(StmtAssign { value, .. })) = checker
|
||||
.semantic()
|
||||
.binding(binding_id)
|
||||
.statement(checker.semantic())
|
||||
{
|
||||
if let Expr::Call(ExprCall { func, .. }) = value.as_ref() {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["flask", "Flask"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FlaskDebugTrue, debug_argument.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub(crate) use assert_used::*;
|
||||
pub(crate) use bad_file_permissions::*;
|
||||
pub(crate) use exec_used::*;
|
||||
pub(crate) use flask_debug_true::*;
|
||||
pub(crate) use hardcoded_bind_all_interfaces::*;
|
||||
pub(crate) use hardcoded_password_default::*;
|
||||
pub(crate) use hardcoded_password_func_arg::*;
|
||||
@@ -24,6 +25,7 @@ pub(crate) use unsafe_yaml_load::*;
|
||||
mod assert_used;
|
||||
mod bad_file_permissions;
|
||||
mod exec_used;
|
||||
mod flask_debug_true;
|
||||
mod hardcoded_bind_all_interfaces;
|
||||
mod hardcoded_password_default;
|
||||
mod hardcoded_password_func_arg;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use num_traits::{One, Zero};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
@@ -57,7 +55,7 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall)
|
||||
..
|
||||
}) = &keyword.value
|
||||
{
|
||||
if value.is_zero() || value.is_one() {
|
||||
if *value == 0 || *value == 1 {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S201.py:10:9: S201 Use of `debug=True` in Flask app detected
|
||||
|
|
||||
9 | # OK
|
||||
10 | app.run(debug=True)
|
||||
| ^^^^^^^^^^ S201
|
||||
11 |
|
||||
12 | # Errors
|
||||
|
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ mod tests {
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_4.py"))]
|
||||
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
||||
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
|
||||
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"))]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::autofix::edits::pad;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
@@ -80,17 +81,21 @@ pub(crate) fn getattr_with_constant(
|
||||
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
pad(
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
expr.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_4.py:7:26: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
6 | class FormFeedIndent:
|
||||
7 | def __init__(self, a=[]):
|
||||
| ^^ B006
|
||||
8 | print(a)
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 6 | class FormFeedIndent:
|
||||
7 |- def __init__(self, a=[]):
|
||||
7 |+ def __init__(self, a=None):
|
||||
8 |+ if a is None:
|
||||
9 |+ a = []
|
||||
8 10 | print(a)
|
||||
9 11 |
|
||||
|
||||
|
||||
@@ -316,4 +316,19 @@ B009_B010.py:34:1: B009 [*] Do not call `getattr` with a constant attribute valu
|
||||
37 37 |
|
||||
38 38 | # Valid setattr usage
|
||||
|
||||
B009_B010.py:58:8: B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
57 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
58 | assert getattr(func, '_rpc')is True
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ B009
|
||||
|
|
||||
= help: Replace `getattr` with attribute access
|
||||
|
||||
ℹ Suggested fix
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
56 56 |
|
||||
57 57 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
58 |-assert getattr(func, '_rpc')is True
|
||||
58 |+assert func._rpc is True
|
||||
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ B009_B010.py:53:1: B010 [*] Do not call `setattr` with a constant attribute valu
|
||||
53 |+foo.abc123 = None
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
56 56 |
|
||||
|
||||
B009_B010.py:54:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
@@ -100,6 +101,8 @@ B009_B010.py:54:1: B010 [*] Do not call `setattr` with a constant attribute valu
|
||||
54 |-setattr(foo, r"abc123", None)
|
||||
54 |+foo.abc123 = None
|
||||
55 55 | setattr(foo.bar, r"baz", None)
|
||||
56 56 |
|
||||
57 57 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
|
||||
B009_B010.py:55:1: B010 [*] Do not call `setattr` with a constant attribute value. It is not any safer than normal property access.
|
||||
|
|
||||
@@ -107,6 +110,8 @@ B009_B010.py:55:1: B010 [*] Do not call `setattr` with a constant attribute valu
|
||||
54 | setattr(foo, r"abc123", None)
|
||||
55 | setattr(foo.bar, r"baz", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B010
|
||||
56 |
|
||||
57 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
|
|
||||
= help: Replace `setattr` with assignment
|
||||
|
||||
@@ -116,5 +121,8 @@ B009_B010.py:55:1: B010 [*] Do not call `setattr` with a constant attribute valu
|
||||
54 54 | setattr(foo, r"abc123", None)
|
||||
55 |-setattr(foo.bar, r"baz", None)
|
||||
55 |+foo.bar.baz = None
|
||||
56 56 |
|
||||
57 57 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
58 58 | assert getattr(func, '_rpc')is True
|
||||
|
||||
|
||||
|
||||
@@ -54,18 +54,23 @@ pub(crate) fn unnecessary_generator_dict(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Expr::GeneratorExp(ast::ExprGeneratorExp { elt, .. }) = argument {
|
||||
match elt.as_ref() {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) if elts.len() == 2 => {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let Expr::GeneratorExp(ast::ExprGeneratorExp { elt, .. }) = argument else {
|
||||
return;
|
||||
};
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = elt.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if elts.len() != 2 {
|
||||
return;
|
||||
}
|
||||
if elts.iter().any(Expr::is_starred_expr) {
|
||||
return;
|
||||
}
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, UnaryOp};
|
||||
@@ -93,7 +91,7 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if *val != BigInt::from(1) {
|
||||
if *val != 1 {
|
||||
return;
|
||||
};
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -251,6 +251,8 @@ C402.py:21:1: C402 [*] Unnecessary generator (rewrite as a `dict` comprehension)
|
||||
20 | # Regression test for: https://github.com/astral-sh/ruff/issues/7086
|
||||
21 | dict((k,v)for k,v in d.iteritems() if k in only_args)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C402
|
||||
22 |
|
||||
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458940
|
||||
|
|
||||
= help: Rewrite as a `dict` comprehension
|
||||
|
||||
@@ -260,5 +262,8 @@ C402.py:21:1: C402 [*] Unnecessary generator (rewrite as a `dict` comprehension)
|
||||
20 20 | # Regression test for: https://github.com/astral-sh/ruff/issues/7086
|
||||
21 |-dict((k,v)for k,v in d.iteritems() if k in only_args)
|
||||
21 |+{k: v for k,v in d.iteritems() if k in only_args}
|
||||
22 22 |
|
||||
23 23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458940
|
||||
24 24 | dict((*v, k) for k, v in enumerate(calendar.month_abbr))
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ mod tests {
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::DirectLoggerInstantiation, Path::new("LOG001.py"))]
|
||||
#[test_case(Rule::InvalidGetLoggerArgument, Path::new("LOG002.py"))]
|
||||
#[test_case(Rule::ExceptionWithoutExcInfo, Path::new("LOG007.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());
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_semantic::analyze::logging::is_logger_candidate;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `logging.exception()` with `exc_info` set to `False`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `logging.exception()` method captures the exception automatically, but
|
||||
/// accepts an optional `exc_info` argument to override this behavior. Setting
|
||||
/// `exc_info` to `False` disables the automatic capture of the exception and
|
||||
/// stack trace.
|
||||
///
|
||||
/// Instead of setting `exc_info` to `False`, prefer `logging.error()`, which
|
||||
/// has equivalent behavior to `logging.exception()` with `exc_info` set to
|
||||
/// `False`, but is clearer in intent.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// logging.exception("...", exc_info=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// logging.error("...")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct ExceptionWithoutExcInfo;
|
||||
|
||||
impl Violation for ExceptionWithoutExcInfo {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of `logging.exception` with falsy `exc_info`")
|
||||
}
|
||||
}
|
||||
|
||||
/// LOG007
|
||||
pub(crate) fn exception_without_exc_info(checker: &mut Checker, call: &ExprCall) {
|
||||
if !is_logger_candidate(
|
||||
call.func.as_ref(),
|
||||
checker.semantic(),
|
||||
&["exception".to_string()],
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if call
|
||||
.arguments
|
||||
.find_keyword("exc_info")
|
||||
.map(|keyword| &keyword.value)
|
||||
.is_some_and(|value| {
|
||||
Truthiness::from_expr(value, |id| checker.semantic().is_builtin(id)).is_falsey()
|
||||
})
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ExceptionWithoutExcInfo, call.range()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for any usage of `__cached__` and `__file__` as an argument to
|
||||
/// `logging.getLogger()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The [logging documentation] recommends this pattern:
|
||||
///
|
||||
/// ```python
|
||||
/// logging.getLogger(__name__)
|
||||
/// ```
|
||||
///
|
||||
/// Here, `__name__` is the fully qualified module name, such as `foo.bar`,
|
||||
/// which is the intended format for logger names.
|
||||
///
|
||||
/// This rule detects probably-mistaken usage of similar module-level dunder constants:
|
||||
///
|
||||
/// * `__cached__` - the pathname of the module's compiled version, such as `foo/__pycache__/bar.cpython-311.pyc`.
|
||||
/// * `__file__` - the pathname of the module, such as `foo/bar.py`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logger = logging.getLogger(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logger = logging.getLogger(__name__)
|
||||
/// ```
|
||||
///
|
||||
/// [logging documentation]: https://docs.python.org/3/library/logging.html#logger-objects
|
||||
#[violation]
|
||||
pub struct InvalidGetLoggerArgument;
|
||||
|
||||
impl Violation for InvalidGetLoggerArgument {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use `__name__` with `logging.getLogger()`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `__name__`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// LOG002
|
||||
pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
let Some(Expr::Name(expr @ ast::ExprName { id, .. })) = call.arguments.find_argument("name", 0)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(id.as_ref(), "__file__" | "__cached__") {
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(call.func.as_ref())
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "getLogger"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(InvalidGetLoggerArgument, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if checker.semantic().is_builtin("__name__") {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
"__name__".to_string(),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
pub(crate) use direct_logger_instantiation::*;
|
||||
pub(crate) use exception_without_exc_info::*;
|
||||
pub(crate) use invalid_get_logger_argument::*;
|
||||
pub(crate) use undocumented_warn::*;
|
||||
|
||||
mod direct_logger_instantiation;
|
||||
mod exception_without_exc_info;
|
||||
mod invalid_get_logger_argument;
|
||||
mod undocumented_warn;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_logging/mod.rs
|
||||
---
|
||||
LOG002.py:11:11: LOG002 [*] Use `__name__` with `logging.getLogger()`
|
||||
|
|
||||
10 | # LOG002
|
||||
11 | getLogger(__file__)
|
||||
| ^^^^^^^^ LOG002
|
||||
12 | logging.getLogger(name=__file__)
|
||||
|
|
||||
= help: Replace with `name`
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | logging.getLogger(name="custom")
|
||||
9 9 |
|
||||
10 10 | # LOG002
|
||||
11 |-getLogger(__file__)
|
||||
11 |+getLogger(__name__)
|
||||
12 12 | logging.getLogger(name=__file__)
|
||||
13 13 |
|
||||
14 14 | logging.getLogger(__cached__)
|
||||
|
||||
LOG002.py:12:24: LOG002 [*] Use `__name__` with `logging.getLogger()`
|
||||
|
|
||||
10 | # LOG002
|
||||
11 | getLogger(__file__)
|
||||
12 | logging.getLogger(name=__file__)
|
||||
| ^^^^^^^^ LOG002
|
||||
13 |
|
||||
14 | logging.getLogger(__cached__)
|
||||
|
|
||||
= help: Replace with `name`
|
||||
|
||||
ℹ Suggested fix
|
||||
9 9 |
|
||||
10 10 | # LOG002
|
||||
11 11 | getLogger(__file__)
|
||||
12 |-logging.getLogger(name=__file__)
|
||||
12 |+logging.getLogger(name=__name__)
|
||||
13 13 |
|
||||
14 14 | logging.getLogger(__cached__)
|
||||
15 15 | getLogger(name=__cached__)
|
||||
|
||||
LOG002.py:14:19: LOG002 [*] Use `__name__` with `logging.getLogger()`
|
||||
|
|
||||
12 | logging.getLogger(name=__file__)
|
||||
13 |
|
||||
14 | logging.getLogger(__cached__)
|
||||
| ^^^^^^^^^^ LOG002
|
||||
15 | getLogger(name=__cached__)
|
||||
|
|
||||
= help: Replace with `name`
|
||||
|
||||
ℹ Suggested fix
|
||||
11 11 | getLogger(__file__)
|
||||
12 12 | logging.getLogger(name=__file__)
|
||||
13 13 |
|
||||
14 |-logging.getLogger(__cached__)
|
||||
14 |+logging.getLogger(__name__)
|
||||
15 15 | getLogger(name=__cached__)
|
||||
16 16 |
|
||||
17 17 |
|
||||
|
||||
LOG002.py:15:16: LOG002 [*] Use `__name__` with `logging.getLogger()`
|
||||
|
|
||||
14 | logging.getLogger(__cached__)
|
||||
15 | getLogger(name=__cached__)
|
||||
| ^^^^^^^^^^ LOG002
|
||||
|
|
||||
= help: Replace with `name`
|
||||
|
||||
ℹ Suggested fix
|
||||
12 12 | logging.getLogger(name=__file__)
|
||||
13 13 |
|
||||
14 14 | logging.getLogger(__cached__)
|
||||
15 |-getLogger(name=__cached__)
|
||||
15 |+getLogger(name=__name__)
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 18 | # Override `logging.getLogger`
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_logging/mod.rs
|
||||
---
|
||||
LOG007.py:6:1: LOG007 Use of `logging.exception` with falsy `exc_info`
|
||||
|
|
||||
5 | logging.exception("foo") # OK
|
||||
6 | logging.exception("foo", exc_info=False) # LOG007
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG007
|
||||
7 | logging.exception("foo", exc_info=[]) # LOG007
|
||||
8 | logger.exception("foo") # OK
|
||||
|
|
||||
|
||||
LOG007.py:7:1: LOG007 Use of `logging.exception` with falsy `exc_info`
|
||||
|
|
||||
5 | logging.exception("foo") # OK
|
||||
6 | logging.exception("foo", exc_info=False) # LOG007
|
||||
7 | logging.exception("foo", exc_info=[]) # LOG007
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG007
|
||||
8 | logger.exception("foo") # OK
|
||||
9 | logger.exception("foo", exc_info=False) # LOG007
|
||||
|
|
||||
|
||||
LOG007.py:9:1: LOG007 Use of `logging.exception` with falsy `exc_info`
|
||||
|
|
||||
7 | logging.exception("foo", exc_info=[]) # LOG007
|
||||
8 | logger.exception("foo") # OK
|
||||
9 | logger.exception("foo", exc_info=False) # LOG007
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG007
|
||||
10 | logger.exception("foo", exc_info=[]) # LOG007
|
||||
|
|
||||
|
||||
LOG007.py:10:1: LOG007 Use of `logging.exception` with falsy `exc_info`
|
||||
|
|
||||
8 | logger.exception("foo") # OK
|
||||
9 | logger.exception("foo", exc_info=False) # LOG007
|
||||
10 | logger.exception("foo", exc_info=[]) # LOG007
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LOG007
|
||||
|
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -75,7 +73,7 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if *value != BigInt::from(0) {
|
||||
if *value != 0 {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,26 @@ use ruff_text_size::Ranged;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pass` statements in empty stub bodies.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// For consistency, empty stub bodies should contain `...` instead of `pass`.
|
||||
///
|
||||
/// Additionally, an ellipsis better conveys the intent of the stub body (that
|
||||
/// the body has been implemented, but has been intentionally left blank to
|
||||
/// document the interface).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(bar: int) -> list[int]:
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(bar: int) -> list[int]: ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct PassStatementStubBody;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -249,10 +247,10 @@ impl ExpectedComparator {
|
||||
..
|
||||
}) = upper.as_ref()
|
||||
{
|
||||
if *upper == BigInt::one() {
|
||||
if *upper == 1 {
|
||||
return Some(ExpectedComparator::MajorTuple);
|
||||
}
|
||||
if *upper == BigInt::from(2) {
|
||||
if *upper == 2 {
|
||||
return Some(ExpectedComparator::MajorMinorTuple);
|
||||
}
|
||||
}
|
||||
@@ -260,7 +258,7 @@ impl ExpectedComparator {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(n),
|
||||
..
|
||||
}) if n.is_zero() => {
|
||||
}) if *n == 0 => {
|
||||
return Some(ExpectedComparator::MajorDigit);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -10,6 +10,7 @@ use libcst_native::{
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, BoolOp, ExceptHandler, Expr, Keyword, Stmt, UnaryOp,
|
||||
@@ -293,7 +294,13 @@ pub(crate) fn unittest_assertion(
|
||||
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
checker.generator().stmt(&stmt),
|
||||
expr.range(),
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
checker.semantic().current_statement().into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(expr.range()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use ruff_python_ast::node::AstNode;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Decorator, Expr, ExprContext};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -298,12 +299,17 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
|
||||
fn get_parametrize_name_range(
|
||||
decorator: &Decorator,
|
||||
expr: &Expr,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Option<TextRange> {
|
||||
decorator
|
||||
.expression
|
||||
.as_call_expr()
|
||||
.and_then(|call| parenthesized_range(expr.into(), call.arguments.as_any_node_ref(), source))
|
||||
decorator.expression.as_call_expr().and_then(|call| {
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
call.arguments.as_any_node_ref(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// PT006
|
||||
@@ -322,6 +328,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
let name_range = get_parametrize_name_range(
|
||||
decorator,
|
||||
expr,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(expr.range());
|
||||
@@ -356,6 +363,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
let name_range = get_parametrize_name_range(
|
||||
decorator,
|
||||
expr,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(expr.range());
|
||||
|
||||
@@ -637,5 +637,27 @@ PT009.py:94:9: PT009 [*] Use a regular `assert` instead of unittest-style `failI
|
||||
93 93 | def test_fail_if_equal(self):
|
||||
94 |- self.failIfEqual(1, 2) # Error
|
||||
94 |+ assert 1 != 2 # Error
|
||||
95 95 |
|
||||
96 96 |
|
||||
97 97 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459517
|
||||
|
||||
PT009.py:98:2: PT009 [*] Use a regular `assert` instead of unittest-style `assertTrue`
|
||||
|
|
||||
97 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459517
|
||||
98 | (self.assertTrue(
|
||||
| ^^^^^^^^^^^^^^^ PT009
|
||||
99 | "piAx_piAy_beta[r][x][y] = {17}".format(
|
||||
100 | self.model.piAx_piAy_beta[r][x][y])))
|
||||
|
|
||||
= help: Replace `assertTrue(...)` with `assert ...`
|
||||
|
||||
ℹ Suggested fix
|
||||
95 95 |
|
||||
96 96 |
|
||||
97 97 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459517
|
||||
98 |-(self.assertTrue(
|
||||
99 |- "piAx_piAy_beta[r][x][y] = {17}".format(
|
||||
100 |- self.model.piAx_piAy_beta[r][x][y])))
|
||||
98 |+assert "piAx_piAy_beta[r][x][y] = {17}".format(self.model.piAx_piAy_beta[r][x][y])
|
||||
|
||||
|
||||
|
||||
@@ -773,9 +773,14 @@ fn is_short_circuit(
|
||||
edit = Some(get_short_circuit_edit(
|
||||
value,
|
||||
TextRange::new(
|
||||
parenthesized_range(furthest.into(), expr.into(), checker.locator().contents())
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
parenthesized_range(
|
||||
furthest.into(),
|
||||
expr.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
expr.end(),
|
||||
),
|
||||
short_circuit_truthiness,
|
||||
@@ -796,9 +801,14 @@ fn is_short_circuit(
|
||||
edit = Some(get_short_circuit_edit(
|
||||
next_value,
|
||||
TextRange::new(
|
||||
parenthesized_range(furthest.into(), expr.into(), checker.locator().contents())
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
parenthesized_range(
|
||||
furthest.into(),
|
||||
expr.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
expr.end(),
|
||||
),
|
||||
short_circuit_truthiness,
|
||||
|
||||
@@ -163,8 +163,13 @@ pub(crate) fn if_expr_with_true_false(
|
||||
checker
|
||||
.locator()
|
||||
.slice(
|
||||
parenthesized_range(test.into(), expr.into(), checker.locator().contents())
|
||||
.unwrap_or(test.range()),
|
||||
parenthesized_range(
|
||||
test.into(),
|
||||
expr.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(test.range()),
|
||||
)
|
||||
.to_string(),
|
||||
expr.range(),
|
||||
|
||||
@@ -88,10 +88,20 @@ fn key_in_dict(
|
||||
}
|
||||
|
||||
// Extract the exact range of the left and right expressions.
|
||||
let left_range = parenthesized_range(left.into(), parent, checker.locator().contents())
|
||||
.unwrap_or(left.range());
|
||||
let right_range = parenthesized_range(right.into(), parent, checker.locator().contents())
|
||||
.unwrap_or(right.range());
|
||||
let left_range = parenthesized_range(
|
||||
left.into(),
|
||||
parent,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(left.range());
|
||||
let right_range = parenthesized_range(
|
||||
right.into(),
|
||||
parent,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(right.range());
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
InDictKeys {
|
||||
|
||||
@@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Keyword, Stmt};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -85,6 +86,7 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
call,
|
||||
keyword,
|
||||
statement,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator(),
|
||||
) {
|
||||
diagnostic.set_fix(fix);
|
||||
@@ -107,15 +109,21 @@ fn convert_inplace_argument_to_assignment(
|
||||
call: &ast::ExprCall,
|
||||
keyword: &Keyword,
|
||||
statement: &Stmt,
|
||||
comment_ranges: &CommentRanges,
|
||||
locator: &Locator,
|
||||
) -> Option<Fix> {
|
||||
// Add the assignment.
|
||||
let attr = call.func.as_attribute_expr()?;
|
||||
let insert_assignment = Edit::insertion(
|
||||
format!("{name} = ", name = locator.slice(attr.value.range())),
|
||||
parenthesized_range(call.into(), statement.into(), locator.contents())
|
||||
.unwrap_or(call.range())
|
||||
.start(),
|
||||
parenthesized_range(
|
||||
call.into(),
|
||||
statement.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.unwrap_or(call.range())
|
||||
.start(),
|
||||
);
|
||||
|
||||
// Remove the `inplace` argument.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use num_traits::One;
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -115,7 +114,7 @@ pub(crate) fn nunique_constant_series_check(
|
||||
fn is_constant_one(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Int(int) => int.is_one(),
|
||||
Constant::Int(int) => *int == 1,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
||||
@@ -70,9 +70,12 @@ pub(crate) fn invalid_function_name(
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore any functions that are explicitly `@override`. These are defined elsewhere,
|
||||
// so if they're first-party, we'll flag them at the definition site.
|
||||
if visibility::is_override(decorator_list, semantic) {
|
||||
// Ignore any functions that are explicitly `@override` or `@overload`.
|
||||
// These are defined elsewhere, so if they're first-party,
|
||||
// we'll flag them at the definition site.
|
||||
if visibility::is_override(decorator_list, semantic)
|
||||
|| visibility::is_overload(decorator_list, semantic)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use unicode_width::UnicodeWidthStr;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{CmpOp, Expr};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{Line, Locator};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
@@ -17,6 +18,7 @@ pub(super) fn generate_comparison(
|
||||
ops: &[CmpOp],
|
||||
comparators: &[Expr],
|
||||
parent: AnyNodeRef,
|
||||
comment_ranges: &CommentRanges,
|
||||
locator: &Locator,
|
||||
) -> String {
|
||||
let start = left.start();
|
||||
@@ -24,9 +26,12 @@ pub(super) fn generate_comparison(
|
||||
let mut contents = String::with_capacity(usize::from(end - start));
|
||||
|
||||
// Add the left side of the comparison.
|
||||
contents.push_str(locator.slice(
|
||||
parenthesized_range(left.into(), parent, locator.contents()).unwrap_or(left.range()),
|
||||
));
|
||||
contents.push_str(
|
||||
locator.slice(
|
||||
parenthesized_range(left.into(), parent, comment_ranges, locator.contents())
|
||||
.unwrap_or(left.range()),
|
||||
),
|
||||
);
|
||||
|
||||
for (op, comparator) in ops.iter().zip(comparators) {
|
||||
// Add the operator.
|
||||
@@ -46,8 +51,13 @@ pub(super) fn generate_comparison(
|
||||
// Add the right side of the comparison.
|
||||
contents.push_str(
|
||||
locator.slice(
|
||||
parenthesized_range(comparator.into(), parent, locator.contents())
|
||||
.unwrap_or(comparator.range()),
|
||||
parenthesized_range(
|
||||
comparator.into(),
|
||||
parent,
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.unwrap_or(comparator.range()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -283,6 +283,7 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp
|
||||
&ops,
|
||||
&compare.comparators,
|
||||
compare.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator(),
|
||||
);
|
||||
for diagnostic in &mut diagnostics {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::autofix::edits::pad;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
@@ -95,11 +96,16 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) {
|
||||
let mut diagnostic = Diagnostic::new(NotInTest, unary_op.operand.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
generate_comparison(
|
||||
left,
|
||||
&[CmpOp::NotIn],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
pad(
|
||||
generate_comparison(
|
||||
left,
|
||||
&[CmpOp::NotIn],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator(),
|
||||
),
|
||||
unary_op.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
unary_op.range(),
|
||||
@@ -113,11 +119,16 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) {
|
||||
let mut diagnostic = Diagnostic::new(NotIsTest, unary_op.operand.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
generate_comparison(
|
||||
left,
|
||||
&[CmpOp::IsNot],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
pad(
|
||||
generate_comparison(
|
||||
left,
|
||||
&[CmpOp::IsNot],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator(),
|
||||
),
|
||||
unary_op.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
unary_op.range(),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_trivia::leading_indentation;
|
||||
use ruff_source_file::Line;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for indentation that uses tabs.
|
||||
@@ -37,17 +38,46 @@ impl Violation for TabIndentation {
|
||||
}
|
||||
|
||||
/// W191
|
||||
pub(crate) fn tab_indentation(line: &Line, indexer: &Indexer) -> Option<Diagnostic> {
|
||||
let indent = leading_indentation(line);
|
||||
if let Some(tab_index) = indent.find('\t') {
|
||||
// If the tab character is within a multi-line string, abort.
|
||||
let tab_offset = line.start() + TextSize::try_from(tab_index).unwrap();
|
||||
if indexer.triple_quoted_string_range(tab_offset).is_none() {
|
||||
return Some(Diagnostic::new(
|
||||
TabIndentation,
|
||||
TextRange::at(line.start(), indent.text_len()),
|
||||
));
|
||||
pub(crate) fn tab_indentation(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
// Always check the first line for tab indentation as there's no newline
|
||||
// token before it.
|
||||
tab_indentation_at_line_start(diagnostics, locator, TextSize::default());
|
||||
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::Newline | Tok::NonLogicalNewline) {
|
||||
tab_indentation_at_line_start(diagnostics, locator, range.end());
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
// The lexer doesn't emit `Newline` / `NonLogicalNewline` for a line
|
||||
// continuation character (`\`), so we need to manually check for tab
|
||||
// indentation for lines that follow a line continuation character.
|
||||
for continuation_line in indexer.continuation_line_starts() {
|
||||
tab_indentation_at_line_start(
|
||||
diagnostics,
|
||||
locator,
|
||||
locator.full_line_end(*continuation_line),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for indentation that uses tabs for a line starting at
|
||||
/// the given [`TextSize`].
|
||||
fn tab_indentation_at_line_start(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
locator: &Locator,
|
||||
line_start: TextSize,
|
||||
) {
|
||||
let indent = leading_indentation(locator.after(line_start));
|
||||
if indent.find('\t').is_some() {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
TabIndentation,
|
||||
TextRange::at(line_start, indent.text_len()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +102,20 @@ E713.py:14:9: E713 [*] Test for membership should be `not in`
|
||||
16 16 |
|
||||
17 17 | #: Okay
|
||||
|
||||
E713.py:40:12: E713 [*] Test for membership should be `not in`
|
||||
|
|
||||
38 | assert [42, not foo] in bar
|
||||
39 | assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
40 | assert not('name' in request)or not request['name']
|
||||
| ^^^^^^^^^^^^^^^^^ E713
|
||||
|
|
||||
= help: Convert to `not in`
|
||||
|
||||
ℹ Fix
|
||||
37 37 | assert {"x": not foo} in bar
|
||||
38 38 | assert [42, not foo] in bar
|
||||
39 39 | assert not (re.search(r"^.:\\Users\\[^\\]*\\Downloads\\.*") is None)
|
||||
40 |-assert not('name' in request)or not request['name']
|
||||
40 |+assert 'name' not in request or not request['name']
|
||||
|
||||
|
||||
|
||||
@@ -1,345 +1,352 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
W19.py:3:1: W191 Indentation contains tabs
|
||||
W19.py:1:1: W191 Indentation contains tabs
|
||||
|
|
||||
1 | #: W191
|
||||
2 | if False:
|
||||
3 | print # indented with 1 tab
|
||||
1 | '''File starts with a tab
|
||||
| ^^^^ W191
|
||||
4 | #:
|
||||
2 | multiline string with tab in it'''
|
||||
|
|
||||
|
||||
W19.py:9:1: W191 Indentation contains tabs
|
||||
W19.py:6:1: W191 Indentation contains tabs
|
||||
|
|
||||
4 | #: W191
|
||||
5 | if False:
|
||||
6 | print # indented with 1 tab
|
||||
| ^^^^ W191
|
||||
7 | #:
|
||||
|
|
||||
|
||||
W19.py:12:1: W191 Indentation contains tabs
|
||||
|
|
||||
7 | #: W191
|
||||
8 | y = x == 2 \
|
||||
9 | or x == 3
|
||||
10 | #: W191
|
||||
11 | y = x == 2 \
|
||||
12 | or x == 3
|
||||
| ^^^^ W191
|
||||
10 | #: E101 W191 W504
|
||||
11 | if (
|
||||
13 | #: E101 W191 W504
|
||||
14 | if (
|
||||
|
|
||||
|
||||
W19.py:16:1: W191 Indentation contains tabs
|
||||
W19.py:19:1: W191 Indentation contains tabs
|
||||
|
|
||||
14 | ) or
|
||||
15 | y == 4):
|
||||
16 | pass
|
||||
17 | ) or
|
||||
18 | y == 4):
|
||||
19 | pass
|
||||
| ^^^^ W191
|
||||
17 | #: E101 W191
|
||||
18 | if x == 2 \
|
||||
20 | #: E101 W191
|
||||
21 | if x == 2 \
|
||||
|
|
||||
|
||||
W19.py:21:1: W191 Indentation contains tabs
|
||||
W19.py:24:1: W191 Indentation contains tabs
|
||||
|
|
||||
19 | or y > 1 \
|
||||
20 | or x == 3:
|
||||
21 | pass
|
||||
22 | or y > 1 \
|
||||
23 | or x == 3:
|
||||
24 | pass
|
||||
| ^^^^ W191
|
||||
22 | #: E101 W191
|
||||
23 | if x == 2 \
|
||||
25 | #: E101 W191
|
||||
26 | if x == 2 \
|
||||
|
|
||||
|
||||
W19.py:26:1: W191 Indentation contains tabs
|
||||
W19.py:29:1: W191 Indentation contains tabs
|
||||
|
|
||||
24 | or y > 1 \
|
||||
25 | or x == 3:
|
||||
26 | pass
|
||||
27 | or y > 1 \
|
||||
28 | or x == 3:
|
||||
29 | pass
|
||||
| ^^^^ W191
|
||||
27 | #:
|
||||
30 | #:
|
||||
|
|
||||
|
||||
W19.py:32:1: W191 Indentation contains tabs
|
||||
W19.py:35:1: W191 Indentation contains tabs
|
||||
|
|
||||
30 | if (foo == bar and
|
||||
31 | baz == bop):
|
||||
32 | pass
|
||||
33 | if (foo == bar and
|
||||
34 | baz == bop):
|
||||
35 | pass
|
||||
| ^^^^ W191
|
||||
33 | #: E101 W191 W504
|
||||
34 | if (
|
||||
36 | #: E101 W191 W504
|
||||
37 | if (
|
||||
|
|
||||
|
||||
W19.py:38:1: W191 Indentation contains tabs
|
||||
W19.py:41:1: W191 Indentation contains tabs
|
||||
|
|
||||
36 | baz == bop
|
||||
37 | ):
|
||||
38 | pass
|
||||
39 | baz == bop
|
||||
40 | ):
|
||||
41 | pass
|
||||
| ^^^^ W191
|
||||
39 | #:
|
||||
42 | #:
|
||||
|
|
||||
|
||||
W19.py:44:1: W191 Indentation contains tabs
|
||||
W19.py:47:1: W191 Indentation contains tabs
|
||||
|
|
||||
42 | if start[1] > end_col and not (
|
||||
43 | over_indent == 4 and indent_next):
|
||||
44 | return (0, "E121 continuation line over-"
|
||||
45 | if start[1] > end_col and not (
|
||||
46 | over_indent == 4 and indent_next):
|
||||
47 | return (0, "E121 continuation line over-"
|
||||
| ^^^^ W191
|
||||
45 | "indented for visual indent")
|
||||
46 | #:
|
||||
48 | "indented for visual indent")
|
||||
49 | #:
|
||||
|
|
||||
|
||||
W19.py:45:1: W191 Indentation contains tabs
|
||||
W19.py:48:1: W191 Indentation contains tabs
|
||||
|
|
||||
43 | over_indent == 4 and indent_next):
|
||||
44 | return (0, "E121 continuation line over-"
|
||||
45 | "indented for visual indent")
|
||||
46 | over_indent == 4 and indent_next):
|
||||
47 | return (0, "E121 continuation line over-"
|
||||
48 | "indented for visual indent")
|
||||
| ^^^^^^^^^^^^ W191
|
||||
46 | #:
|
||||
49 | #:
|
||||
|
|
||||
|
||||
W19.py:54:1: W191 Indentation contains tabs
|
||||
W19.py:57:1: W191 Indentation contains tabs
|
||||
|
|
||||
52 | var_one, var_two, var_three,
|
||||
53 | var_four):
|
||||
54 | print(var_one)
|
||||
55 | var_one, var_two, var_three,
|
||||
56 | var_four):
|
||||
57 | print(var_one)
|
||||
| ^^^^ W191
|
||||
55 | #: E101 W191 W504
|
||||
56 | if ((row < 0 or self.moduleCount <= row or
|
||||
|
|
||||
|
||||
W19.py:58:1: W191 Indentation contains tabs
|
||||
|
|
||||
56 | if ((row < 0 or self.moduleCount <= row or
|
||||
57 | col < 0 or self.moduleCount <= col)):
|
||||
58 | raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
|
||||
| ^^^^ W191
|
||||
59 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
60 | if bar:
|
||||
58 | #: E101 W191 W504
|
||||
59 | if ((row < 0 or self.moduleCount <= row or
|
||||
|
|
||||
|
||||
W19.py:61:1: W191 Indentation contains tabs
|
||||
|
|
||||
59 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
60 | if bar:
|
||||
61 | return (
|
||||
59 | if ((row < 0 or self.moduleCount <= row or
|
||||
60 | col < 0 or self.moduleCount <= col)):
|
||||
61 | raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
|
||||
| ^^^^ W191
|
||||
62 | start, 'E121 lines starting with a '
|
||||
63 | 'closing bracket should be indented '
|
||||
|
|
||||
|
||||
W19.py:62:1: W191 Indentation contains tabs
|
||||
|
|
||||
60 | if bar:
|
||||
61 | return (
|
||||
62 | start, 'E121 lines starting with a '
|
||||
| ^^^^^^^^ W191
|
||||
63 | 'closing bracket should be indented '
|
||||
64 | "to match that of the opening "
|
||||
|
|
||||
|
||||
W19.py:63:1: W191 Indentation contains tabs
|
||||
|
|
||||
61 | return (
|
||||
62 | start, 'E121 lines starting with a '
|
||||
63 | 'closing bracket should be indented '
|
||||
| ^^^^^^^^ W191
|
||||
64 | "to match that of the opening "
|
||||
65 | "bracket's line"
|
||||
62 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
63 | if bar:
|
||||
|
|
||||
|
||||
W19.py:64:1: W191 Indentation contains tabs
|
||||
|
|
||||
62 | start, 'E121 lines starting with a '
|
||||
63 | 'closing bracket should be indented '
|
||||
64 | "to match that of the opening "
|
||||
| ^^^^^^^^ W191
|
||||
65 | "bracket's line"
|
||||
66 | )
|
||||
62 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
63 | if bar:
|
||||
64 | return (
|
||||
| ^^^^ W191
|
||||
65 | start, 'E121 lines starting with a '
|
||||
66 | 'closing bracket should be indented '
|
||||
|
|
||||
|
||||
W19.py:65:1: W191 Indentation contains tabs
|
||||
|
|
||||
63 | 'closing bracket should be indented '
|
||||
64 | "to match that of the opening "
|
||||
65 | "bracket's line"
|
||||
63 | if bar:
|
||||
64 | return (
|
||||
65 | start, 'E121 lines starting with a '
|
||||
| ^^^^^^^^ W191
|
||||
66 | )
|
||||
67 | #
|
||||
66 | 'closing bracket should be indented '
|
||||
67 | "to match that of the opening "
|
||||
|
|
||||
|
||||
W19.py:66:1: W191 Indentation contains tabs
|
||||
|
|
||||
64 | "to match that of the opening "
|
||||
65 | "bracket's line"
|
||||
66 | )
|
||||
| ^^^^ W191
|
||||
67 | #
|
||||
68 | #: E101 W191 W504
|
||||
64 | return (
|
||||
65 | start, 'E121 lines starting with a '
|
||||
66 | 'closing bracket should be indented '
|
||||
| ^^^^^^^^ W191
|
||||
67 | "to match that of the opening "
|
||||
68 | "bracket's line"
|
||||
|
|
||||
|
||||
W19.py:73:1: W191 Indentation contains tabs
|
||||
W19.py:67:1: W191 Indentation contains tabs
|
||||
|
|
||||
71 | foo.bar("bop")
|
||||
72 | )):
|
||||
73 | print "yes"
|
||||
| ^^^^ W191
|
||||
74 | #: E101 W191 W504
|
||||
75 | # also ok, but starting to look like LISP
|
||||
65 | start, 'E121 lines starting with a '
|
||||
66 | 'closing bracket should be indented '
|
||||
67 | "to match that of the opening "
|
||||
| ^^^^^^^^ W191
|
||||
68 | "bracket's line"
|
||||
69 | )
|
||||
|
|
||||
|
||||
W19.py:78:1: W191 Indentation contains tabs
|
||||
W19.py:68:1: W191 Indentation contains tabs
|
||||
|
|
||||
76 | if ((foo.bar("baz") and
|
||||
77 | foo.bar("bop"))):
|
||||
78 | print "yes"
|
||||
| ^^^^ W191
|
||||
79 | #: E101 W191 W504
|
||||
80 | if (a == 2 or
|
||||
66 | 'closing bracket should be indented '
|
||||
67 | "to match that of the opening "
|
||||
68 | "bracket's line"
|
||||
| ^^^^^^^^ W191
|
||||
69 | )
|
||||
70 | #
|
||||
|
|
||||
|
||||
W19.py:83:1: W191 Indentation contains tabs
|
||||
W19.py:69:1: W191 Indentation contains tabs
|
||||
|
|
||||
81 | b == "abc def ghi"
|
||||
82 | "jkl mno"):
|
||||
83 | return True
|
||||
67 | "to match that of the opening "
|
||||
68 | "bracket's line"
|
||||
69 | )
|
||||
| ^^^^ W191
|
||||
84 | #: E101 W191 W504
|
||||
85 | if (a == 2 or
|
||||
70 | #
|
||||
71 | #: E101 W191 W504
|
||||
|
|
||||
|
||||
W19.py:88:1: W191 Indentation contains tabs
|
||||
W19.py:76:1: W191 Indentation contains tabs
|
||||
|
|
||||
86 | b == """abc def ghi
|
||||
87 | jkl mno"""):
|
||||
88 | return True
|
||||
74 | foo.bar("bop")
|
||||
75 | )):
|
||||
76 | print "yes"
|
||||
| ^^^^ W191
|
||||
89 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
90 | if length > options.max_line_length:
|
||||
77 | #: E101 W191 W504
|
||||
78 | # also ok, but starting to look like LISP
|
||||
|
|
||||
|
||||
W19.py:81:1: W191 Indentation contains tabs
|
||||
|
|
||||
79 | if ((foo.bar("baz") and
|
||||
80 | foo.bar("bop"))):
|
||||
81 | print "yes"
|
||||
| ^^^^ W191
|
||||
82 | #: E101 W191 W504
|
||||
83 | if (a == 2 or
|
||||
|
|
||||
|
||||
W19.py:86:1: W191 Indentation contains tabs
|
||||
|
|
||||
84 | b == "abc def ghi"
|
||||
85 | "jkl mno"):
|
||||
86 | return True
|
||||
| ^^^^ W191
|
||||
87 | #: E101 W191 W504
|
||||
88 | if (a == 2 or
|
||||
|
|
||||
|
||||
W19.py:91:1: W191 Indentation contains tabs
|
||||
|
|
||||
89 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
90 | if length > options.max_line_length:
|
||||
91 | return options.max_line_length, \
|
||||
89 | b == """abc def ghi
|
||||
90 | jkl mno"""):
|
||||
91 | return True
|
||||
| ^^^^ W191
|
||||
92 | "E501 line too long (%d characters)" % length
|
||||
92 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
93 | if length > options.max_line_length:
|
||||
|
|
||||
|
||||
W19.py:92:1: W191 Indentation contains tabs
|
||||
W19.py:94:1: W191 Indentation contains tabs
|
||||
|
|
||||
90 | if length > options.max_line_length:
|
||||
91 | return options.max_line_length, \
|
||||
92 | "E501 line too long (%d characters)" % length
|
||||
92 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
93 | if length > options.max_line_length:
|
||||
94 | return options.max_line_length, \
|
||||
| ^^^^ W191
|
||||
95 | "E501 line too long (%d characters)" % length
|
||||
|
|
||||
|
||||
W19.py:95:1: W191 Indentation contains tabs
|
||||
|
|
||||
93 | if length > options.max_line_length:
|
||||
94 | return options.max_line_length, \
|
||||
95 | "E501 line too long (%d characters)" % length
|
||||
| ^^^^^^^^ W191
|
||||
|
|
||||
|
||||
W19.py:98:1: W191 Indentation contains tabs
|
||||
W19.py:101:1: W191 Indentation contains tabs
|
||||
|
|
||||
96 | #: E101 W191 W191 W504
|
||||
97 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
98 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
99 | #: E101 W191 W191 W504
|
||||
100 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
101 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
| ^^^^ W191
|
||||
99 | self._pep8_options(targetfile))
|
||||
100 | #: W191 - okay
|
||||
102 | self._pep8_options(targetfile))
|
||||
103 | #: W191 - okay
|
||||
|
|
||||
|
||||
W19.py:99:1: W191 Indentation contains tabs
|
||||
W19.py:102:1: W191 Indentation contains tabs
|
||||
|
|
||||
97 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
98 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
99 | self._pep8_options(targetfile))
|
||||
100 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
101 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
102 | self._pep8_options(targetfile))
|
||||
| ^^^^^^^^^^^ W191
|
||||
100 | #: W191 - okay
|
||||
101 | '''
|
||||
103 | #: W191 - okay
|
||||
104 | '''
|
||||
|
|
||||
|
||||
W19.py:125:1: W191 Indentation contains tabs
|
||||
W19.py:128:1: W191 Indentation contains tabs
|
||||
|
|
||||
123 | if foo is None and bar is "bop" and \
|
||||
124 | blah == 'yeah':
|
||||
125 | blah = 'yeahnah'
|
||||
126 | if foo is None and bar is "bop" and \
|
||||
127 | blah == 'yeah':
|
||||
128 | blah = 'yeahnah'
|
||||
| ^^^^ W191
|
||||
|
|
||||
|
||||
W19.py:131:1: W191 Indentation contains tabs
|
||||
W19.py:134:1: W191 Indentation contains tabs
|
||||
|
|
||||
129 | #: W191 W191 W191
|
||||
130 | if True:
|
||||
131 | foo(
|
||||
132 | #: W191 W191 W191
|
||||
133 | if True:
|
||||
134 | foo(
|
||||
| ^^^^ W191
|
||||
132 | 1,
|
||||
133 | 2)
|
||||
135 | 1,
|
||||
136 | 2)
|
||||
|
|
||||
|
||||
W19.py:132:1: W191 Indentation contains tabs
|
||||
W19.py:135:1: W191 Indentation contains tabs
|
||||
|
|
||||
130 | if True:
|
||||
131 | foo(
|
||||
132 | 1,
|
||||
133 | if True:
|
||||
134 | foo(
|
||||
135 | 1,
|
||||
| ^^^^^^^^ W191
|
||||
133 | 2)
|
||||
134 | #: W191 W191 W191 W191 W191
|
||||
|
|
||||
|
||||
W19.py:133:1: W191 Indentation contains tabs
|
||||
|
|
||||
131 | foo(
|
||||
132 | 1,
|
||||
133 | 2)
|
||||
| ^^^^^^^^ W191
|
||||
134 | #: W191 W191 W191 W191 W191
|
||||
135 | def test_keys(self):
|
||||
136 | 2)
|
||||
137 | #: W191 W191 W191 W191 W191
|
||||
|
|
||||
|
||||
W19.py:136:1: W191 Indentation contains tabs
|
||||
|
|
||||
134 | #: W191 W191 W191 W191 W191
|
||||
135 | def test_keys(self):
|
||||
136 | """areas.json - All regions are accounted for."""
|
||||
| ^^^^ W191
|
||||
137 | expected = set([
|
||||
138 | u'Norrbotten',
|
||||
|
|
||||
|
||||
W19.py:137:1: W191 Indentation contains tabs
|
||||
|
|
||||
135 | def test_keys(self):
|
||||
136 | """areas.json - All regions are accounted for."""
|
||||
137 | expected = set([
|
||||
| ^^^^ W191
|
||||
138 | u'Norrbotten',
|
||||
139 | u'V\xe4sterbotten',
|
||||
|
|
||||
|
||||
W19.py:138:1: W191 Indentation contains tabs
|
||||
|
|
||||
136 | """areas.json - All regions are accounted for."""
|
||||
137 | expected = set([
|
||||
138 | u'Norrbotten',
|
||||
134 | foo(
|
||||
135 | 1,
|
||||
136 | 2)
|
||||
| ^^^^^^^^ W191
|
||||
139 | u'V\xe4sterbotten',
|
||||
140 | ])
|
||||
137 | #: W191 W191 W191 W191 W191
|
||||
138 | def test_keys(self):
|
||||
|
|
||||
|
||||
W19.py:139:1: W191 Indentation contains tabs
|
||||
|
|
||||
137 | expected = set([
|
||||
138 | u'Norrbotten',
|
||||
139 | u'V\xe4sterbotten',
|
||||
| ^^^^^^^^ W191
|
||||
140 | ])
|
||||
141 | #: W191
|
||||
137 | #: W191 W191 W191 W191 W191
|
||||
138 | def test_keys(self):
|
||||
139 | """areas.json - All regions are accounted for."""
|
||||
| ^^^^ W191
|
||||
140 | expected = set([
|
||||
141 | u'Norrbotten',
|
||||
|
|
||||
|
||||
W19.py:140:1: W191 Indentation contains tabs
|
||||
|
|
||||
138 | u'Norrbotten',
|
||||
139 | u'V\xe4sterbotten',
|
||||
140 | ])
|
||||
138 | def test_keys(self):
|
||||
139 | """areas.json - All regions are accounted for."""
|
||||
140 | expected = set([
|
||||
| ^^^^ W191
|
||||
141 | #: W191
|
||||
142 | x = [
|
||||
141 | u'Norrbotten',
|
||||
142 | u'V\xe4sterbotten',
|
||||
|
|
||||
|
||||
W19.py:141:1: W191 Indentation contains tabs
|
||||
|
|
||||
139 | """areas.json - All regions are accounted for."""
|
||||
140 | expected = set([
|
||||
141 | u'Norrbotten',
|
||||
| ^^^^^^^^ W191
|
||||
142 | u'V\xe4sterbotten',
|
||||
143 | ])
|
||||
|
|
||||
|
||||
W19.py:142:1: W191 Indentation contains tabs
|
||||
|
|
||||
140 | expected = set([
|
||||
141 | u'Norrbotten',
|
||||
142 | u'V\xe4sterbotten',
|
||||
| ^^^^^^^^ W191
|
||||
143 | ])
|
||||
144 | #: W191
|
||||
|
|
||||
|
||||
W19.py:143:1: W191 Indentation contains tabs
|
||||
|
|
||||
141 | #: W191
|
||||
142 | x = [
|
||||
143 | 'abc'
|
||||
141 | u'Norrbotten',
|
||||
142 | u'V\xe4sterbotten',
|
||||
143 | ])
|
||||
| ^^^^ W191
|
||||
144 | ]
|
||||
145 | #: W191 - okay
|
||||
144 | #: W191
|
||||
145 | x = [
|
||||
|
|
||||
|
||||
W19.py:146:1: W191 Indentation contains tabs
|
||||
|
|
||||
144 | #: W191
|
||||
145 | x = [
|
||||
146 | 'abc'
|
||||
| ^^^^ W191
|
||||
147 | ]
|
||||
148 | #: W191 - okay
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ use crate::registry::{AsRule, Rule};
|
||||
/// ## Options
|
||||
/// - `pydocstyle.convention`
|
||||
///
|
||||
/// [D211]: https://beta.ruff.rs/docs/rules/blank-line-before-class
|
||||
/// [D211]: https://docs.astral.sh/ruff/rules/blank-line-before-class
|
||||
#[violation]
|
||||
pub struct OneBlankLineBeforeClass;
|
||||
|
||||
@@ -136,7 +136,7 @@ impl AlwaysAutofixableViolation for OneBlankLineAfterClass {
|
||||
/// ## Options
|
||||
/// - `pydocstyle.convention`
|
||||
///
|
||||
/// [D203]: https://beta.ruff.rs/docs/rules/one-blank-line-before-class
|
||||
/// [D203]: https://docs.astral.sh/ruff/rules/one-blank-line-before-class
|
||||
#[violation]
|
||||
pub struct BlankLineBeforeClass;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ use crate::registry::{AsRule, Rule};
|
||||
/// """
|
||||
/// ```
|
||||
///
|
||||
/// [D213]: https://beta.ruff.rs/docs/rules/multi-line-summary-second-line
|
||||
/// [D213]: https://docs.astral.sh/ruff/rules/multi-line-summary-second-line
|
||||
#[violation]
|
||||
pub struct MultiLineSummaryFirstLine;
|
||||
|
||||
@@ -102,7 +102,7 @@ impl AlwaysAutofixableViolation for MultiLineSummaryFirstLine {
|
||||
/// """
|
||||
/// ```
|
||||
///
|
||||
/// [D212]: https://beta.ruff.rs/docs/rules/multi-line-summary-first-line
|
||||
/// [D212]: https://docs.astral.sh/ruff/rules/multi-line-summary-first-line
|
||||
#[violation]
|
||||
pub struct MultiLineSummarySecondLine;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_semantic::Binding;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_python_trivia::{BackwardsTokenizer, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -91,20 +91,27 @@ pub(crate) fn remove_exception_handler_assignment(
|
||||
bound_exception: &Binding,
|
||||
locator: &Locator,
|
||||
) -> Result<Edit> {
|
||||
// Lex backwards, to the token just before the `as`.
|
||||
// Find the position just after the exception name. This is a late pass so we only have the
|
||||
// binding and can't look its parent in the AST up anymore.
|
||||
// ```
|
||||
// except ZeroDivisionError as err:
|
||||
// ^^^ This is the bound_exception range
|
||||
// ^^^^ lex this range
|
||||
// ^ preceding_end (we want to remove from here)
|
||||
// ```
|
||||
// There can't be any comments in that range.
|
||||
let mut tokenizer =
|
||||
SimpleTokenizer::up_to_without_back_comment(bound_exception.start(), locator.contents())
|
||||
.skip_trivia();
|
||||
BackwardsTokenizer::up_to(bound_exception.start(), locator.contents(), &[]).skip_trivia();
|
||||
|
||||
// Eat the `as` token.
|
||||
let preceding = tokenizer
|
||||
.next_back()
|
||||
.next()
|
||||
.context("expected the exception name to be preceded by `as`")?;
|
||||
debug_assert!(matches!(preceding.kind, SimpleTokenKind::As));
|
||||
|
||||
// Lex to the end of the preceding token, which should be the exception value.
|
||||
let preceding = tokenizer
|
||||
.next_back()
|
||||
.next()
|
||||
.context("expected the exception name to be preceded by a token")?;
|
||||
|
||||
// Lex forwards, to the `:` token.
|
||||
|
||||
@@ -224,6 +224,7 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option<Fix> {
|
||||
let start = parenthesized_range(
|
||||
target.into(),
|
||||
statement.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(target.range())
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::noqa::Directive;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Ruff documentation](https://beta.ruff.rs/docs/configuration/#error-suppression)
|
||||
/// - [Ruff documentation](https://docs.astral.sh/ruff/configuration/#error-suppression)
|
||||
#[violation]
|
||||
pub struct BlanketNOQA;
|
||||
|
||||
|
||||
@@ -83,6 +83,9 @@ fn is_known_dunder_method(method: &str) -> bool {
|
||||
| "__aiter__"
|
||||
| "__and__"
|
||||
| "__anext__"
|
||||
| "__attrs_init__"
|
||||
| "__attrs_post_init__"
|
||||
| "__attrs_pre_init__"
|
||||
| "__await__"
|
||||
| "__bool__"
|
||||
| "__bytes__"
|
||||
@@ -120,6 +123,7 @@ fn is_known_dunder_method(method: &str) -> bool {
|
||||
| "__getstate__"
|
||||
| "__gt__"
|
||||
| "__hash__"
|
||||
| "__html__"
|
||||
| "__iadd__"
|
||||
| "__iand__"
|
||||
| "__ifloordiv__"
|
||||
|
||||
@@ -2,6 +2,7 @@ use itertools::Itertools;
|
||||
use ruff_python_ast::{self as ast, Arguments, BoolOp, Expr};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::autofix::edits::pad;
|
||||
use crate::autofix::snippet::SourceCodeSnippet;
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -133,7 +134,10 @@ pub(crate) fn repeated_isinstance_calls(
|
||||
expr.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(call, expr.range())));
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
pad(call, expr.range(), checker.locator()),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -124,4 +124,21 @@ repeated_isinstance_calls.py:30:14: PLR1701 [*] Merge `isinstance` calls: `isins
|
||||
32 32 | # not merged but valid
|
||||
33 33 | result = isinstance(var[5], int) and var[5] * 14 or isinstance(var[5], float) and var[5] * 14.4
|
||||
|
||||
repeated_isinstance_calls.py:42:3: PLR1701 [*] Merge `isinstance` calls: `isinstance(self.k, float | int)`
|
||||
|
|
||||
41 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
42 | if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1701
|
||||
43 | ...
|
||||
|
|
||||
= help: Replace with `isinstance(self.k, float | int)`
|
||||
|
||||
ℹ Fix
|
||||
39 39 |
|
||||
40 40 |
|
||||
41 41 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
42 |-if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
42 |+if isinstance(self.k, float | int):
|
||||
43 43 | ...
|
||||
|
||||
|
||||
|
||||
@@ -124,4 +124,21 @@ repeated_isinstance_calls.py:30:14: PLR1701 [*] Merge `isinstance` calls: `isins
|
||||
32 32 | # not merged but valid
|
||||
33 33 | result = isinstance(var[5], int) and var[5] * 14 or isinstance(var[5], float) and var[5] * 14.4
|
||||
|
||||
repeated_isinstance_calls.py:42:3: PLR1701 [*] Merge `isinstance` calls: `isinstance(self.k, (float, int))`
|
||||
|
|
||||
41 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
42 | if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR1701
|
||||
43 | ...
|
||||
|
|
||||
= help: Replace with `isinstance(self.k, (float, int))`
|
||||
|
||||
ℹ Fix
|
||||
39 39 |
|
||||
40 40 |
|
||||
41 41 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
|
||||
42 |-if(isinstance(self.k, int)) or (isinstance(self.k, float)):
|
||||
42 |+if isinstance(self.k, (float, int)):
|
||||
43 43 | ...
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use malachite::Integer;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
@@ -47,7 +46,7 @@ impl From<LiteralType> for Constant {
|
||||
value: Vec::new(),
|
||||
implicit_concatenated: false,
|
||||
}),
|
||||
LiteralType::Int => Constant::Int(BigInt::from(0)),
|
||||
LiteralType::Int => Constant::Int(Integer::from(0)),
|
||||
LiteralType::Float => Constant::Float(0.0),
|
||||
LiteralType::Bool => Constant::Bool(false),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use malachite::Integer;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -122,31 +122,24 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||
..
|
||||
}) => {
|
||||
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);
|
||||
}
|
||||
if *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 if *number == 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);
|
||||
}
|
||||
_ => {}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +149,7 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
fn compare_version(target_version: &[Integer], py_version: PythonVersion, or_equal: bool) -> bool {
|
||||
let mut target_version_iter = target_version.iter();
|
||||
|
||||
let Some(if_major) = target_version_iter.next() else {
|
||||
@@ -165,7 +158,7 @@ fn compare_version(target_version: &[u32], py_version: PythonVersion, or_equal:
|
||||
|
||||
let (py_major, py_minor) = py_version.as_tuple();
|
||||
|
||||
match if_major.cmp(&py_major) {
|
||||
match if_major.cmp(&Integer::from(py_major)) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Greater => false,
|
||||
Ordering::Equal => {
|
||||
@@ -353,26 +346,16 @@ fn fix_always_true_branch(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the version from the tuple
|
||||
fn extract_version(elts: &[Expr]) -> Vec<u32> {
|
||||
let mut version: Vec<u32> = vec![];
|
||||
fn extract_version(elts: &[Expr]) -> Vec<Integer> {
|
||||
let mut version = Vec::new();
|
||||
for elt in elts {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(item),
|
||||
value: Constant::Int(number),
|
||||
..
|
||||
}) = &elt
|
||||
{
|
||||
let number = bigint_to_u32(item);
|
||||
version.push(number);
|
||||
version.push(number.clone());
|
||||
} else {
|
||||
return version;
|
||||
}
|
||||
@@ -403,6 +386,13 @@ mod tests {
|
||||
or_equal: bool,
|
||||
expected: bool,
|
||||
) {
|
||||
assert_eq!(compare_version(version_vec, version, or_equal), expected);
|
||||
let version_integers = version_vec
|
||||
.iter()
|
||||
.map(|x| Integer::from(*x))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
compare_version(&version_integers, version, or_equal),
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use crate::autofix::edits::pad;
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -76,7 +77,7 @@ pub(crate) fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr,
|
||||
let builtin = primitive.builtin();
|
||||
if checker.semantic().is_builtin(&builtin) {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
primitive.builtin(),
|
||||
pad(primitive.builtin(), expr.range(), checker.locator()),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::autofix::edits::{remove_argument, Parentheses};
|
||||
use crate::autofix::edits::{pad, remove_argument, Parentheses};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
|
||||
@@ -151,7 +151,10 @@ fn replace_with_bytes_literal(
|
||||
prev = range.end();
|
||||
}
|
||||
|
||||
Fix::automatic(Edit::range_replacement(replacement, call.range()))
|
||||
Fix::automatic(Edit::range_replacement(
|
||||
pad(replacement, call.range(), locator),
|
||||
call.range(),
|
||||
))
|
||||
}
|
||||
|
||||
/// UP012
|
||||
|
||||
@@ -108,6 +108,7 @@ pub(crate) fn yield_in_for_loop(checker: &mut Checker, stmt_for: &ast::StmtFor)
|
||||
parenthesized_range(
|
||||
iter.as_ref().into(),
|
||||
stmt_for.into(),
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(iter.range()),
|
||||
|
||||
@@ -96,4 +96,19 @@ UP003.py:5:1: UP003 [*] Use `complex` instead of `type(...)`
|
||||
7 7 | # OK
|
||||
8 8 | type(arg)(" ")
|
||||
|
||||
UP003.py:14:29: UP003 [*] Use `str` instead of `type(...)`
|
||||
|
|
||||
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459841
|
||||
14 | assert isinstance(fullname, type("")is not True)
|
||||
| ^^^^^^^^ UP003
|
||||
|
|
||||
= help: Replace `type(...)` with `str`
|
||||
|
||||
ℹ Fix
|
||||
11 11 | y = x.dtype.type(0.0)
|
||||
12 12 |
|
||||
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459841
|
||||
14 |-assert isinstance(fullname, type("")is not True)
|
||||
14 |+assert isinstance(fullname, str is not True)
|
||||
|
||||
|
||||
|
||||
@@ -410,5 +410,8 @@ UP007.py:110:28: UP007 [*] Use `X | Y` for type annotations
|
||||
109 109 | class ServiceRefOrValue:
|
||||
110 |- service_specification: Optional[str]is not True = None
|
||||
110 |+ service_specification: str | None is not True = None
|
||||
111 111 |
|
||||
112 112 |
|
||||
113 113 | # Regression test for: https://github.com/astral-sh/ruff/issues/7452
|
||||
|
||||
|
||||
|
||||
@@ -510,6 +510,7 @@ UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
75 |+(f"foo{bar}").encode()
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
78 78 |
|
||||
|
||||
UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
@@ -528,6 +529,8 @@ UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
76 |-("unicode text©").encode("utf-8")
|
||||
76 |+("unicode text©").encode()
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
78 78 |
|
||||
79 79 |
|
||||
|
||||
UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
@@ -544,5 +547,24 @@ UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 |-("unicode text©").encode(encoding="utf-8")
|
||||
77 |+("unicode text©").encode()
|
||||
78 78 |
|
||||
79 79 |
|
||||
80 80 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459882
|
||||
|
||||
UP012.py:82:17: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
|
|
||||
80 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459882
|
||||
81 | def _match_ignore(line):
|
||||
82 | input=stdin and'\n'.encode()or None
|
||||
| ^^^^^^^^^^^^^ UP012
|
||||
|
|
||||
= help: Rewrite as bytes literal
|
||||
|
||||
ℹ Fix
|
||||
79 79 |
|
||||
80 80 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722459882
|
||||
81 81 | def _match_ignore(line):
|
||||
82 |- input=stdin and'\n'.encode()or None
|
||||
82 |+ input=stdin and b'\n' or None
|
||||
|
||||
|
||||
|
||||
@@ -17,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::ReimplementedStarmap, Path::new("FURB140.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());
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
pub(crate) use check_and_remove_from_set::*;
|
||||
pub(crate) use delete_full_slice::*;
|
||||
pub(crate) use reimplemented_starmap::*;
|
||||
pub(crate) use repeated_append::*;
|
||||
pub(crate) use slice_copy::*;
|
||||
|
||||
mod check_and_remove_from_set;
|
||||
mod delete_full_slice;
|
||||
mod reimplemented_starmap;
|
||||
mod repeated_append;
|
||||
mod slice_copy;
|
||||
|
||||
332
crates/ruff/src/rules/refurb/rules/reimplemented_starmap.rs
Normal file
332
crates/ruff/src/rules/refurb/rules/reimplemented_starmap.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for generator expressions, list and set comprehensions that can
|
||||
/// be replaced with `itertools.starmap`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When unpacking values from iterators to pass them directly to
|
||||
/// a function, prefer `itertools.starmap`.
|
||||
///
|
||||
/// Using `itertools.starmap` is more concise and readable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// scores = [85, 100, 60]
|
||||
/// passing_scores = [60, 80, 70]
|
||||
///
|
||||
///
|
||||
/// def passed_test(score: int, passing_score: int) -> bool:
|
||||
/// return score >= passing_score
|
||||
///
|
||||
///
|
||||
/// passed_all_tests = all(
|
||||
/// passed_test(score, passing_score)
|
||||
/// for score, passing_score in zip(scores, passing_scores)
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from itertools import starmap
|
||||
///
|
||||
///
|
||||
/// scores = [85, 100, 60]
|
||||
/// passing_scores = [60, 80, 70]
|
||||
///
|
||||
///
|
||||
/// def passed_test(score: int, passing_score: int) -> bool:
|
||||
/// return score >= passing_score
|
||||
///
|
||||
///
|
||||
/// passed_all_tests = all(starmap(passed_test, zip(scores, passing_scores)))
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `itertools.starmap`](https://docs.python.org/3/library/itertools.html#itertools.starmap)
|
||||
#[violation]
|
||||
pub struct ReimplementedStarmap;
|
||||
|
||||
impl Violation for ReimplementedStarmap {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use `itertools.starmap` instead of the generator")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `itertools.starmap`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// FURB140
|
||||
pub(crate) fn reimplemented_starmap(checker: &mut Checker, target: &StarmapCandidate) {
|
||||
// Generator should have exactly one comprehension.
|
||||
let [comprehension @ ast::Comprehension { .. }] = target.generators() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// This comprehension should have a form:
|
||||
// ```python
|
||||
// (x, y, z, ...) in iter
|
||||
// ```
|
||||
//
|
||||
// `x, y, z, ...` are what we call `elts` for short.
|
||||
let Some((elts, iter)) = match_comprehension(comprehension) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Generator should produce one element that should look like:
|
||||
// ```python
|
||||
// func(a, b, c, ...)
|
||||
// ```
|
||||
//
|
||||
// here we refer to `a, b, c, ...` as `args`.
|
||||
//
|
||||
// NOTE: `func` is not necessarily just a function name, it can be an attribute access,
|
||||
// or even a call itself.
|
||||
let Some((args, func)) = match_call(target.element()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Here we want to check that `args` and `elts` are the same (same length, same elements,
|
||||
// same order).
|
||||
if elts.len() != args.len()
|
||||
|| !std::iter::zip(elts, args)
|
||||
.all(|(x, y)| ComparableExpr::from(x) == ComparableExpr::from(y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(ReimplementedStarmap, target.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
// Try importing `starmap` from `itertools`.
|
||||
//
|
||||
// It is not required to be `itertools.starmap`, though. The user might've already
|
||||
// imported it. Maybe even under a different name. So, we should use that name
|
||||
// for fix construction.
|
||||
let (import_edit, starmap_name) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("itertools", "starmap"),
|
||||
target.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
// The actual fix suggestion depends on what type of expression we were looking at.
|
||||
//
|
||||
// - For generator expressions, we use `starmap` call directly.
|
||||
// - For list and set comprehensions, we'd want to wrap it with `list` and `set`
|
||||
// correspondingly.
|
||||
let main_edit = Edit::range_replacement(
|
||||
target.try_make_suggestion(starmap_name, iter, func, checker)?,
|
||||
target.range(),
|
||||
);
|
||||
Ok(Fix::suggested_edits(import_edit, [main_edit]))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// An enum for a node that can be considered a candidate for replacement with `starmap`.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum StarmapCandidate<'a> {
|
||||
Generator(&'a ast::ExprGeneratorExp),
|
||||
ListComp(&'a ast::ExprListComp),
|
||||
SetComp(&'a ast::ExprSetComp),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprGeneratorExp> for StarmapCandidate<'a> {
|
||||
fn from(generator: &'a ast::ExprGeneratorExp) -> Self {
|
||||
Self::Generator(generator)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprListComp> for StarmapCandidate<'a> {
|
||||
fn from(list_comp: &'a ast::ExprListComp) -> Self {
|
||||
Self::ListComp(list_comp)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprSetComp> for StarmapCandidate<'a> {
|
||||
fn from(set_comp: &'a ast::ExprSetComp) -> Self {
|
||||
Self::SetComp(set_comp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for StarmapCandidate<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
Self::Generator(generator) => generator.range(),
|
||||
Self::ListComp(list_comp) => list_comp.range(),
|
||||
Self::SetComp(set_comp) => set_comp.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StarmapCandidate<'_> {
|
||||
/// Return the generated element for the candidate.
|
||||
pub(crate) fn element(&self) -> &Expr {
|
||||
match self {
|
||||
Self::Generator(generator) => generator.elt.as_ref(),
|
||||
Self::ListComp(list_comp) => list_comp.elt.as_ref(),
|
||||
Self::SetComp(set_comp) => set_comp.elt.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the generator comprehensions for the candidate.
|
||||
pub(crate) fn generators(&self) -> &[ast::Comprehension] {
|
||||
match self {
|
||||
Self::Generator(generator) => generator.generators.as_slice(),
|
||||
Self::ListComp(list_comp) => list_comp.generators.as_slice(),
|
||||
Self::SetComp(set_comp) => set_comp.generators.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to produce a fix suggestion transforming this node into a call to `starmap`.
|
||||
pub(crate) fn try_make_suggestion(
|
||||
&self,
|
||||
name: String,
|
||||
iter: &Expr,
|
||||
func: &Expr,
|
||||
checker: &Checker,
|
||||
) -> Result<String> {
|
||||
match self {
|
||||
Self::Generator(_) => {
|
||||
// For generator expressions, we replace:
|
||||
// ```python
|
||||
// (foo(...) for ... in iter)
|
||||
// ```
|
||||
//
|
||||
// with:
|
||||
// ```python
|
||||
// itertools.starmap(foo, iter)
|
||||
// ```
|
||||
let call = construct_starmap_call(name, iter, func);
|
||||
Ok(checker.generator().expr(&call.into()))
|
||||
}
|
||||
Self::ListComp(_) => {
|
||||
// For list comprehensions, we replace:
|
||||
// ```python
|
||||
// [foo(...) for ... in iter]
|
||||
// ```
|
||||
//
|
||||
// with:
|
||||
// ```python
|
||||
// list(itertools.starmap(foo, iter))
|
||||
// ```
|
||||
try_construct_call(name, iter, func, "list", checker)
|
||||
}
|
||||
Self::SetComp(_) => {
|
||||
// For set comprehensions, we replace:
|
||||
// ```python
|
||||
// {foo(...) for ... in iter}
|
||||
// ```
|
||||
//
|
||||
// with:
|
||||
// ```python
|
||||
// set(itertools.starmap(foo, iter))
|
||||
// ```
|
||||
try_construct_call(name, iter, func, "set", checker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try constructing the call to `itertools.starmap` and wrapping it with the given builtin.
|
||||
fn try_construct_call(
|
||||
name: String,
|
||||
iter: &Expr,
|
||||
func: &Expr,
|
||||
builtin: &str,
|
||||
checker: &Checker,
|
||||
) -> Result<String> {
|
||||
// We can only do our fix if `builtin` identifier is still bound to
|
||||
// the built-in type.
|
||||
if !checker.semantic().is_builtin(builtin) {
|
||||
bail!(format!("Can't use built-in `{builtin}` constructor"))
|
||||
}
|
||||
|
||||
// In general, we replace:
|
||||
// ```python
|
||||
// foo(...) for ... in iter
|
||||
// ```
|
||||
//
|
||||
// with:
|
||||
// ```python
|
||||
// builtin(itertools.starmap(foo, iter))
|
||||
// ```
|
||||
// where `builtin` is a constructor for a target collection.
|
||||
let call = construct_starmap_call(name, iter, func);
|
||||
let wrapped = wrap_with_call_to(call, builtin);
|
||||
Ok(checker.generator().expr(&wrapped.into()))
|
||||
}
|
||||
|
||||
/// Construct the call to `itertools.starmap` for suggestion.
|
||||
fn construct_starmap_call(starmap_binding: String, iter: &Expr, func: &Expr) -> ast::ExprCall {
|
||||
let starmap = ast::ExprName {
|
||||
id: starmap_binding,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
ast::ExprCall {
|
||||
func: Box::new(starmap.into()),
|
||||
arguments: ast::Arguments {
|
||||
args: vec![func.clone(), iter.clone()],
|
||||
keywords: vec![],
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap given function call with yet another call.
|
||||
fn wrap_with_call_to(call: ast::ExprCall, func_name: &str) -> ast::ExprCall {
|
||||
let name = ast::ExprName {
|
||||
id: func_name.to_string(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
ast::ExprCall {
|
||||
func: Box::new(name.into()),
|
||||
arguments: ast::Arguments {
|
||||
args: vec![call.into()],
|
||||
keywords: vec![],
|
||||
range: TextRange::default(),
|
||||
},
|
||||
range: TextRange::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Match that the given comprehension is `(x, y, z, ...) in iter`.
|
||||
fn match_comprehension(comprehension: &ast::Comprehension) -> Option<(&[Expr], &Expr)> {
|
||||
if comprehension.is_async || !comprehension.ifs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ast::ExprTuple { elts, .. } = comprehension.target.as_tuple_expr()?;
|
||||
Some((elts, &comprehension.iter))
|
||||
}
|
||||
|
||||
/// Match that the given expression is `func(x, y, z, ...)`.
|
||||
fn match_call(element: &Expr) -> Option<(&[Expr], &Expr)> {
|
||||
let ast::ExprCall {
|
||||
func,
|
||||
arguments: ast::Arguments { args, keywords, .. },
|
||||
..
|
||||
} = element.as_call_expr()?;
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((args, func))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB140.py:7:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
6 | # FURB140
|
||||
7 | [print(x, y) for x, y in zipped()]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
8 |
|
||||
9 | # FURB140
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 |+from itertools import starmap
|
||||
1 2 | def zipped():
|
||||
2 3 | return zip([1, 2, 3], "ABC")
|
||||
3 4 |
|
||||
4 5 | # Errors.
|
||||
5 6 |
|
||||
6 7 | # FURB140
|
||||
7 |-[print(x, y) for x, y in zipped()]
|
||||
8 |+list(starmap(print, zipped()))
|
||||
8 9 |
|
||||
9 10 | # FURB140
|
||||
10 11 | (print(x, y) for x, y in zipped())
|
||||
|
||||
FURB140.py:10:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
9 | # FURB140
|
||||
10 | (print(x, y) for x, y in zipped())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
11 |
|
||||
12 | # FURB140
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 |+from itertools import starmap
|
||||
1 2 | def zipped():
|
||||
2 3 | return zip([1, 2, 3], "ABC")
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
7 8 | [print(x, y) for x, y in zipped()]
|
||||
8 9 |
|
||||
9 10 | # FURB140
|
||||
10 |-(print(x, y) for x, y in zipped())
|
||||
11 |+starmap(print, zipped())
|
||||
11 12 |
|
||||
12 13 | # FURB140
|
||||
13 14 | {print(x, y) for x, y in zipped()}
|
||||
|
||||
FURB140.py:13:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
12 | # FURB140
|
||||
13 | {print(x, y) for x, y in zipped()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 |+from itertools import starmap
|
||||
1 2 | def zipped():
|
||||
2 3 | return zip([1, 2, 3], "ABC")
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | (print(x, y) for x, y in zipped())
|
||||
11 12 |
|
||||
12 13 | # FURB140
|
||||
13 |-{print(x, y) for x, y in zipped()}
|
||||
14 |+set(starmap(print, zipped()))
|
||||
14 15 |
|
||||
15 16 |
|
||||
16 17 | from itertools import starmap as sm
|
||||
|
||||
FURB140.py:19:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
18 | # FURB140
|
||||
19 | [print(x, y) for x, y in zipped()]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
20 |
|
||||
21 | # FURB140
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
16 16 | from itertools import starmap as sm
|
||||
17 17 |
|
||||
18 18 | # FURB140
|
||||
19 |-[print(x, y) for x, y in zipped()]
|
||||
19 |+list(sm(print, zipped()))
|
||||
20 20 |
|
||||
21 21 | # FURB140
|
||||
22 22 | (print(x, y) for x, y in zipped())
|
||||
|
||||
FURB140.py:22:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
21 | # FURB140
|
||||
22 | (print(x, y) for x, y in zipped())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
23 |
|
||||
24 | # FURB140
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
19 19 | [print(x, y) for x, y in zipped()]
|
||||
20 20 |
|
||||
21 21 | # FURB140
|
||||
22 |-(print(x, y) for x, y in zipped())
|
||||
22 |+sm(print, zipped())
|
||||
23 23 |
|
||||
24 24 | # FURB140
|
||||
25 25 | {print(x, y) for x, y in zipped()}
|
||||
|
||||
FURB140.py:25:1: FURB140 [*] Use `itertools.starmap` instead of the generator
|
||||
|
|
||||
24 | # FURB140
|
||||
25 | {print(x, y) for x, y in zipped()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB140
|
||||
26 |
|
||||
27 | # Non-errors.
|
||||
|
|
||||
= help: Replace with `itertools.starmap`
|
||||
|
||||
ℹ Suggested fix
|
||||
22 22 | (print(x, y) for x, y in zipped())
|
||||
23 23 |
|
||||
24 24 | # FURB140
|
||||
25 |-{print(x, y) for x, y in zipped()}
|
||||
25 |+set(sm(print, zipped()))
|
||||
26 26 |
|
||||
27 27 | # Non-errors.
|
||||
28 28 |
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use num_traits::ToPrimitive;
|
||||
use malachite::Integer;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, UnaryOp};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -46,11 +46,11 @@ impl Violation for PairwiseOverZipped {
|
||||
#[derive(Debug)]
|
||||
struct SliceInfo {
|
||||
arg_name: String,
|
||||
slice_start: Option<i64>,
|
||||
slice_start: Option<Integer>,
|
||||
}
|
||||
|
||||
impl SliceInfo {
|
||||
pub(crate) fn new(arg_name: String, slice_start: Option<i64>) -> Self {
|
||||
pub(crate) fn new(arg_name: String, slice_start: Option<Integer>) -> Self {
|
||||
Self {
|
||||
arg_name,
|
||||
slice_start,
|
||||
@@ -89,12 +89,12 @@ fn match_slice_info(expr: &Expr) -> Option<SliceInfo> {
|
||||
))
|
||||
}
|
||||
|
||||
fn to_bound(expr: &Expr) -> Option<i64> {
|
||||
fn to_bound(expr: &Expr) -> Option<Integer> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) => value.to_i64(),
|
||||
}) => Some(value.clone()),
|
||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
op: UnaryOp::USub | UnaryOp::Invert,
|
||||
operand,
|
||||
@@ -105,7 +105,7 @@ fn to_bound(expr: &Expr) -> Option<i64> {
|
||||
..
|
||||
}) = operand.as_ref()
|
||||
{
|
||||
value.to_i64().map(|v| -v)
|
||||
Some(-value.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -155,7 +155,10 @@ pub(crate) fn pairwise_over_zipped(checker: &mut Checker, func: &Expr, args: &[E
|
||||
}
|
||||
|
||||
// Verify that the arguments are successive.
|
||||
if second_arg_info.slice_start.unwrap_or(0) - first_arg_info.slice_start.unwrap_or(0) != 1 {
|
||||
if second_arg_info.slice_start.unwrap_or(Integer::from(0))
|
||||
- first_arg_info.slice_start.unwrap_or(Integer::from(0))
|
||||
!= 1
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use num_traits::Zero;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Arguments, Comprehension, Constant, Expr};
|
||||
@@ -115,7 +113,7 @@ fn is_head_slice(expr: &Expr) -> bool {
|
||||
..
|
||||
}) = expr
|
||||
{
|
||||
value.is_zero()
|
||||
*value == 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct UnusedCodes {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Automatic `noqa` management](https://beta.ruff.rs/docs/configuration/#automatic-noqa-management)
|
||||
/// - [Automatic `noqa` management](https://docs.astral.sh/ruff/configuration/#automatic-noqa-management)
|
||||
#[violation]
|
||||
pub struct UnusedNOQA {
|
||||
pub codes: Option<UnusedCodes>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user