Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67076b2dcb | ||
|
|
7e3ba7f32a | ||
|
|
09dbd2029c | ||
|
|
1380bd94da | ||
|
|
c10a4535b9 | ||
|
|
97802e7466 | ||
|
|
4fd4a65718 | ||
|
|
d78c614764 | ||
|
|
3f3dd7af99 | ||
|
|
871b92a385 | ||
|
|
9158f13ee6 | ||
|
|
72e0ffc1ac | ||
|
|
ffcf0618c7 | ||
|
|
1ccef5150d | ||
|
|
6a52577630 | ||
|
|
3c2f41b615 | ||
|
|
b76b4b6016 | ||
|
|
bbadbb5de5 | ||
|
|
ba6370e5d0 | ||
|
|
be6e00ef6e | ||
|
|
865205d992 | ||
|
|
572adf7994 | ||
|
|
3b26bf84f5 | ||
|
|
f4f88308ae | ||
|
|
ea3d3a655d | ||
|
|
fd34797d0f | ||
|
|
6532455672 | ||
|
|
257c571c43 | ||
|
|
ccdee55e6e | ||
|
|
6d6d7abf70 | ||
|
|
0096938789 | ||
|
|
853d8354cb | ||
|
|
5f64d2346f | ||
|
|
ddbe5a1243 | ||
|
|
04097d194c | ||
|
|
a2b8487ae3 | ||
|
|
8969ad5879 | ||
|
|
bfa1c28c00 | ||
|
|
cf7aa26aa4 | ||
|
|
d66ce76691 | ||
|
|
b8bb9e8b92 | ||
|
|
5e46dcbf21 | ||
|
|
045449ab12 | ||
|
|
d5ff8d7c43 | ||
|
|
d92fb11e80 | ||
|
|
3d947196f8 | ||
|
|
e846f2688b | ||
|
|
7b91a162c6 | ||
|
|
8c2cfade90 | ||
|
|
a435c0df4b | ||
|
|
48e1852893 | ||
|
|
03f141f53d | ||
|
|
8dea47afc1 | ||
|
|
d3b71f1e04 | ||
|
|
04e8e74499 | ||
|
|
318653c427 | ||
|
|
f08fd5cbf0 | ||
|
|
99a755f936 | ||
|
|
e7dfb35778 | ||
|
|
085fd37209 | ||
|
|
83536cf87b | ||
|
|
9366eb919d | ||
|
|
8be51942dd | ||
|
|
d365dab904 | ||
|
|
f23851130a | ||
|
|
efdf383f5e | ||
|
|
61f21a6513 | ||
|
|
43d6aa9173 | ||
|
|
c54e48dce5 | ||
|
|
b913e99bde | ||
|
|
4ac506526b | ||
|
|
cd41de2588 | ||
|
|
3344d367f5 | ||
|
|
d7a369e7dc | ||
|
|
1b1788c8ad | ||
|
|
4d5a339d9e | ||
|
|
0801f14046 | ||
|
|
edaf891042 | ||
|
|
3beff29026 | ||
|
|
5ac2c7d293 | ||
|
|
e66fdb83d0 | ||
|
|
a95bafefb0 | ||
|
|
539af34f58 | ||
|
|
983bb31577 | ||
|
|
b98b604071 | ||
|
|
cd27b39aff | ||
|
|
a9fc648faf | ||
|
|
c1f0661225 | ||
|
|
2c91412321 |
@@ -33,4 +33,5 @@ rustflags = [
|
||||
"-Wclippy::rc_buffer",
|
||||
"-Wclippy::rc_mutex",
|
||||
"-Wclippy::rest_pat_in_fully_bound_structs",
|
||||
"-Wunreachable_pub"
|
||||
]
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -237,5 +237,7 @@ jobs:
|
||||
run: python scripts/transform_readme.py --target mkdocs
|
||||
- name: "Generate docs"
|
||||
run: python scripts/generate_mkdocs.py
|
||||
- name: "Check docs formatting"
|
||||
run: python scripts/check_docs_formatted.py
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict
|
||||
|
||||
29
.github/workflows/release.yaml
vendored
29
.github/workflows/release.yaml
vendored
@@ -33,9 +33,10 @@ jobs:
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist --sdist
|
||||
- name: "Install built wheel - x86_64"
|
||||
- name: "Test wheel - x86_64"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -68,9 +69,10 @@ jobs:
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
args: --release --universal2 --out dist
|
||||
- name: "Install built wheel - universal2"
|
||||
- name: "Test wheel - universal2"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -113,11 +115,12 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: --release --out dist
|
||||
- name: "Install built wheel"
|
||||
- name: "Test wheel"
|
||||
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -158,10 +161,11 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
args: --release --out dist
|
||||
- name: "Install built wheel"
|
||||
- name: "Test wheel"
|
||||
if: ${{ startsWith(matrix.target, 'x86_64') }}
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -187,6 +191,9 @@ jobs:
|
||||
platform:
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
arch: aarch64
|
||||
# see https://github.com/charliermarsh/ruff/issues/3791
|
||||
# and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
arch: armv7
|
||||
- target: s390x-unknown-linux-gnu
|
||||
@@ -195,6 +202,7 @@ jobs:
|
||||
arch: ppc64le
|
||||
- target: powerpc64-unknown-linux-gnu
|
||||
arch: ppc64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -207,10 +215,11 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: auto
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
args: --release --out dist
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
if: matrix.platform.arch != 'ppc64'
|
||||
name: Install built wheel
|
||||
name: Test wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
distro: ubuntu20.04
|
||||
@@ -221,6 +230,7 @@ jobs:
|
||||
pip3 install -U pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -260,7 +270,7 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist
|
||||
- name: "Install built wheel"
|
||||
- name: "Test wheel"
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
@@ -269,6 +279,7 @@ jobs:
|
||||
run: |
|
||||
apk add py3-pip
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -294,8 +305,10 @@ jobs:
|
||||
platform:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
arch: aarch64
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -309,8 +322,9 @@ jobs:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
args: --release --out dist
|
||||
docker-options: ${{ matrix.platform.maturin_docker_options }}
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Install built wheel
|
||||
name: Test wheel
|
||||
with:
|
||||
arch: ${{ matrix.platform.arch }}
|
||||
distro: alpine_latest
|
||||
@@ -319,6 +333,7 @@ jobs:
|
||||
apk add py3-pip
|
||||
run: |
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff check --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,7 +3,8 @@
|
||||
crates/ruff/resources/test/cpython
|
||||
mkdocs.yml
|
||||
.overrides
|
||||
github_search.jsonl
|
||||
ruff-old
|
||||
github_search*.jsonl
|
||||
|
||||
###
|
||||
# Rust.gitignore
|
||||
|
||||
@@ -63,11 +63,6 @@ repos:
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: |
|
||||
(?x)^(
|
||||
crates/ruff/resources/.*|
|
||||
crates/ruff_python_formatter/resources/.*
|
||||
)$
|
||||
|
||||
ci:
|
||||
skip: [cargo-fmt, clippy, dev-generate-all]
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.266
|
||||
|
||||
### `update-check` is no longer a valid configuration option ([#4313](https://github.com/charliermarsh/ruff/pull/4313))
|
||||
|
||||
The `update-check` functionality was deprecated in [#2530](https://github.com/charliermarsh/ruff/pull/2530),
|
||||
in that the behavior itself was removed, and Ruff was changed to warn when that option was enabled.
|
||||
|
||||
Now, Ruff will throw an error when `update-check` is provided via a configuration file (e.g.,
|
||||
`update-check = false`) or through the command-line, since it has no effect. Users should remove
|
||||
this option from their configuration.
|
||||
|
||||
## 0.0.265
|
||||
|
||||
### `--fix-only` now exits with a zero exit code, unless `--exit-non-zero-on-fix` is specified ([#4146](https://github.com/charliermarsh/ruff/pull/4146))
|
||||
|
||||
278
Cargo.lock
generated
278
Cargo.lock
generated
@@ -144,15 +144,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
|
||||
dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.11"
|
||||
@@ -200,21 +191,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -223,9 +199,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.1.0"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -700,16 +676,6 @@ dependencies = [
|
||||
"dirs-sys 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
@@ -732,17 +698,6 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
@@ -767,15 +722,6 @@ version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
@@ -833,15 +779,9 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.265"
|
||||
version = "0.0.266"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.2.4",
|
||||
@@ -1168,37 +1108,11 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.19.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"bit-set",
|
||||
"diff",
|
||||
"ena",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"pico-args",
|
||||
"regex",
|
||||
"regex-syntax 0.6.29",
|
||||
"string_cache",
|
||||
"term",
|
||||
"tiny-keccak",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.19.9"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
@@ -1388,12 +1302,6 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nextest-workspace-hack"
|
||||
version = "0.1.0"
|
||||
@@ -1525,29 +1433,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.12"
|
||||
@@ -1624,23 +1509,13 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.1",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1650,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared 0.11.1",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1659,19 +1534,10 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.1",
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.1"
|
||||
@@ -1681,12 +1547,6 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
@@ -1738,12 +1598,6 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.0.3"
|
||||
@@ -1944,7 +1798,7 @@ checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.1",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.1",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1953,12 +1807,6 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
@@ -2004,11 +1852,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.265"
|
||||
version = "0.0.266"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"bitflags 2.1.0",
|
||||
"bitflags 2.2.1",
|
||||
"chrono",
|
||||
"clap 4.2.4",
|
||||
"colored",
|
||||
@@ -2093,7 +1941,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.265"
|
||||
version = "0.0.266"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2101,7 +1949,7 @@ dependencies = [
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"bincode",
|
||||
"bitflags 2.1.0",
|
||||
"bitflags 2.2.1",
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap 4.2.4",
|
||||
@@ -2200,7 +2048,7 @@ name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.1.0",
|
||||
"bitflags 2.2.1",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"log",
|
||||
@@ -2209,10 +2057,9 @@ dependencies = [
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff_rustpython",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-common",
|
||||
"rustpython-literal 0.2.0 (git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec)",
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -2234,7 +2081,6 @@ dependencies = [
|
||||
"ruff_testing_macros",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
"similar",
|
||||
"test-case",
|
||||
@@ -2244,7 +2090,7 @@ dependencies = [
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.1.0",
|
||||
"bitflags 2.2.1",
|
||||
"is-macro",
|
||||
"nohash-hasher",
|
||||
"ruff_python_ast",
|
||||
@@ -2268,8 +2114,6 @@ name = "ruff_rustpython"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"once_cell",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
]
|
||||
|
||||
@@ -2286,7 +2130,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=c3147d2c1524ebd0e90cf1c2938d770314fd5a5a#c3147d2c1524ebd0e90cf1c2938d770314fd5a5a"
|
||||
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2357,75 +2201,93 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=c3147d2c1524ebd0e90cf1c2938d770314fd5a5a#c3147d2c1524ebd0e90cf1c2938d770314fd5a5a"
|
||||
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
"ruff_text_size",
|
||||
"rustpython-parser-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=c3147d2c1524ebd0e90cf1c2938d770314fd5a5a#c3147d2c1524ebd0e90cf1c2938d770314fd5a5a"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=f3e4d3409253660bd4fa7f3d24d3db747e7dca61#f3e4d3409253660bd4fa7f3d24d3db747e7dca61"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.2.1",
|
||||
"bstr 0.2.17",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"hexf-parse",
|
||||
"itertools",
|
||||
"lexical-parse-float",
|
||||
"libc",
|
||||
"lock_api",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"radium",
|
||||
"rand",
|
||||
"rustpython-literal 0.2.0 (git+https://github.com/youknowone/RustPython-parser.git?rev=5b2af304a2baa53598e594097824165d4ac7a119)",
|
||||
"siphasher",
|
||||
"unic-ucd-category",
|
||||
"volatile",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=c3147d2c1524ebd0e90cf1c2938d770314fd5a5a#c3147d2c1524ebd0e90cf1c2938d770314fd5a5a"
|
||||
source = "git+https://github.com/youknowone/RustPython-parser.git?rev=5b2af304a2baa53598e594097824165d4ac7a119#5b2af304a2baa53598e594097824165d4ac7a119"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"itertools",
|
||||
"lz4_flex",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"ruff_text_size",
|
||||
"hexf-parse",
|
||||
"lexical-parse-float",
|
||||
"num-traits",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"lexical-parse-float",
|
||||
"num-traits",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=c3147d2c1524ebd0e90cf1c2938d770314fd5a5a#c3147d2c1524ebd0e90cf1c2938d770314fd5a5a"
|
||||
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"log",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
"rustpython-compiler-core",
|
||||
"rustpython-parser-core",
|
||||
"tiny-keccak",
|
||||
"unic-emoji-char",
|
||||
"unic-ucd-ident",
|
||||
"unicode_names2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/Parser.git?rev=947fb53d0b41fec465db3d8e725bdb2eec1299ec#947fb53d0b41fec465db3d8e725bdb2eec1299ec"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"lz4_flex",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.12"
|
||||
@@ -2613,19 +2475,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -2698,17 +2547,6 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"rustversion",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
@@ -3061,12 +2899,6 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "0.6.0"
|
||||
|
||||
@@ -11,7 +11,7 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.1.0" }
|
||||
bitflags = { version = "2.2.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
@@ -30,10 +30,11 @@ path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
ruff_text_size = { git = "https://github.com/charliermarsh/RustPython.git", rev = "c3147d2c1524ebd0e90cf1c2938d770314fd5a5a" }
|
||||
ruff_text_size = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "c3147d2c1524ebd0e90cf1c2938d770314fd5a5a" }
|
||||
rustpython-parser = { git = "https://github.com/charliermarsh/RustPython.git", rev = "c3147d2c1524ebd0e90cf1c2938d770314fd5a5a" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f3e4d3409253660bd4fa7f3d24d3db747e7dca61" }
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "947fb53d0b41fec465db3d8e725bdb2eec1299ec" , default-features = false}
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||
|
||||
24
LICENSE
24
LICENSE
@@ -550,6 +550,30 @@ are:
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- flynt, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2022 Ilya Kamenshchikov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- isort, licensed as follows:
|
||||
"""
|
||||
|
||||
@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.265'
|
||||
rev: 'v0.0.266'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -183,6 +183,7 @@ exclude = [
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
@@ -280,12 +281,13 @@ quality tools, including:
|
||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
||||
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
|
||||
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/charliermarsh/ruff/issues/2102))
|
||||
- [isort](https://pypi.org/project/isort/)
|
||||
- [mccabe](https://pypi.org/project/mccabe/)
|
||||
- [pandas-vet](https://pypi.org/project/pandas-vet/)
|
||||
- [pep8-naming](https://pypi.org/project/pep8-naming/)
|
||||
- [pydocstyle](https://pypi.org/project/pydocstyle/)
|
||||
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980))
|
||||
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
|
||||
- [pyupgrade](https://pypi.org/project/pyupgrade/)
|
||||
- [tryceratops](https://pypi.org/project/tryceratops/)
|
||||
- [yesqa](https://pypi.org/project/yesqa/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.265"
|
||||
version = "0.0.266"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ requires-python = ">=3.7"
|
||||
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.14,<0.15"]
|
||||
requires = ["maturin>=0.15.1,<0.16"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[tool.maturin]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.265"
|
||||
version = "0.0.266"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -56,7 +56,7 @@ result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
@@ -80,5 +80,7 @@ colored = { workspace = true, features = ["no-color"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
schemars = ["dep:schemars"]
|
||||
logical_lines = []
|
||||
jupyter_notebook = []
|
||||
ecosystem_ci = []
|
||||
|
||||
@@ -4,7 +4,12 @@ B027 - on lines 13, 16, 19, 23
|
||||
"""
|
||||
import abc
|
||||
from abc import ABC
|
||||
from abc import abstractmethod, abstractproperty
|
||||
from abc import (
|
||||
abstractmethod,
|
||||
abstractproperty,
|
||||
abstractclassmethod,
|
||||
abstractstaticmethod,
|
||||
)
|
||||
from abc import abstractmethod as notabstract
|
||||
from abc import abstractproperty as notabstract_property
|
||||
|
||||
@@ -55,6 +60,22 @@ class AbstractClass(ABC):
|
||||
def abstract_6(self):
|
||||
...
|
||||
|
||||
@abstractclassmethod
|
||||
def abstract_7(self):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def abstract_8(self):
|
||||
...
|
||||
|
||||
@abstractstaticmethod
|
||||
def abstract_9(self):
|
||||
pass
|
||||
|
||||
@abc.abstractstaticmethod
|
||||
def abstract_10(self):
|
||||
...
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
"""
|
||||
Should emit:
|
||||
B027 - on lines 13, 16, 19, 23
|
||||
"""
|
||||
from abc import ABC
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
def empty_1(self): # error
|
||||
...
|
||||
|
||||
def empty_2(self): # error
|
||||
pass
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
def body_2(self):
|
||||
self.body_1()
|
||||
|
||||
|
||||
def foo():
|
||||
class InnerAbstractClass(ABC):
|
||||
def empty_1(self): # error
|
||||
...
|
||||
|
||||
def empty_2(self): # error
|
||||
pass
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
def body_2(self):
|
||||
self.body_1()
|
||||
|
||||
return InnerAbstractClass
|
||||
|
||||
@@ -17,3 +17,23 @@ all((x.id for x in bar))
|
||||
|
||||
async def f() -> bool:
|
||||
return all([await use_greeting(greeting) for greeting in await greetings()])
|
||||
|
||||
|
||||
# Special comment handling
|
||||
any(
|
||||
[ # lbracket comment
|
||||
# second line comment
|
||||
i.bit_count()
|
||||
# random middle comment
|
||||
for i in range(5) # rbracket comment
|
||||
] # rpar comment
|
||||
# trailing comment
|
||||
)
|
||||
|
||||
# Weird case where the function call, opening bracket, and comment are all
|
||||
# on the same line.
|
||||
any([ # lbracket comment
|
||||
# second line comment
|
||||
i.bit_count() for i in range(5) # rbracket comment
|
||||
] # rpar comment
|
||||
)
|
||||
|
||||
@@ -7,3 +7,12 @@ foo.info("Hello {}".format("World!"))
|
||||
logging.log(logging.INFO, msg="Hello {}".format("World!"))
|
||||
logging.log(level=logging.INFO, msg="Hello {}".format("World!"))
|
||||
logging.log(msg="Hello {}".format("World!"), level=logging.INFO)
|
||||
|
||||
# Flask support
|
||||
import flask
|
||||
from flask import current_app
|
||||
from flask import current_app as app
|
||||
|
||||
flask.current_app.logger.info("Hello {}".format("World!"))
|
||||
current_app.logger.info("Hello {}".format("World!"))
|
||||
app.logger.log(logging.INFO, "Hello {}".format("World!"))
|
||||
|
||||
@@ -1,73 +1,96 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
try:
|
||||
foo()
|
||||
except ValueError: # SIM105
|
||||
pass
|
||||
|
||||
try:
|
||||
foo()
|
||||
except (ValueError, OSError): # SIM105
|
||||
pass
|
||||
|
||||
try:
|
||||
foo()
|
||||
except: # SIM105
|
||||
pass
|
||||
|
||||
try:
|
||||
foo()
|
||||
except (a.Error, b.Error): # SIM105
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
print('foo')
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except:
|
||||
pass
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except (a.Error, b.Error):
|
||||
pass
|
||||
|
||||
# OK
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
print("foo")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# OK
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
print('bar')
|
||||
print("bar")
|
||||
|
||||
# OK
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
pass
|
||||
finally:
|
||||
print('bar')
|
||||
print("bar")
|
||||
|
||||
# OK
|
||||
try:
|
||||
foo()
|
||||
foo()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# OK
|
||||
try:
|
||||
for i in range(3):
|
||||
foo()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def bar():
|
||||
# OK
|
||||
try:
|
||||
return foo()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def with_ellipsis():
|
||||
# OK
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
|
||||
def with_ellipsis_and_return():
|
||||
# OK
|
||||
try:
|
||||
return foo()
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
|
||||
def with_comment():
|
||||
try:
|
||||
foo()
|
||||
except (ValueError, OSError):
|
||||
pass # Trailing comment.
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Case: There's a random import, so it should add `contextlib` after it."""
|
||||
import math
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
math.sqrt(-1)
|
||||
except ValueError: # SIM105
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,7 @@ def foo():
|
||||
pass
|
||||
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
|
||||
16
crates/ruff/resources/test/fixtures/flake8_simplify/SIM105_3.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/flake8_simplify/SIM105_3.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Case: `contextlib` is imported after the call site."""
|
||||
|
||||
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
def bar():
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
import contextlib
|
||||
@@ -12,3 +12,10 @@ if not a == b: # OK
|
||||
|
||||
if not a != b: # OK
|
||||
pass
|
||||
|
||||
a = not not b # SIM208
|
||||
|
||||
f(not not a) # SIM208
|
||||
|
||||
if 1 + (not (not a)): # SIM208
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,7 @@ a = True if b + c else False # SIM210
|
||||
|
||||
a = False if b else True # OK
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
def bool():
|
||||
|
||||
18
crates/ruff/resources/test/fixtures/flynt/FLY002.py
vendored
Normal file
18
crates/ruff/resources/test/fixtures/flynt/FLY002.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import secrets
|
||||
from random import random, choice
|
||||
|
||||
a = "Hello"
|
||||
ok1 = " ".join([a, " World"]) # OK
|
||||
ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||
ok3 = "x".join(("1", "2", "3")) # OK
|
||||
ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||
ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||
ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||
|
||||
nok1 = "x".join({"4", "5", "yee"}) # Not OK (set)
|
||||
nok2 = a.join(["1", "2", "3"]) # Not OK (not a static joiner)
|
||||
nok3 = "a".join(a) # Not OK (not a static joinee)
|
||||
nok4 = "a".join([a, a, *a]) # Not OK (not a static length)
|
||||
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
||||
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||
@@ -22,3 +22,7 @@ if True:
|
||||
x.drop(["a"], axis=1, **kwargs, inplace=True)
|
||||
x.drop(["a"], axis=1, inplace=True, **kwargs)
|
||||
f(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
x.apply(lambda x: x.sort_values('a', inplace=True))
|
||||
import torch
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Here's a top-level docstring that's over the limit."""
|
||||
|
||||
|
||||
def f():
|
||||
def f1():
|
||||
"""Here's a docstring that's also over the limit."""
|
||||
|
||||
x = 1 # Here's a comment that's over the limit, but it's not standalone.
|
||||
@@ -16,3 +16,16 @@ def f():
|
||||
|
||||
|
||||
"This is also considered a docstring, and is over the limit."
|
||||
|
||||
|
||||
def f2():
|
||||
"""Here's a multi-line docstring.
|
||||
|
||||
It's over the limit on this line, which isn't the first line in the docstring.
|
||||
"""
|
||||
|
||||
|
||||
def f3():
|
||||
"""Here's a multi-line docstring.
|
||||
|
||||
It's over the limit on this line, which isn't the first line in the docstring."""
|
||||
|
||||
@@ -13,3 +13,15 @@ def another_function():
|
||||
|
||||
def utf8_function():
|
||||
"""éste docstring is capitalized."""
|
||||
|
||||
def uppercase_char_not_possible():
|
||||
"""'args' is not capitalized."""
|
||||
|
||||
def non_alphabetic():
|
||||
"""th!is is not capitalized."""
|
||||
|
||||
def non_ascii():
|
||||
"""th•s is not capitalized."""
|
||||
|
||||
def all_caps():
|
||||
"""th•s is not capitalized."""
|
||||
|
||||
18
crates/ruff/resources/test/fixtures/pydocstyle/all.py
vendored
Normal file
18
crates/ruff/resources/test/fixtures/pydocstyle/all.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
def public_func():
|
||||
pass
|
||||
|
||||
|
||||
def private_func():
|
||||
pass
|
||||
|
||||
|
||||
class PublicClass:
|
||||
class PublicNestedClass:
|
||||
pass
|
||||
|
||||
|
||||
class PrivateClass:
|
||||
pass
|
||||
|
||||
|
||||
__all__ = ("public_func", "PublicClass")
|
||||
19
crates/ruff/resources/test/fixtures/pygrep-hooks/PGH005_0.py
vendored
Normal file
19
crates/ruff/resources/test/fixtures/pygrep-hooks/PGH005_0.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Errors
|
||||
assert my_mock.not_called()
|
||||
assert my_mock.called_once_with()
|
||||
assert my_mock.not_called
|
||||
assert my_mock.called_once_with
|
||||
my_mock.assert_not_called
|
||||
my_mock.assert_called
|
||||
my_mock.assert_called_once_with
|
||||
my_mock.assert_called_once_with
|
||||
MyMock.assert_called_once_with
|
||||
|
||||
# OK
|
||||
assert my_mock.call_count == 1
|
||||
assert my_mock.called
|
||||
my_mock.assert_not_called()
|
||||
my_mock.assert_called()
|
||||
my_mock.assert_called_once_with()
|
||||
"""like :meth:`Mock.assert_called_once_with`"""
|
||||
"""like :meth:`MagicMock.assert_called_once_with`"""
|
||||
21
crates/ruff/resources/test/fixtures/pylint/nested_min_max.py
vendored
Normal file
21
crates/ruff/resources/test/fixtures/pylint/nested_min_max.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
min(1, 2, 3)
|
||||
min(1, min(2, 3))
|
||||
min(1, min(2, min(3, 4)))
|
||||
min(1, foo("a", "b"), min(3, 4))
|
||||
min(1, max(2, 3))
|
||||
max(1, 2, 3)
|
||||
max(1, max(2, 3))
|
||||
max(1, max(2, max(3, 4)))
|
||||
max(1, foo("a", "b"), max(3, 4))
|
||||
|
||||
# These should not trigger; we do not flag cases with keyword args.
|
||||
min(1, min(2, 3), key=test)
|
||||
min(1, min(2, 3, key=test))
|
||||
# This will still trigger, to merge the calls without keyword args.
|
||||
min(1, min(2, 3, key=test), min(4, 5))
|
||||
|
||||
# Don't provide a fix if there are comments within the call.
|
||||
min(
|
||||
1, # This is a comment.
|
||||
min(2, 3),
|
||||
)
|
||||
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_7.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_7.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
def main():
|
||||
exit(0)
|
||||
|
||||
|
||||
import functools
|
||||
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_8.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_8.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
from sys import argv
|
||||
|
||||
|
||||
def main():
|
||||
exit(0)
|
||||
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_9.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/pylint/sys_exit_alias_9.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
def main():
|
||||
exit(0)
|
||||
|
||||
|
||||
from sys import argv
|
||||
@@ -22,13 +22,13 @@ def f(x=1, y=1, z=1): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, /, u, v, w): # OK
|
||||
def f(x, y, z, /, u, v, w): # Too many arguments (6/5)
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, *, u, v, w): # OK
|
||||
def f(x, y, z, *, u, v, w): # Too many arguments (6/5)
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many arguments (6/5)
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many arguments (9/5)
|
||||
pass
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
class TestClass:
|
||||
def __bool__(self):
|
||||
...
|
||||
|
||||
|
||||
def __bool__(self, x): # too many mandatory args
|
||||
...
|
||||
|
||||
|
||||
def __bool__(self, x=1): # additional optional args OK
|
||||
...
|
||||
|
||||
def __bool__(self, *args): # varargs OK
|
||||
...
|
||||
|
||||
|
||||
def __bool__(): # ignored; should be caughty by E0211/N805
|
||||
...
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __bool__():
|
||||
...
|
||||
@@ -21,31 +18,58 @@ class TestClass:
|
||||
@staticmethod
|
||||
def __bool__(x): # too many mandatory args
|
||||
...
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __bool__(x=1): # additional optional args OK
|
||||
...
|
||||
|
||||
|
||||
def __eq__(self, other): # multiple args
|
||||
...
|
||||
|
||||
|
||||
def __eq__(self, other=1): # expected arg is optional
|
||||
...
|
||||
|
||||
|
||||
def __eq__(self): # too few mandatory args
|
||||
...
|
||||
|
||||
|
||||
def __eq__(self, other, other_other): # too many mandatory args
|
||||
...
|
||||
|
||||
def __round__(self): # allow zero additional args.
|
||||
|
||||
def __round__(self): # allow zero additional args
|
||||
...
|
||||
|
||||
def __round__(self, x): # allow one additional args.
|
||||
|
||||
def __round__(self, x): # allow one additional args
|
||||
...
|
||||
|
||||
|
||||
def __round__(self, x, y): # disallow 2 args
|
||||
...
|
||||
|
||||
|
||||
def __round__(self, x, y, z=2): # disallow 3 args even when one is optional
|
||||
...
|
||||
...
|
||||
|
||||
def __eq__(self, *args): # ignore *args
|
||||
...
|
||||
|
||||
def __eq__(self, x, *args): # extra *args is ok
|
||||
...
|
||||
|
||||
def __eq__(self, x, y, *args): # too many args with *args
|
||||
...
|
||||
|
||||
def __round__(self, *args): # allow zero additional args
|
||||
...
|
||||
|
||||
def __round__(self, x, *args): # allow one additional args
|
||||
...
|
||||
|
||||
def __round__(self, x, y, *args): # disallow 2 args
|
||||
...
|
||||
|
||||
def __eq__(self, **kwargs): # ignore **kwargs
|
||||
...
|
||||
|
||||
def __eq__(self, /, other=42): # ignore positional-only args
|
||||
...
|
||||
|
||||
def __eq__(self, *, other=42): # ignore positional-only args
|
||||
...
|
||||
|
||||
@@ -59,3 +59,14 @@ u"foo".encode("utf-8") # b"foo"
|
||||
R"foo\o".encode("utf-8") # br"foo\o"
|
||||
U"foo".encode("utf-8") # b"foo"
|
||||
print("foo".encode()) # print(b"foo")
|
||||
|
||||
# `encode` on parenthesized strings.
|
||||
(
|
||||
"abc"
|
||||
"def"
|
||||
).encode()
|
||||
|
||||
((
|
||||
"abc"
|
||||
"def"
|
||||
)).encode()
|
||||
|
||||
1
crates/ruff/resources/test/fixtures/pyupgrade/UP032_1.py
vendored
Normal file
1
crates/ruff/resources/test/fixtures/pyupgrade/UP032_1.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"{} {}".format(a, b) # Intentionally at start-of-file, to ensure graceful handling.
|
||||
@@ -45,3 +45,10 @@ def good():
|
||||
logger.exception("a failed")
|
||||
except Exception:
|
||||
logger.exception("something failed")
|
||||
|
||||
|
||||
def fine():
|
||||
try:
|
||||
a = process() # This throws the exception now
|
||||
finally:
|
||||
print("finally")
|
||||
|
||||
@@ -5,7 +5,7 @@ use libcst_native::{
|
||||
Codegen, CodegenState, ImportNames, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||
};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{self, ExcepthandlerKind, Expr, Keyword, Stmt, StmtKind};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -28,21 +28,21 @@ fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
|
||||
/// Determine if a child is the only statement in its body.
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
|
||||
match &parent.node {
|
||||
StmtKind::FunctionDef { body, .. }
|
||||
| StmtKind::AsyncFunctionDef { body, .. }
|
||||
| StmtKind::ClassDef { body, .. }
|
||||
| StmtKind::With { body, .. }
|
||||
| StmtKind::AsyncWith { body, .. } => {
|
||||
StmtKind::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| StmtKind::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| StmtKind::With(ast::StmtWith { body, .. })
|
||||
| StmtKind::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else {
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::For { body, orelse, .. }
|
||||
| StmtKind::AsyncFor { body, orelse, .. }
|
||||
| StmtKind::While { body, orelse, .. }
|
||||
| StmtKind::If { body, orelse, .. } => {
|
||||
StmtKind::For(ast::StmtFor { body, orelse, .. })
|
||||
| StmtKind::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| StmtKind::While(ast::StmtWhile { body, orelse, .. })
|
||||
| StmtKind::If(ast::StmtIf { body, orelse, .. }) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
@@ -51,18 +51,18 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
StmtKind::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
}
|
||||
| StmtKind::TryStar {
|
||||
})
|
||||
| StmtKind::TryStar(ast::StmtTryStar {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
} => {
|
||||
}) => {
|
||||
if body.iter().contains(child) {
|
||||
Ok(has_single_child(body, deleted))
|
||||
} else if orelse.iter().contains(child) {
|
||||
@@ -70,7 +70,9 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
} else if finalbody.iter().contains(child) {
|
||||
Ok(has_single_child(finalbody, deleted))
|
||||
} else if let Some(body) = handlers.iter().find_map(|handler| match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { body, .. } => {
|
||||
ExcepthandlerKind::ExceptHandler(ast::ExcepthandlerExceptHandler {
|
||||
body, ..
|
||||
}) => {
|
||||
if body.iter().contains(child) {
|
||||
Some(body)
|
||||
} else {
|
||||
@@ -83,7 +85,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
|
||||
bail!("Unable to find child in parent body")
|
||||
}
|
||||
}
|
||||
StmtKind::Match { cases, .. } => {
|
||||
StmtKind::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if let Some(body) = cases.iter().find_map(|case| {
|
||||
if case.body.iter().contains(child) {
|
||||
Some(&case.body)
|
||||
@@ -166,7 +168,7 @@ fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
|
||||
/// remove the entire start and end lines.
|
||||
/// - If the `Stmt` is the last statement in its parent body, replace it with a
|
||||
/// `pass` instead.
|
||||
pub fn delete_stmt(
|
||||
pub(crate) fn delete_stmt(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
@@ -203,7 +205,7 @@ pub fn delete_stmt(
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to remove any unused imports from an `import` statement.
|
||||
pub fn remove_unused_imports<'a>(
|
||||
pub(crate) fn remove_unused_imports<'a>(
|
||||
unused_imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
@@ -328,7 +330,7 @@ pub fn remove_unused_imports<'a>(
|
||||
///
|
||||
/// Supports the removal of parentheses when this is the only (kw)arg left.
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub fn remove_argument(
|
||||
pub(crate) fn remove_argument(
|
||||
locator: &Locator,
|
||||
call_at: TextSize,
|
||||
expr_range: TextRange,
|
||||
@@ -350,7 +352,7 @@ pub fn remove_argument(
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count: usize = 0;
|
||||
for (tok, range) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
@@ -382,7 +384,7 @@ pub fn remove_argument(
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::NonLogicalNewline) {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
@@ -405,7 +407,7 @@ pub fn remove_argument(
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (tok, range) in lexer::lex_located(contents, Mode::Module, call_at).flatten() {
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if range.start() == expr_range.start() {
|
||||
fix_end = Some(expr_range.end());
|
||||
break;
|
||||
@@ -432,17 +434,29 @@ pub fn remove_argument(
|
||||
/// name on which the `lru_cache` symbol would be made available (`"functools.lru_cache"`).
|
||||
///
|
||||
/// Attempts to reuse existing imports when possible.
|
||||
pub fn get_or_import_symbol(
|
||||
pub(crate) fn get_or_import_symbol(
|
||||
module: &str,
|
||||
member: &str,
|
||||
at: TextSize,
|
||||
context: &Context,
|
||||
importer: &Importer,
|
||||
locator: &Locator,
|
||||
) -> Result<(Edit, String)> {
|
||||
if let Some((source, binding)) = context.resolve_qualified_import_name(module, member) {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
//
|
||||
// We also add a no-nop edit to force conflicts with any other fixes that might try to
|
||||
|
||||
// The exception: the symbol source (i.e., the import statement) comes after the current
|
||||
// location. For example, we could be generating an edit within a function, and the import
|
||||
// could be defined in the module scope, but after the function definition. In this case,
|
||||
// it's unclear whether we can use the symbol (the function could be called between the
|
||||
// import and the current location, and thus the symbol would not be available). It's also
|
||||
// unclear whether should add an import statement at the top of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if source.start() > at {
|
||||
bail!("Unable to use existing symbol `{binding}` due to late-import");
|
||||
}
|
||||
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
// remove the import. Consider:
|
||||
//
|
||||
// ```py
|
||||
@@ -462,7 +476,7 @@ pub fn get_or_import_symbol(
|
||||
Edit::range_replacement(locator.slice(source.range()).to_string(), source.range());
|
||||
Ok((import_edit, binding))
|
||||
} else {
|
||||
if let Some(stmt) = importer.get_import_from(module) {
|
||||
if let Some(stmt) = importer.find_import_from(module, at) {
|
||||
// Case 1: `from functools import lru_cache` is in scope, and we're trying to reference
|
||||
// `functools.cache`; thus, we add `cache` to the import, and return `"cache"` as the
|
||||
// bound name.
|
||||
@@ -473,10 +487,7 @@ pub fn get_or_import_symbol(
|
||||
let import_edit = importer.add_member(stmt, member)?;
|
||||
Ok((import_edit, member.to_string()))
|
||||
} else {
|
||||
bail!(
|
||||
"Unable to insert `{}` into scope due to name conflict",
|
||||
member
|
||||
)
|
||||
bail!("Unable to insert `{member}` into scope due to name conflict")
|
||||
}
|
||||
} else {
|
||||
// Case 2: No `functools` import is in scope; thus, we add `import functools`, and
|
||||
@@ -485,13 +496,11 @@ pub fn get_or_import_symbol(
|
||||
.find_binding(module)
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
{
|
||||
let import_edit = importer.add_import(&AnyImport::Import(Import::module(module)));
|
||||
let import_edit =
|
||||
importer.add_import(&AnyImport::Import(Import::module(module)), at);
|
||||
Ok((import_edit, format!("{module}.{member}")))
|
||||
} else {
|
||||
bail!(
|
||||
"Unable to insert `{}` into scope due to name conflict",
|
||||
module
|
||||
)
|
||||
bail!("Unable to insert `{module}` into scope due to name conflict")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,16 @@ use ruff_python_ast::source_code::Locator;
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub mod actions;
|
||||
pub(crate) mod actions;
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<(String, FixTable)> {
|
||||
pub(crate) fn fix_file(
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
) -> Option<(String, FixTable)> {
|
||||
let mut with_fixes = diagnostics
|
||||
.iter()
|
||||
.filter(|diag| !diag.fix.is_empty())
|
||||
.filter(|diag| diag.fix.is_some())
|
||||
.peekable();
|
||||
|
||||
if with_fixes.peek().is_none() {
|
||||
@@ -38,11 +41,10 @@ fn apply_fixes<'a>(
|
||||
|
||||
for (rule, fix) in diagnostics
|
||||
.filter_map(|diagnostic| {
|
||||
if diagnostic.fix.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((diagnostic.kind.rule(), &diagnostic.fix))
|
||||
}
|
||||
diagnostic
|
||||
.fix
|
||||
.as_ref()
|
||||
.map(|fix| (diagnostic.kind.rule(), fix))
|
||||
})
|
||||
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
|
||||
{
|
||||
@@ -103,18 +105,20 @@ mod tests {
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::apply_fixes;
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn create_diagnostics(edit: impl IntoIterator<Item = Edit>) -> Vec<Diagnostic> {
|
||||
edit.into_iter()
|
||||
.map(|edit| Diagnostic {
|
||||
// The choice of rule here is arbitrary.
|
||||
kind: MissingNewlineAtEndOfFile.into(),
|
||||
range: edit.range(),
|
||||
fix: edit.into(),
|
||||
fix: Some(Fix::unspecified(edit)),
|
||||
parent: None,
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use ruff_python_ast::types::RefEquality;
|
||||
use ruff_python_semantic::analyze::visibility::{Visibility, VisibleScope};
|
||||
use ruff_python_semantic::scope::ScopeId;
|
||||
|
||||
use crate::checkers::ast::AnnotationContext;
|
||||
use crate::docstrings::definition::Definition;
|
||||
|
||||
type Context<'a> = (ScopeId, Vec<RefEquality<'a, Stmt>>);
|
||||
use ruff_python_semantic::context::Snapshot;
|
||||
|
||||
/// A collection of AST nodes that are deferred for later analysis.
|
||||
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
||||
/// module-level definitions have been analyzed.
|
||||
#[derive(Default)]
|
||||
pub struct Deferred<'a> {
|
||||
pub definitions: Vec<(Definition<'a>, Visibility, Context<'a>)>,
|
||||
pub string_type_definitions: Vec<(TextRange, &'a str, AnnotationContext, Context<'a>)>,
|
||||
pub type_definitions: Vec<(&'a Expr, AnnotationContext, Context<'a>)>,
|
||||
pub functions: Vec<(&'a Stmt, Context<'a>, VisibleScope)>,
|
||||
pub lambdas: Vec<(&'a Expr, Context<'a>)>,
|
||||
pub for_loops: Vec<(&'a Stmt, Context<'a>)>,
|
||||
pub assignments: Vec<Context<'a>>,
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Deferred<'a> {
|
||||
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) for_loops: Vec<Snapshot>,
|
||||
pub(crate) assignments: Vec<Snapshot>,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
|
||||
use crate::rules::pep8_naming::rules::invalid_module_name;
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub fn check_file_path(
|
||||
pub(crate) fn check_file_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::{StmtKind, Suite};
|
||||
use rustpython_parser::ast::{self, StmtKind, Suite};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
use ruff_python_ast::imports::{ImportMap, ModuleImport};
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
|
||||
use crate::directives::IsortDirectives;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::isort;
|
||||
use crate::rules::isort::track::{Block, ImportTracker};
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) -> Option<ImportMap> {
|
||||
let Some(package) = package else {
|
||||
@@ -29,20 +29,21 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
let mut module_imports = Vec::with_capacity(num_imports);
|
||||
for stmt in blocks.iter().flat_map(|block| &block.imports) {
|
||||
match &stmt.node {
|
||||
StmtKind::Import { names } => {
|
||||
StmtKind::Import(ast::StmtImport { names }) => {
|
||||
module_imports.extend(
|
||||
names
|
||||
.iter()
|
||||
.map(|name| ModuleImport::new(name.node.name.clone(), stmt.range())),
|
||||
.map(|name| ModuleImport::new(name.node.name.to_string(), stmt.range())),
|
||||
);
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
StmtKind::ImportFrom(ast::StmtImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
let level = level.unwrap_or(0);
|
||||
}) => {
|
||||
let level = level.map_or(0, |level| level.to_usize());
|
||||
let module = if let Some(module) = module {
|
||||
let module: &String = module.as_ref();
|
||||
if level == 0 {
|
||||
Cow::Borrowed(module)
|
||||
} else {
|
||||
@@ -72,14 +73,13 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_imports(
|
||||
pub(crate) fn check_imports(
|
||||
python_ast: &Suite,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
directives: &IsortDirectives,
|
||||
settings: &Settings,
|
||||
stylist: &Stylist,
|
||||
autofix: flags::Autofix,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
@@ -99,7 +99,7 @@ pub fn check_imports(
|
||||
for block in &blocks {
|
||||
if !block.imports.is_empty() {
|
||||
if let Some(diagnostic) = isort::rules::organize_imports(
|
||||
block, locator, stylist, indexer, settings, autofix, package,
|
||||
block, locator, stylist, indexer, settings, package,
|
||||
) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -108,7 +108,7 @@ pub fn check_imports(
|
||||
}
|
||||
if settings.rules.enabled(Rule::MissingRequiredImport) {
|
||||
diagnostics.extend(isort::rules::add_required_imports(
|
||||
&blocks, python_ast, locator, stylist, settings, autofix, is_stub,
|
||||
&blocks, python_ast, locator, stylist, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_ast::token_kind::TokenKind;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
whitespace_around_named_parameter_equals, whitespace_before_comment,
|
||||
whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||
};
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
||||
fn expand_indent(line: &str) -> usize {
|
||||
@@ -30,25 +30,23 @@ fn expand_indent(line: &str) -> usize {
|
||||
indent
|
||||
}
|
||||
|
||||
pub fn check_logical_lines(
|
||||
pub(crate) fn check_logical_lines(
|
||||
tokens: &[LexResult],
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut context = LogicalLinesContext::new(settings);
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_missing_whitespace =
|
||||
autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_missing_whitespace = false;
|
||||
|
||||
#[cfg(feature = "logical_lines")]
|
||||
let should_fix_whitespace_before_parameters =
|
||||
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
|
||||
#[cfg(not(feature = "logical_lines"))]
|
||||
let should_fix_whitespace_before_parameters = false;
|
||||
@@ -138,19 +136,19 @@ impl<'a> LogicalLinesContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<K: Into<DiagnosticKind>>(&mut self, kind: K, range: TextRange) {
|
||||
pub(crate) fn push<K: Into<DiagnosticKind>>(&mut self, kind: K, range: TextRange) {
|
||||
let kind = kind.into();
|
||||
if self.settings.rules.enabled(kind.rule()) {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
kind,
|
||||
range,
|
||||
fix: Fix::empty(),
|
||||
fix: None,
|
||||
parent: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
|
||||
pub(crate) fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
|
||||
if self.settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod ast;
|
||||
pub mod filesystem;
|
||||
pub mod imports;
|
||||
pub(crate) mod ast;
|
||||
pub(crate) mod filesystem;
|
||||
pub(crate) mod imports;
|
||||
#[cfg(feature = "logical_lines")]
|
||||
pub(crate) mod logical_lines;
|
||||
pub mod noqa;
|
||||
pub mod physical_lines;
|
||||
pub mod tokens;
|
||||
pub(crate) mod noqa;
|
||||
pub(crate) mod physical_lines;
|
||||
pub(crate) mod tokens;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::noqa;
|
||||
@@ -11,15 +11,14 @@ use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping};
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub fn check_noqa(
|
||||
pub(crate) fn check_noqa(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
locator: &Locator,
|
||||
comment_ranges: &[TextRange],
|
||||
noqa_line_for: &NoqaMapping,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<usize> {
|
||||
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
|
||||
|
||||
@@ -101,8 +100,9 @@ pub fn check_noqa(
|
||||
if line.matches.is_empty() {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(UnusedNOQA { codes: None }, *noqa_range);
|
||||
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(delete_noqa(
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix_from_edit(delete_noqa(
|
||||
*leading_spaces,
|
||||
*noqa_range,
|
||||
*trailing_spaces,
|
||||
@@ -169,19 +169,21 @@ pub fn check_noqa(
|
||||
},
|
||||
*range,
|
||||
);
|
||||
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic.set_fix(delete_noqa(
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix_from_edit(delete_noqa(
|
||||
*leading_spaces,
|
||||
*range,
|
||||
*trailing_spaces,
|
||||
locator,
|
||||
));
|
||||
} else {
|
||||
diagnostic.set_fix(Edit::range_replacement(
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
format!("# noqa: {}", valid_codes.join(", ")),
|
||||
*range,
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
|
||||
@@ -19,16 +19,15 @@ use crate::rules::pycodestyle::rules::{
|
||||
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
|
||||
use crate::rules::pylint;
|
||||
use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub fn check_physical_lines(
|
||||
pub(crate) fn check_physical_lines(
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
doc_lines: &[TextSize],
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
let mut has_any_shebang = false;
|
||||
@@ -51,10 +50,8 @@ pub fn check_physical_lines(
|
||||
settings.rules.enabled(Rule::BlankLineWithWhitespace);
|
||||
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
|
||||
|
||||
let fix_unnecessary_coding_comment =
|
||||
autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
|
||||
let fix_shebang_whitespace =
|
||||
autofix.into() && settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
|
||||
let fix_unnecessary_coding_comment = settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
|
||||
let fix_shebang_whitespace = settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
|
||||
|
||||
let mut commented_lines_iter = indexer.comment_ranges().iter().peekable();
|
||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||
@@ -121,7 +118,7 @@ pub fn check_physical_lines(
|
||||
}
|
||||
|
||||
while doc_lines_iter
|
||||
.next_if(|doc_line_start| line.range().contains(**doc_line_start))
|
||||
.next_if(|doc_line_start| line.range().contains_inclusive(**doc_line_start))
|
||||
.is_some()
|
||||
{
|
||||
if enforce_doc_line_too_long {
|
||||
@@ -148,7 +145,7 @@ pub fn check_physical_lines(
|
||||
}
|
||||
|
||||
if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace {
|
||||
if let Some(diagnostic) = trailing_whitespace(&line, settings, autofix) {
|
||||
if let Some(diagnostic) = trailing_whitespace(&line, settings) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -164,7 +161,7 @@ pub fn check_physical_lines(
|
||||
if let Some(diagnostic) = no_newline_at_end_of_file(
|
||||
locator,
|
||||
stylist,
|
||||
autofix.into() && settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
|
||||
settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
|
||||
) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -188,7 +185,7 @@ mod tests {
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
use super::check_physical_lines;
|
||||
|
||||
@@ -211,7 +208,6 @@ mod tests {
|
||||
line_length,
|
||||
..Settings::for_rule(Rule::LineTooLong)
|
||||
},
|
||||
flags::Autofix::Enabled,
|
||||
)
|
||||
};
|
||||
assert_eq!(check_with_max_line_length(8), vec![]);
|
||||
|
||||
@@ -10,15 +10,14 @@ use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
|
||||
pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
pub fn check_tokens(
|
||||
pub(crate) fn check_tokens(
|
||||
locator: &Locator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
is_stub: bool,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
@@ -85,7 +84,6 @@ pub fn check_tokens(
|
||||
Context::Comment
|
||||
},
|
||||
settings,
|
||||
autofix,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -96,7 +94,7 @@ pub fn check_tokens(
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::Comment(_)) {
|
||||
if let Some(diagnostic) =
|
||||
eradicate::rules::commented_out_code(locator, *range, settings, autofix)
|
||||
eradicate::rules::commented_out_code(locator, *range, settings)
|
||||
{
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -111,7 +109,7 @@ pub fn check_tokens(
|
||||
diagnostics.extend(pycodestyle::rules::invalid_escape_sequence(
|
||||
locator,
|
||||
*range,
|
||||
autofix.into() && settings.rules.should_fix(Rule::InvalidEscapeSequence),
|
||||
settings.rules.should_fix(Rule::InvalidEscapeSequence),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -121,7 +119,7 @@ pub fn check_tokens(
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
diagnostics.extend(
|
||||
pylint::rules::invalid_string_characters(locator, *range, autofix.into())
|
||||
pylint::rules::invalid_string_characters(locator, *range)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
@@ -132,7 +130,7 @@ pub fn check_tokens(
|
||||
// E701, E702, E703
|
||||
if enforce_compound_statements {
|
||||
diagnostics.extend(
|
||||
pycodestyle::rules::compound_statements(tokens, settings, autofix)
|
||||
pycodestyle::rules::compound_statements(tokens, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
@@ -141,7 +139,7 @@ pub fn check_tokens(
|
||||
// Q001, Q002, Q003
|
||||
if enforce_quotes {
|
||||
diagnostics.extend(
|
||||
flake8_quotes::rules::from_tokens(tokens, locator, settings, autofix)
|
||||
flake8_quotes::rules::from_tokens(tokens, locator, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
@@ -163,7 +161,7 @@ pub fn check_tokens(
|
||||
// COM812, COM818, COM819
|
||||
if enforce_trailing_comma {
|
||||
diagnostics.extend(
|
||||
flake8_commas::rules::trailing_commas(tokens, locator, settings, autofix)
|
||||
flake8_commas::rules::trailing_commas(tokens, locator, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
@@ -172,8 +170,7 @@ pub fn check_tokens(
|
||||
// UP034
|
||||
if enforce_extraneous_parenthesis {
|
||||
diagnostics.extend(
|
||||
pyupgrade::rules::extraneous_parentheses(tokens, locator, settings, autofix)
|
||||
.into_iter(),
|
||||
pyupgrade::rules::extraneous_parentheses(tokens, locator, settings).into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::registry::{Linter, Rule};
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::registry::{Linter, Rule};
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NoqaCode(&'static str, &'static str);
|
||||
|
||||
@@ -211,6 +212,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
|
||||
(Pylint, "W2901") => Rule::RedefinedLoopName,
|
||||
(Pylint, "E0302") => Rule::UnexpectedSpecialMethodSignature,
|
||||
(Pylint, "W3301") => Rule::NestedMinMax,
|
||||
|
||||
// flake8-builtins
|
||||
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
|
||||
@@ -561,6 +563,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(PygrepHooks, "002") => Rule::DeprecatedLogWarn,
|
||||
(PygrepHooks, "003") => Rule::BlanketTypeIgnore,
|
||||
(PygrepHooks, "004") => Rule::BlanketNOQA,
|
||||
(PygrepHooks, "005") => Rule::InvalidMockAccess,
|
||||
|
||||
// pandas-vet
|
||||
(PandasVet, "002") => Rule::PandasUseOfInplaceArgument,
|
||||
@@ -735,6 +738,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
|
||||
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
|
||||
|
||||
// flynt
|
||||
// Reserved: (Flynt, "001") => Rule::StringConcatenationToFString,
|
||||
(Flynt, "002") => Rule::StaticJoinToFString,
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose_call_path(expr: &Expression) -> Option<String> {
|
||||
pub(crate) fn compose_call_path(expr: &Expression) -> Option<String> {
|
||||
let mut segments = vec![];
|
||||
compose_call_path_inner(expr, &mut segments);
|
||||
if segments.is_empty() {
|
||||
@@ -26,7 +26,7 @@ pub fn compose_call_path(expr: &Expression) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose_module_path(module: &NameOrAttribute) -> String {
|
||||
pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
|
||||
match module {
|
||||
NameOrAttribute::N(name) => name.value.to_string(),
|
||||
NameOrAttribute::A(attr) => {
|
||||
|
||||
@@ -4,21 +4,21 @@ use libcst_native::{
|
||||
ImportNames, Module, SimpleString, SmallStatement, Statement,
|
||||
};
|
||||
|
||||
pub fn match_module(module_text: &str) -> Result<Module> {
|
||||
pub(crate) fn match_module(module_text: &str) -> Result<Module> {
|
||||
match libcst_native::parse_module(module_text, None) {
|
||||
Ok(module) => Ok(module),
|
||||
Err(_) => bail!("Failed to extract CST from source"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_expression(expression_text: &str) -> Result<Expression> {
|
||||
pub(crate) fn match_expression(expression_text: &str) -> Result<Expression> {
|
||||
match libcst_native::parse_expression(expression_text) {
|
||||
Ok(expression) => Ok(expression),
|
||||
Err(_) => bail!("Failed to extract CST from source"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
pub(crate) fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
@@ -30,7 +30,7 @@ pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import<'b>> {
|
||||
pub(crate) fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Import(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
@@ -42,7 +42,9 @@ pub fn match_import<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Import
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_import_from<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut ImportFrom<'b>> {
|
||||
pub(crate) fn match_import_from<'a, 'b>(
|
||||
module: &'a mut Module<'b>,
|
||||
) -> Result<&'a mut ImportFrom<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::ImportFrom(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
@@ -54,7 +56,7 @@ pub fn match_import_from<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut I
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_aliases<'a, 'b>(
|
||||
pub(crate) fn match_aliases<'a, 'b>(
|
||||
import_from: &'a mut ImportFrom<'b>,
|
||||
) -> Result<&'a mut Vec<ImportAlias<'b>>> {
|
||||
if let ImportNames::Aliases(aliases) = &mut import_from.names {
|
||||
@@ -64,7 +66,7 @@ pub fn match_aliases<'a, 'b>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_call<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Call<'b>> {
|
||||
pub(crate) fn match_call<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Call<'b>> {
|
||||
if let Expression::Call(call) = expression {
|
||||
Ok(call)
|
||||
} else {
|
||||
@@ -72,7 +74,7 @@ pub fn match_call<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_comparison<'a, 'b>(
|
||||
pub(crate) fn match_comparison<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut Comparison<'b>> {
|
||||
if let Expression::Comparison(comparison) = expression {
|
||||
@@ -82,7 +84,7 @@ pub fn match_comparison<'a, 'b>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_dict<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Dict<'b>> {
|
||||
pub(crate) fn match_dict<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Dict<'b>> {
|
||||
if let Expression::Dict(dict) = expression {
|
||||
Ok(dict)
|
||||
} else {
|
||||
@@ -90,7 +92,7 @@ pub fn match_dict<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_attribute<'a, 'b>(
|
||||
pub(crate) fn match_attribute<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut Attribute<'b>> {
|
||||
if let Expression::Attribute(attribute) = expression {
|
||||
@@ -100,7 +102,7 @@ pub fn match_attribute<'a, 'b>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_simple_string<'a, 'b>(
|
||||
pub(crate) fn match_simple_string<'a, 'b>(
|
||||
expression: &'a mut Expression<'b>,
|
||||
) -> Result<&'a mut SimpleString<'b>> {
|
||||
if let Expression::SimpleString(simple_string) = expression {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod helpers;
|
||||
pub mod matchers;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod matchers;
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
//! Doc line extraction. In this context, a doc line is a line consisting of a
|
||||
//! standalone comment or a constant string statement.
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind, Suite};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustpython_parser::ast::{self, Constant, ExprKind, Stmt, StmtKind, Suite};
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::Tok;
|
||||
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::newlines::UniversalNewlineIterator;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
|
||||
/// Extract doc lines (standalone comments) from a token sequence.
|
||||
pub fn doc_lines_from_tokens<'a>(lxr: &'a [LexResult], locator: &'a Locator<'a>) -> DocLines<'a> {
|
||||
pub(crate) fn doc_lines_from_tokens<'a>(
|
||||
lxr: &'a [LexResult],
|
||||
locator: &'a Locator<'a>,
|
||||
) -> DocLines<'a> {
|
||||
DocLines::new(lxr, locator)
|
||||
}
|
||||
|
||||
pub struct DocLines<'a> {
|
||||
pub(crate) struct DocLines<'a> {
|
||||
inner: std::iter::Flatten<core::slice::Iter<'a, LexResult>>,
|
||||
locator: &'a Locator<'a>,
|
||||
prev: TextSize,
|
||||
@@ -69,29 +72,43 @@ impl Iterator for DocLines<'_> {
|
||||
|
||||
impl FusedIterator for DocLines<'_> {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StringLinesVisitor {
|
||||
struct StringLinesVisitor<'a> {
|
||||
string_lines: Vec<TextSize>,
|
||||
locator: &'a Locator<'a>,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for StringLinesVisitor {
|
||||
impl StatementVisitor<'_> for StringLinesVisitor<'_> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
if let StmtKind::Expr { value } = &stmt.node {
|
||||
if let ExprKind::Constant {
|
||||
if let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node {
|
||||
if let ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
..
|
||||
} = &value.node
|
||||
}) = &value.node
|
||||
{
|
||||
self.string_lines.push(value.start());
|
||||
for line in UniversalNewlineIterator::with_offset(
|
||||
self.locator.slice(value.range()),
|
||||
value.start(),
|
||||
) {
|
||||
self.string_lines.push(line.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
visitor::walk_stmt(self, stmt);
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StringLinesVisitor<'a> {
|
||||
fn new(locator: &'a Locator<'a>) -> Self {
|
||||
Self {
|
||||
string_lines: Vec::new(),
|
||||
locator,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract doc lines (standalone strings) start positions from an AST.
|
||||
pub fn doc_lines_from_ast(python_ast: &Suite) -> Vec<TextSize> {
|
||||
let mut visitor = StringLinesVisitor::default();
|
||||
pub(crate) fn doc_lines_from_ast(python_ast: &Suite, locator: &Locator) -> Vec<TextSize> {
|
||||
let mut visitor = StringLinesVisitor::new(locator);
|
||||
visitor.visit_body(python_ast);
|
||||
visitor.string_lines
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustpython_parser::ast::{Expr, Stmt};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_semantic::analyze::visibility::{
|
||||
class_visibility, function_visibility, method_visibility, Modifier, Visibility, VisibleScope,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DefinitionKind<'a> {
|
||||
Module,
|
||||
Package,
|
||||
Class(&'a Stmt),
|
||||
NestedClass(&'a Stmt),
|
||||
Function(&'a Stmt),
|
||||
NestedFunction(&'a Stmt),
|
||||
Method(&'a Stmt),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Definition<'a> {
|
||||
pub kind: DefinitionKind<'a>,
|
||||
pub docstring: Option<&'a Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Docstring<'a> {
|
||||
pub kind: DefinitionKind<'a>,
|
||||
pub expr: &'a Expr,
|
||||
/// The content of the docstring, including the leading and trailing quotes.
|
||||
pub contents: &'a str,
|
||||
|
||||
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
||||
pub body_range: TextRange,
|
||||
pub indentation: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Docstring<'a> {
|
||||
pub fn body(&self) -> DocstringBody {
|
||||
DocstringBody { docstring: self }
|
||||
}
|
||||
|
||||
pub const fn start(&self) -> TextSize {
|
||||
self.expr.start()
|
||||
}
|
||||
|
||||
pub const fn end(&self) -> TextSize {
|
||||
self.expr.end()
|
||||
}
|
||||
|
||||
pub const fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
|
||||
pub fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DocstringBody<'a> {
|
||||
docstring: &'a Docstring<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DocstringBody<'a> {
|
||||
#[inline]
|
||||
pub fn start(self) -> TextSize {
|
||||
self.range().start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn end(self) -> TextSize {
|
||||
self.range().end()
|
||||
}
|
||||
|
||||
pub fn range(self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'a str {
|
||||
&self.docstring.contents[self.docstring.body_range]
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DocstringBody<'_> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DocstringBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DocstringBody")
|
||||
.field("text", &self.as_str())
|
||||
.field("range", &self.range())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Documentable {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
pub fn transition_scope(scope: VisibleScope, stmt: &Stmt, kind: Documentable) -> VisibleScope {
|
||||
match kind {
|
||||
Documentable::Function => VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
visibility: match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: Visibility::Public,
|
||||
} => function_visibility(stmt),
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: Visibility::Public,
|
||||
} => method_visibility(stmt),
|
||||
_ => Visibility::Private,
|
||||
},
|
||||
},
|
||||
Documentable::Class => VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: Visibility::Public,
|
||||
} => class_visibility(stmt),
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: Visibility::Public,
|
||||
} => class_visibility(stmt),
|
||||
_ => Visibility::Private,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,91 @@
|
||||
//! Extract docstrings from an AST.
|
||||
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
use ruff_python_semantic::definition::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||
|
||||
/// Extract a docstring from a function or class body.
|
||||
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
let stmt = suite.first()?;
|
||||
// Require the docstring to be a standalone expression.
|
||||
let StmtKind::Expr { value } = &stmt.node else {
|
||||
let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node else {
|
||||
return None;
|
||||
};
|
||||
// Only match strings.
|
||||
if !matches!(
|
||||
&value.node,
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
}
|
||||
})
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
|
||||
/// Extract a docstring from a `Definition`.
|
||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||
match definition {
|
||||
Definition::Module(module) => docstring_from(module.python_ast),
|
||||
Definition::Member(member) => {
|
||||
if let StmtKind::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| StmtKind::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) =
|
||||
&member.stmt.node
|
||||
{
|
||||
docstring_from(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ExtractionTarget {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||
pub fn extract<'a>(
|
||||
scope: visibility::VisibleScope,
|
||||
pub(crate) fn extract_definition<'a>(
|
||||
target: ExtractionTarget,
|
||||
stmt: &'a Stmt,
|
||||
body: &'a [Stmt],
|
||||
kind: Documentable,
|
||||
) -> Definition<'a> {
|
||||
let expr = docstring_from(body);
|
||||
match kind {
|
||||
Documentable::Function => match scope {
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Function(stmt),
|
||||
docstring: expr,
|
||||
parent: DefinitionId,
|
||||
definitions: &Definitions<'a>,
|
||||
) -> Member<'a> {
|
||||
match target {
|
||||
ExtractionTarget::Function => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Function,
|
||||
stmt,
|
||||
},
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Class,
|
||||
Definition::Member(Member {
|
||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Method(stmt),
|
||||
docstring: expr,
|
||||
}) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Method,
|
||||
stmt,
|
||||
},
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedFunction(stmt),
|
||||
docstring: expr,
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedFunction,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
Documentable::Class => match scope {
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Class(stmt),
|
||||
docstring: expr,
|
||||
ExtractionTarget::Class => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Class,
|
||||
stmt,
|
||||
},
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Class,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
visibility::VisibleScope {
|
||||
modifier: visibility::Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
docstring: expr,
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedClass,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,89 @@
|
||||
pub mod definition;
|
||||
pub mod extraction;
|
||||
pub mod google;
|
||||
pub mod numpy;
|
||||
pub mod sections;
|
||||
pub mod styles;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use ruff_python_semantic::definition::Definition;
|
||||
|
||||
pub(crate) mod extraction;
|
||||
pub(crate) mod google;
|
||||
pub(crate) mod numpy;
|
||||
pub(crate) mod sections;
|
||||
pub(crate) mod styles;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Docstring<'a> {
|
||||
pub(crate) definition: &'a Definition<'a>,
|
||||
pub(crate) expr: &'a Expr,
|
||||
/// The content of the docstring, including the leading and trailing quotes.
|
||||
pub(crate) contents: &'a str,
|
||||
|
||||
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
||||
pub(crate) body_range: TextRange,
|
||||
pub(crate) indentation: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Docstring<'a> {
|
||||
pub(crate) fn body(&self) -> DocstringBody {
|
||||
DocstringBody { docstring: self }
|
||||
}
|
||||
|
||||
pub(crate) const fn start(&self) -> TextSize {
|
||||
self.expr.start()
|
||||
}
|
||||
|
||||
pub(crate) const fn end(&self) -> TextSize {
|
||||
self.expr.end()
|
||||
}
|
||||
|
||||
pub(crate) const fn range(&self) -> TextRange {
|
||||
self.expr.range()
|
||||
}
|
||||
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct DocstringBody<'a> {
|
||||
docstring: &'a Docstring<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DocstringBody<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn start(self) -> TextSize {
|
||||
self.range().start()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn end(self) -> TextSize {
|
||||
self.range().end()
|
||||
}
|
||||
|
||||
pub(crate) fn range(self) -> TextRange {
|
||||
self.docstring.body_range + self.docstring.start()
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(self) -> &'a str {
|
||||
&self.docstring.contents[self.docstring.body_range]
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DocstringBody<'_> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DocstringBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DocstringBody")
|
||||
.field("text", &self.as_str())
|
||||
.field("range", &self.range())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::docstrings::definition::{Docstring, DocstringBody};
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::docstrings::{Docstring, DocstringBody};
|
||||
use ruff_python_ast::whitespace;
|
||||
|
||||
#[derive(EnumIter, PartialEq, Eq, Debug, Clone, Copy)]
|
||||
@@ -137,7 +137,7 @@ pub(crate) struct SectionContexts<'a> {
|
||||
|
||||
impl<'a> SectionContexts<'a> {
|
||||
/// Extract all `SectionContext` values from a docstring.
|
||||
pub fn from_docstring(docstring: &'a Docstring<'a>, style: SectionStyle) -> Self {
|
||||
pub(crate) fn from_docstring(docstring: &'a Docstring<'a>, style: SectionStyle) -> Self {
|
||||
let contents = docstring.body();
|
||||
|
||||
let mut contexts = Vec::new();
|
||||
@@ -190,11 +190,11 @@ impl<'a> SectionContexts<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.contexts.len()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> SectionContextsIter {
|
||||
pub(crate) fn iter(&self) -> SectionContextsIter {
|
||||
SectionContextsIter {
|
||||
docstring_body: self.docstring.body(),
|
||||
inner: self.contexts.iter(),
|
||||
|
||||
@@ -13,7 +13,7 @@ static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").
|
||||
|
||||
/// Parse a comma-separated list of `RuleSelector` values (e.g.,
|
||||
/// "F401,E501").
|
||||
pub fn parse_prefix_codes(value: &str) -> Vec<RuleSelector> {
|
||||
pub(crate) fn parse_prefix_codes(value: &str) -> Vec<RuleSelector> {
|
||||
let mut codes: Vec<RuleSelector> = vec![];
|
||||
for code in COMMA_SEPARATED_LIST_RE.split(value) {
|
||||
let code = code.trim();
|
||||
@@ -30,7 +30,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<RuleSelector> {
|
||||
}
|
||||
|
||||
/// Parse a comma-separated list of strings (e.g., "__init__.py,__main__.py").
|
||||
pub fn parse_strings(value: &str) -> Vec<String> {
|
||||
pub(crate) fn parse_strings(value: &str) -> Vec<String> {
|
||||
COMMA_SEPARATED_LIST_RE
|
||||
.split(value)
|
||||
.map(str::trim)
|
||||
@@ -40,7 +40,7 @@ pub fn parse_strings(value: &str) -> Vec<String> {
|
||||
}
|
||||
|
||||
/// Parse a boolean.
|
||||
pub fn parse_bool(value: &str) -> Result<bool> {
|
||||
pub(crate) fn parse_bool(value: &str) -> Result<bool> {
|
||||
match value.trim() {
|
||||
"true" => Ok(true),
|
||||
"false" => Ok(false),
|
||||
@@ -138,7 +138,7 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
|
||||
|
||||
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
|
||||
/// See: <https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45>
|
||||
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
|
||||
pub(crate) fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
|
||||
if value.trim().is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair
|
||||
}
|
||||
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pub(crate) fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> FxHashMap<String, Vec<RuleSelector>> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();
|
||||
|
||||
@@ -175,7 +175,7 @@ impl From<&Plugin> for Linter {
|
||||
///
|
||||
/// For example, if the user specified a `mypy-init-return` setting, we should
|
||||
/// infer that `flake8-annotations` is active.
|
||||
pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
|
||||
pub(crate) fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
|
||||
let mut plugins = BTreeSet::new();
|
||||
for key in flake8.keys() {
|
||||
match key.as_str() {
|
||||
@@ -292,7 +292,7 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
|
||||
///
|
||||
/// For example, if the user ignores `ANN101`, we should infer that
|
||||
/// `flake8-annotations` is active.
|
||||
pub fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec<Plugin> {
|
||||
pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec<Plugin> {
|
||||
// Ignore cases in which we've knowingly changed rule prefixes.
|
||||
[
|
||||
Plugin::Flake82020,
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::{Codegen, CodegenState, ImportAlias, Name, NameOrAttribute};
|
||||
use ruff_text_size::TextSize;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{Stmt, StmtKind, Suite};
|
||||
use rustpython_parser::ast::{self, Stmt, StmtKind, Suite};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -18,10 +17,7 @@ pub struct Importer<'a> {
|
||||
python_ast: &'a Suite,
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
/// A map from module name to top-level `StmtKind::ImportFrom` statements.
|
||||
import_from_map: FxHashMap<&'a str, &'a Stmt>,
|
||||
/// The last top-level import statement.
|
||||
trailing_import: Option<&'a Stmt>,
|
||||
ordered_imports: Vec<&'a Stmt>,
|
||||
}
|
||||
|
||||
impl<'a> Importer<'a> {
|
||||
@@ -30,34 +26,21 @@ impl<'a> Importer<'a> {
|
||||
python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
import_from_map: FxHashMap::default(),
|
||||
trailing_import: None,
|
||||
ordered_imports: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit a top-level import statement.
|
||||
pub fn visit_import(&mut self, import: &'a Stmt) {
|
||||
// Store a reference to the import statement in the appropriate map.
|
||||
match &import.node {
|
||||
StmtKind::Import { .. } => {
|
||||
// Nothing to do here, we don't extend top-level `import` statements at all, so
|
||||
// no need to track them.
|
||||
}
|
||||
StmtKind::ImportFrom { module, level, .. } => {
|
||||
// Store a reverse-map from module name to `import ... from` statement.
|
||||
if level.map_or(true, |level| level == 0) {
|
||||
if let Some(module) = module {
|
||||
self.import_from_map.insert(module.as_str(), import);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Expected StmtKind::Import | StmtKind::ImportFrom");
|
||||
}
|
||||
}
|
||||
self.ordered_imports.push(import);
|
||||
}
|
||||
|
||||
// Store a reference to the last top-level import statement.
|
||||
self.trailing_import = Some(import);
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
fn preceding_import(&self, at: TextSize) -> Option<&Stmt> {
|
||||
self.ordered_imports
|
||||
.partition_point(|stmt| stmt.start() < at)
|
||||
.checked_sub(1)
|
||||
.map(|idx| self.ordered_imports[idx])
|
||||
}
|
||||
|
||||
/// Add an import statement to import the given module.
|
||||
@@ -65,9 +48,9 @@ impl<'a> Importer<'a> {
|
||||
/// If there are no existing imports, the new import will be added at the top
|
||||
/// of the file. Otherwise, it will be added after the most recent top-level
|
||||
/// import statement.
|
||||
pub fn add_import(&self, import: &AnyImport) -> Edit {
|
||||
pub fn add_import(&self, import: &AnyImport, at: TextSize) -> Edit {
|
||||
let required_import = import.to_string();
|
||||
if let Some(stmt) = self.trailing_import {
|
||||
if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
let Insertion {
|
||||
prefix,
|
||||
@@ -88,10 +71,28 @@ impl<'a> Importer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the top-level [`Stmt`] that imports the given module using `StmtKind::ImportFrom`.
|
||||
/// if it exists.
|
||||
pub fn get_import_from(&self, module: &str) -> Option<&Stmt> {
|
||||
self.import_from_map.get(module).copied()
|
||||
/// Return the top-level [`Stmt`] that imports the given module using `StmtKind::ImportFrom`
|
||||
/// preceding the given position, if any.
|
||||
pub fn find_import_from(&self, module: &str, at: TextSize) -> Option<&Stmt> {
|
||||
let mut import_from = None;
|
||||
for stmt in &self.ordered_imports {
|
||||
if stmt.start() >= at {
|
||||
break;
|
||||
}
|
||||
if let StmtKind::ImportFrom(ast::StmtImportFrom {
|
||||
module: name,
|
||||
level,
|
||||
..
|
||||
}) = &stmt.node
|
||||
{
|
||||
if level.map_or(true, |level| level.to_u32() == 0)
|
||||
&& name.as_ref().map_or(false, |name| name == module)
|
||||
{
|
||||
import_from = Some(*stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
import_from
|
||||
}
|
||||
|
||||
/// Add the given member to an existing `StmtKind::ImportFrom` statement.
|
||||
@@ -177,7 +178,8 @@ fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
|
||||
/// along with a trailing newline suffix.
|
||||
fn end_of_statement_insertion(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
|
||||
let location = stmt.end();
|
||||
let mut tokens = lexer::lex_located(locator.after(location), Mode::Module, location).flatten();
|
||||
let mut tokens =
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten();
|
||||
if let Some((Tok::Semi, range)) = tokens.next() {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
@@ -210,7 +212,7 @@ fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) ->
|
||||
let mut location = if let Some(location) = match_docstring_end(body) {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as an
|
||||
// inline statement;
|
||||
let first_token = lexer::lex_located(locator.after(location), Mode::Module, location)
|
||||
let first_token = lexer::lex_starts_at(locator.after(location), Mode::Module, location)
|
||||
.flatten()
|
||||
.next();
|
||||
if let Some((Tok::Semi, range)) = first_token {
|
||||
@@ -225,7 +227,7 @@ fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) ->
|
||||
|
||||
// Skip over any comments and empty lines.
|
||||
for (tok, range) in
|
||||
lexer::lex_located(locator.after(location), Mode::Module, location).flatten()
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
|
||||
{
|
||||
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
|
||||
location = locator.full_line_end(range.end());
|
||||
@@ -240,11 +242,11 @@ fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) ->
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_python_ast::newlines::LineEnding;
|
||||
use ruff_text_size::TextSize;
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use ruff_python_ast::newlines::LineEnding;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use crate::importer::{top_of_file_insertion, Insertion};
|
||||
|
||||
@@ -25,13 +25,13 @@ enum State {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StateMachine {
|
||||
pub(crate) struct StateMachine {
|
||||
state: State,
|
||||
bracket_count: usize,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub fn consume(&mut self, tok: &Tok) -> bool {
|
||||
pub(crate) fn consume(&mut self, tok: &Tok) -> bool {
|
||||
match tok {
|
||||
Tok::NonLogicalNewline
|
||||
| Tok::Newline
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub mod docstring_detection;
|
||||
pub(crate) mod docstring_detection;
|
||||
|
||||
@@ -77,7 +77,6 @@ pub fn check_path(
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
autofix: flags::Autofix,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
@@ -99,7 +98,7 @@ pub fn check_path(
|
||||
.any(|rule_code| rule_code.lint_source().is_tokens())
|
||||
{
|
||||
let is_stub = is_python_stub_file(path);
|
||||
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix, is_stub));
|
||||
diagnostics.extend(check_tokens(locator, &tokens, settings, is_stub));
|
||||
}
|
||||
|
||||
// Run the filesystem-based rules.
|
||||
@@ -119,11 +118,7 @@ pub fn check_path(
|
||||
{
|
||||
#[cfg(feature = "logical_lines")]
|
||||
diagnostics.extend(crate::checkers::logical_lines::check_logical_lines(
|
||||
&tokens,
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
flags::Autofix::Enabled,
|
||||
&tokens, locator, stylist, settings,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -148,7 +143,6 @@ pub fn check_path(
|
||||
indexer,
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
@@ -162,7 +156,6 @@ pub fn check_path(
|
||||
&directives.isort,
|
||||
settings,
|
||||
stylist,
|
||||
autofix,
|
||||
path,
|
||||
package,
|
||||
);
|
||||
@@ -170,7 +163,7 @@ pub fn check_path(
|
||||
diagnostics.extend(import_diagnostics);
|
||||
}
|
||||
if use_doc_lines {
|
||||
doc_lines.extend(doc_lines_from_ast(&python_ast));
|
||||
doc_lines.extend(doc_lines_from_ast(&python_ast, locator));
|
||||
}
|
||||
}
|
||||
Err(parse_error) => {
|
||||
@@ -198,7 +191,7 @@ pub fn check_path(
|
||||
.any(|rule_code| rule_code.lint_source().is_physical_lines())
|
||||
{
|
||||
diagnostics.extend(check_physical_lines(
|
||||
path, locator, stylist, indexer, &doc_lines, settings, autofix,
|
||||
path, locator, stylist, indexer, &doc_lines, settings,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -223,7 +216,6 @@ pub fn check_path(
|
||||
indexer.comment_ranges(),
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
error.as_ref().map_or(autofix, |_| flags::Autofix::Disabled),
|
||||
);
|
||||
if noqa.into() {
|
||||
for index in ignored.iter().rev() {
|
||||
@@ -293,7 +285,6 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
&directives,
|
||||
settings,
|
||||
flags::Noqa::Disabled,
|
||||
flags::Autofix::Disabled,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
@@ -320,7 +311,6 @@ pub fn lint_only(
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
autofix: flags::Autofix,
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_rustpython::tokenize(contents);
|
||||
@@ -353,7 +343,6 @@ pub fn lint_only(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
autofix,
|
||||
);
|
||||
|
||||
result.map(|(diagnostics, imports)| {
|
||||
@@ -444,7 +433,6 @@ pub fn lint_fix<'a>(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
flags::Autofix::Enabled,
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
@@ -509,8 +497,8 @@ fn collect_rule_codes(rules: impl IntoIterator<Item = Rule>) -> String {
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[Diagnostic]) {
|
||||
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
|
||||
if cfg!(debug_assertions) {
|
||||
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
|
||||
eprintln!(
|
||||
"{}: Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"debug error".red().bold(),
|
||||
@@ -528,13 +516,14 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BInfinite%20loop%5D
|
||||
|
||||
...quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
MAX_ITERATIONS,
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
codes
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -546,8 +535,8 @@ fn report_autofix_syntax_error(
|
||||
error: &ParseError,
|
||||
rules: impl IntoIterator<Item = Rule>,
|
||||
) {
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
let codes = collect_rule_codes(rules);
|
||||
eprintln!(
|
||||
"{}: Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"error".red().bold(),
|
||||
@@ -565,12 +554,13 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BAutofix%20error%5D
|
||||
|
||||
...quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -9,7 +9,7 @@ use fern;
|
||||
use log::Level;
|
||||
use once_cell::sync::Lazy;
|
||||
use ruff_python_ast::source_code::SourceCode;
|
||||
use rustpython_parser::ParseError;
|
||||
use rustpython_parser::{ParseError, ParseErrorType};
|
||||
|
||||
pub(crate) static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
|
||||
|
||||
@@ -145,17 +145,89 @@ impl<'a> DisplayParseError<'a> {
|
||||
|
||||
impl Display for DisplayParseError<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let source_location = self.source_code.source_location(self.error.location);
|
||||
let source_location = self.source_code.source_location(self.error.offset);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{header} {path}{colon}{row}{colon}{column}{colon} {inner}",
|
||||
header = "Failed to parse ".bold(),
|
||||
header = "Failed to parse".bold(),
|
||||
path = fs::relativize_path(Path::new(&self.error.source_path)).bold(),
|
||||
row = source_location.row,
|
||||
column = source_location.column,
|
||||
colon = ":".cyan(),
|
||||
inner = &self.error.error
|
||||
inner = &DisplayParseErrorType(&self.error.error)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayParseErrorType<'a>(&'a ParseErrorType);
|
||||
|
||||
impl<'a> DisplayParseErrorType<'a> {
|
||||
pub(crate) fn new(error: &'a ParseErrorType) -> Self {
|
||||
Self(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DisplayParseErrorType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0 {
|
||||
ParseErrorType::Eof => write!(f, "Expected token but reached end of file."),
|
||||
ParseErrorType::ExtraToken(ref tok) => write!(
|
||||
f,
|
||||
"Got extraneous token: {tok}",
|
||||
tok = TruncateAtNewline(&tok)
|
||||
),
|
||||
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
|
||||
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
|
||||
if let Some(expected) = expected.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
"expected '{expected}', but got {tok}",
|
||||
tok = TruncateAtNewline(&tok)
|
||||
)
|
||||
} else {
|
||||
write!(f, "unexpected token {tok}", tok = TruncateAtNewline(&tok))
|
||||
}
|
||||
}
|
||||
ParseErrorType::Lexical(ref error) => write!(f, "{error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncates the display text before the first newline character to avoid line breaks.
|
||||
struct TruncateAtNewline<'a>(&'a dyn Display);
|
||||
|
||||
impl Display for TruncateAtNewline<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
struct TruncateAdapter<'a> {
|
||||
inner: &'a mut dyn std::fmt::Write,
|
||||
after_new_line: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Write for TruncateAdapter<'_> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
if self.after_new_line {
|
||||
Ok(())
|
||||
} else {
|
||||
if let Some(end) = s.find(['\n', '\r']) {
|
||||
self.inner.write_str(&s[..end])?;
|
||||
self.inner.write_str("\u{23ce}...")?;
|
||||
self.after_new_line = true;
|
||||
Ok(())
|
||||
} else {
|
||||
self.inner.write_str(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write!(
|
||||
TruncateAdapter {
|
||||
inner: f,
|
||||
after_new_line: false,
|
||||
},
|
||||
"{}",
|
||||
self.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::message::Message;
|
||||
use colored::{Color, ColoredString, Colorize, Styles};
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_python_ast::source_code::{OneIndexed, SourceFile};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
@@ -21,15 +21,11 @@ pub(super) struct Diff<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Diff<'a> {
|
||||
pub fn from_message(message: &'a Message) -> Option<Diff> {
|
||||
if message.fix.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Diff {
|
||||
source_code: &message.file,
|
||||
fix: &message.fix,
|
||||
})
|
||||
}
|
||||
pub(crate) fn from_message(message: &'a Message) -> Option<Diff> {
|
||||
message.fix.as_ref().map(|fix| Diff {
|
||||
source_code: &message.file,
|
||||
fix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +47,13 @@ impl Display for Diff<'_> {
|
||||
|
||||
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
|
||||
|
||||
writeln!(f, "{}", "ℹ Suggested fix".blue())?;
|
||||
let message = match self.fix.applicability() {
|
||||
Applicability::Automatic => "Fix",
|
||||
Applicability::Suggested => "Suggested fix",
|
||||
Applicability::Manual => "Possible fix",
|
||||
Applicability::Unspecified => "Suggested fix", // For backwards compatibility, unspecified fixes are 'suggested'
|
||||
};
|
||||
writeln!(f, "ℹ {}", message.blue())?;
|
||||
|
||||
let (largest_old, largest_new) = diff
|
||||
.ops()
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
self.column_length.get() - calculate_print_width(start_location.column).get()
|
||||
),
|
||||
code_and_body = RuleCodeAndBody {
|
||||
message_kind: &message.kind,
|
||||
message,
|
||||
show_fix_status: self.show_fix_status
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::source_code::{SourceCode, SourceLocation};
|
||||
use ruff_python_ast::source_code::SourceCode;
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::json;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -37,14 +37,13 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
for message in self.messages {
|
||||
let source_code = message.file.to_source_code();
|
||||
|
||||
let fix = if message.fix.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(json!({
|
||||
let fix = message.fix.as_ref().map(|fix| {
|
||||
json!({
|
||||
"applicability": fix.applicability(),
|
||||
"message": message.kind.suggestion.as_deref(),
|
||||
"edits": &ExpandedEdits { edits: message.fix.edits(), source_code: &source_code },
|
||||
}))
|
||||
};
|
||||
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code },
|
||||
})
|
||||
});
|
||||
|
||||
let start_location = source_code.source_location(message.start());
|
||||
let end_location = source_code.source_location(message.end());
|
||||
@@ -80,12 +79,10 @@ impl Serialize for ExpandedEdits<'_> {
|
||||
let mut s = serializer.serialize_seq(Some(self.edits.len()))?;
|
||||
|
||||
for edit in self.edits {
|
||||
let start_location = self.source_code.source_location(edit.start());
|
||||
let end_location = self.source_code.source_location(edit.end());
|
||||
let value = json!({
|
||||
"content": edit.content().unwrap_or_default(),
|
||||
"location": to_zero_indexed_column(&start_location),
|
||||
"end_location": to_zero_indexed_column(&end_location)
|
||||
"location": self.source_code.source_location(edit.start()),
|
||||
"end_location": self.source_code.source_location(edit.end())
|
||||
});
|
||||
|
||||
s.serialize_element(&value)?;
|
||||
@@ -95,13 +92,6 @@ impl Serialize for ExpandedEdits<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_zero_indexed_column(location: &SourceLocation) -> Value {
|
||||
json!({
|
||||
"row": location.row,
|
||||
"column": location.column.to_zero_indexed()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
|
||||
@@ -33,7 +33,7 @@ use ruff_python_ast::source_code::{SourceFile, SourceLocation};
|
||||
pub struct Message {
|
||||
pub kind: DiagnosticKind,
|
||||
pub range: TextRange,
|
||||
pub fix: Fix,
|
||||
pub fix: Option<Fix>,
|
||||
pub file: SourceFile,
|
||||
pub noqa_offset: TextSize,
|
||||
}
|
||||
@@ -181,7 +181,11 @@ def fibonacci(n):
|
||||
multiple: false,
|
||||
},
|
||||
TextRange::new(TextSize::from(7), TextSize::from(9)),
|
||||
);
|
||||
)
|
||||
.with_fix(Fix::suggested(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(0),
|
||||
TextSize::from(10),
|
||||
))));
|
||||
|
||||
let fib_source = SourceFileBuilder::new("fib.py", fib).finish();
|
||||
|
||||
@@ -191,10 +195,10 @@ def fibonacci(n):
|
||||
},
|
||||
TextRange::new(TextSize::from(94), TextSize::from(95)),
|
||||
)
|
||||
.with_fix(Fix::new(vec![Edit::deletion(
|
||||
.with_fix(Fix::suggested(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
TextSize::from(99),
|
||||
)]));
|
||||
)));
|
||||
|
||||
let file_2 = r#"if a == 1: pass"#;
|
||||
|
||||
|
||||
@@ -6,7 +6,23 @@ expression: content
|
||||
{
|
||||
"code": "F401",
|
||||
"message": "`os` imported but unused",
|
||||
"fix": null,
|
||||
"fix": {
|
||||
"applicability": "Suggested",
|
||||
"message": "Remove unused import: `os`",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"location": {
|
||||
"row": 1,
|
||||
"column": 1
|
||||
},
|
||||
"end_location": {
|
||||
"row": 2,
|
||||
"column": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"location": {
|
||||
"row": 1,
|
||||
"column": 8
|
||||
@@ -22,17 +38,18 @@ expression: content
|
||||
"code": "F841",
|
||||
"message": "Local variable `x` is assigned to but never used",
|
||||
"fix": {
|
||||
"applicability": "Suggested",
|
||||
"message": "Remove assignment to unused variable `x`",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"location": {
|
||||
"row": 6,
|
||||
"column": 4
|
||||
"column": 5
|
||||
},
|
||||
"end_location": {
|
||||
"row": 6,
|
||||
"column": 9
|
||||
"column": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ use annotate_snippets::display_list::{DisplayList, FormatOptions};
|
||||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_diagnostics::DiagnosticKind;
|
||||
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::borrow::Cow;
|
||||
@@ -93,7 +92,7 @@ impl Emitter for TextEmitter {
|
||||
col = diagnostic_location.column,
|
||||
sep = ":".cyan(),
|
||||
code_and_body = RuleCodeAndBody {
|
||||
message_kind: &message.kind,
|
||||
message,
|
||||
show_fix_status: self.flags.contains(EmitterFlags::SHOW_FIX_STATUS)
|
||||
}
|
||||
)?;
|
||||
@@ -114,45 +113,35 @@ impl Emitter for TextEmitter {
|
||||
}
|
||||
|
||||
pub(super) struct RuleCodeAndBody<'a> {
|
||||
pub message_kind: &'a DiagnosticKind,
|
||||
pub show_fix_status: bool,
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) show_fix_status: bool,
|
||||
}
|
||||
|
||||
impl Display for RuleCodeAndBody<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.show_fix_status && self.message_kind.fixable {
|
||||
let kind = &self.message.kind;
|
||||
|
||||
if self.show_fix_status && self.message.fix.is_some() {
|
||||
write!(
|
||||
f,
|
||||
"{code} {autofix}{body}",
|
||||
code = self
|
||||
.message_kind
|
||||
.rule()
|
||||
.noqa_code()
|
||||
.to_string()
|
||||
.red()
|
||||
.bold(),
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
autofix = format_args!("[{}] ", "*".cyan()),
|
||||
body = self.message_kind.body,
|
||||
body = kind.body,
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = self
|
||||
.message_kind
|
||||
.rule()
|
||||
.noqa_code()
|
||||
.to_string()
|
||||
.red()
|
||||
.bold(),
|
||||
body = self.message_kind.body,
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
body = kind.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub message: &'a Message,
|
||||
pub(crate) message: &'a Message,
|
||||
}
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
@@ -245,36 +234,39 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}
|
||||
|
||||
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
static TAB_SIZE: TextSize = TextSize::new(4);
|
||||
static TAB_SIZE: u32 = 4; // TODO(jonathan): use `pycodestyle.tab-size`
|
||||
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
let mut column = 0;
|
||||
|
||||
for (index, m) in source.match_indices(['\t', '\n', '\r']) {
|
||||
match m {
|
||||
"\t" => {
|
||||
let tab_width = TAB_SIZE - TextSize::new(column % 4);
|
||||
for (index, c) in source.chars().enumerate() {
|
||||
match c {
|
||||
'\t' => {
|
||||
let tab_width = TAB_SIZE - column % TAB_SIZE;
|
||||
column += tab_width;
|
||||
|
||||
if index < usize::from(annotation_range.start()) {
|
||||
range += tab_width - TextSize::new(1);
|
||||
range += TextSize::new(tab_width - 1);
|
||||
} else if index < usize::from(annotation_range.end()) {
|
||||
range = range.add_end(tab_width - TextSize::new(1));
|
||||
range = range.add_end(TextSize::new(tab_width - 1));
|
||||
}
|
||||
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
for _ in 0..u32::from(tab_width) {
|
||||
for _ in 0..tab_width {
|
||||
result.push(' ');
|
||||
}
|
||||
|
||||
last_end = index + 1;
|
||||
}
|
||||
"\n" | "\r" => {
|
||||
'\n' | '\r' => {
|
||||
column = 0;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
_ => {
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Directive<'a> {
|
||||
pub(crate) enum Directive<'a> {
|
||||
None,
|
||||
// (leading spaces, noqa_range, trailing_spaces)
|
||||
All(TextSize, TextRange, TextSize),
|
||||
@@ -36,7 +36,7 @@ pub enum Directive<'a> {
|
||||
}
|
||||
|
||||
/// Extract the noqa `Directive` from a line of Python source code.
|
||||
pub fn extract_noqa_directive<'a>(range: TextRange, locator: &'a Locator) -> Directive<'a> {
|
||||
pub(crate) fn extract_noqa_directive<'a>(range: TextRange, locator: &'a Locator) -> Directive<'a> {
|
||||
let text = &locator.contents()[range];
|
||||
match NOQA_LINE_REGEX.captures(text) {
|
||||
Some(caps) => match (
|
||||
@@ -123,7 +123,7 @@ fn parse_file_exemption(line: &str) -> ParsedExemption {
|
||||
|
||||
/// Returns `true` if the string list of `codes` includes `code` (or an alias
|
||||
/// thereof).
|
||||
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
|
||||
pub(crate) fn includes(needle: Rule, haystack: &[&str]) -> bool {
|
||||
let needle = needle.noqa_code();
|
||||
haystack
|
||||
.iter()
|
||||
@@ -131,7 +131,7 @@ pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`.
|
||||
pub fn rule_is_ignored(
|
||||
pub(crate) fn rule_is_ignored(
|
||||
code: Rule,
|
||||
offset: TextSize,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
@@ -146,7 +146,7 @@ pub fn rule_is_ignored(
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileExemption {
|
||||
pub(crate) enum FileExemption {
|
||||
None,
|
||||
All,
|
||||
Codes(Vec<NoqaCode>),
|
||||
@@ -154,7 +154,7 @@ pub enum FileExemption {
|
||||
|
||||
/// Extract the [`FileExemption`] for a given Python source file, enumerating any rules that are
|
||||
/// globally ignored within the file.
|
||||
pub fn file_exemption(contents: &str, comment_ranges: &[TextRange]) -> FileExemption {
|
||||
pub(crate) fn file_exemption(contents: &str, comment_ranges: &[TextRange]) -> FileExemption {
|
||||
let mut exempt_codes: Vec<NoqaCode> = vec![];
|
||||
|
||||
for range in comment_ranges {
|
||||
@@ -184,7 +184,7 @@ pub fn file_exemption(contents: &str, comment_ranges: &[TextRange]) -> FileExemp
|
||||
}
|
||||
|
||||
/// Adds noqa comments to suppress all diagnostics of a file.
|
||||
pub fn add_noqa(
|
||||
pub(crate) fn add_noqa(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
@@ -368,9 +368,9 @@ fn push_codes<I: Display>(str: &mut String, codes: impl Iterator<Item = I>) {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoqaDirectiveLine<'a> {
|
||||
// The range of the text line for which the noqa directive applies.
|
||||
pub range: TextRange,
|
||||
pub directive: Directive<'a>,
|
||||
pub matches: Vec<NoqaCode>,
|
||||
pub(crate) range: TextRange,
|
||||
pub(crate) directive: Directive<'a>,
|
||||
pub(crate) matches: Vec<NoqaCode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -379,7 +379,10 @@ pub(crate) struct NoqaDirectives<'a> {
|
||||
}
|
||||
|
||||
impl<'a> NoqaDirectives<'a> {
|
||||
pub fn from_commented_ranges(comment_ranges: &[TextRange], locator: &'a Locator<'a>) -> Self {
|
||||
pub(crate) fn from_commented_ranges(
|
||||
comment_ranges: &[TextRange],
|
||||
locator: &'a Locator<'a>,
|
||||
) -> Self {
|
||||
let mut directives = Vec::new();
|
||||
|
||||
for comment_range in comment_ranges {
|
||||
@@ -409,11 +412,11 @@ impl<'a> NoqaDirectives<'a> {
|
||||
Self { inner: directives }
|
||||
}
|
||||
|
||||
pub fn find_line_with_directive(&self, offset: TextSize) -> Option<&NoqaDirectiveLine> {
|
||||
pub(crate) fn find_line_with_directive(&self, offset: TextSize) -> Option<&NoqaDirectiveLine> {
|
||||
self.find_line_index(offset).map(|index| &self.inner[index])
|
||||
}
|
||||
|
||||
pub fn find_line_with_directive_mut(
|
||||
pub(crate) fn find_line_with_directive_mut(
|
||||
&mut self,
|
||||
offset: TextSize,
|
||||
) -> Option<&mut NoqaDirectiveLine<'a>> {
|
||||
@@ -438,7 +441,7 @@ impl<'a> NoqaDirectives<'a> {
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn lines(&self) -> &[NoqaDirectiveLine] {
|
||||
pub(crate) fn lines(&self) -> &[NoqaDirectiveLine] {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ ruff_macros::register_rules!(
|
||||
rules::pylint::rules::LoggingTooFewArgs,
|
||||
rules::pylint::rules::LoggingTooManyArgs,
|
||||
rules::pylint::rules::UnexpectedSpecialMethodSignature,
|
||||
rules::pylint::rules::NestedMinMax,
|
||||
// flake8-builtins
|
||||
rules::flake8_builtins::rules::BuiltinVariableShadowing,
|
||||
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
|
||||
@@ -505,10 +506,11 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_datetimez::rules::CallDateToday,
|
||||
rules::flake8_datetimez::rules::CallDateFromtimestamp,
|
||||
// pygrep-hooks
|
||||
rules::pygrep_hooks::rules::Eval,
|
||||
rules::pygrep_hooks::rules::DeprecatedLogWarn,
|
||||
rules::pygrep_hooks::rules::BlanketTypeIgnore,
|
||||
rules::pygrep_hooks::rules::BlanketNOQA,
|
||||
rules::pygrep_hooks::rules::BlanketTypeIgnore,
|
||||
rules::pygrep_hooks::rules::DeprecatedLogWarn,
|
||||
rules::pygrep_hooks::rules::Eval,
|
||||
rules::pygrep_hooks::rules::InvalidMockAccess,
|
||||
// pandas-vet
|
||||
rules::pandas_vet::rules::PandasUseOfInplaceArgument,
|
||||
rules::pandas_vet::rules::PandasUseOfDotIsNull,
|
||||
@@ -669,6 +671,8 @@ ruff_macros::register_rules!(
|
||||
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
|
||||
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
|
||||
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
|
||||
// flynt
|
||||
rules::flynt::rules::StaticJoinToFString,
|
||||
);
|
||||
|
||||
pub trait AsRule {
|
||||
@@ -824,6 +828,9 @@ pub enum Linter {
|
||||
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
|
||||
#[prefix = "TRY"]
|
||||
Tryceratops,
|
||||
/// [flynt](https://pypi.org/project/flynt/)
|
||||
#[prefix = "FLY"]
|
||||
Flynt,
|
||||
/// NumPy-specific rules
|
||||
#[prefix = "NPY"]
|
||||
Numpy,
|
||||
|
||||
@@ -3,14 +3,16 @@ use ruff_macros::CacheKey;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
const RULESET_SIZE: usize = 10;
|
||||
|
||||
/// A set of [`Rule`]s.
|
||||
///
|
||||
/// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set.
|
||||
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
|
||||
pub struct RuleSet([u64; 10]);
|
||||
pub struct RuleSet([u64; RULESET_SIZE]);
|
||||
|
||||
impl RuleSet {
|
||||
const EMPTY: [u64; 10] = [0; 10];
|
||||
const EMPTY: [u64; RULESET_SIZE] = [0; RULESET_SIZE];
|
||||
|
||||
// 64 fits into a u16 without truncation
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schemars::_serde_json::Value;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::{self, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
@@ -198,59 +194,71 @@ pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for RuleSelector {
|
||||
fn schema_name() -> String {
|
||||
"RuleSelector".to_string()
|
||||
}
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use crate::registry::RuleNamespace;
|
||||
use crate::rule_selector::{Linter, Rule, RuleCodePrefix};
|
||||
use crate::RuleSelector;
|
||||
use itertools::Itertools;
|
||||
use schemars::_serde_json::Value;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
"ALL".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
// Include some common redirect targets for those legacy selectors.
|
||||
"C9".to_string(),
|
||||
"T1".to_string(),
|
||||
"T2".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
RuleCodePrefix::iter()
|
||||
.filter(|p| {
|
||||
// Once logical lines are active by default, please remove this.
|
||||
// This is here because generate-all output otherwise depends on
|
||||
// the feature sets which makes the test running with
|
||||
// `--all-features` fail
|
||||
!Rule::from_code(&format!(
|
||||
"{}{}",
|
||||
p.linter().common_prefix(),
|
||||
p.short_code()
|
||||
))
|
||||
.unwrap()
|
||||
.lint_source()
|
||||
.is_logical_lines()
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
format!("{prefix}{code}")
|
||||
})
|
||||
.chain(Linter::iter().filter_map(|l| {
|
||||
let prefix = l.common_prefix();
|
||||
(!prefix.is_empty()).then(|| prefix.to_string())
|
||||
})),
|
||||
)
|
||||
.sorted()
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
impl JsonSchema for RuleSelector {
|
||||
fn schema_name() -> String {
|
||||
"RuleSelector".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
"ALL".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
// Include some common redirect targets for those legacy selectors.
|
||||
"C9".to_string(),
|
||||
"T1".to_string(),
|
||||
"T2".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
RuleCodePrefix::iter()
|
||||
.filter(|p| {
|
||||
// Once logical lines are active by default, please remove this.
|
||||
// This is here because generate-all output otherwise depends on
|
||||
// the feature sets which makes the test running with
|
||||
// `--all-features` fail
|
||||
!Rule::from_code(&format!(
|
||||
"{}{}",
|
||||
p.linter().common_prefix(),
|
||||
p.short_code()
|
||||
))
|
||||
.unwrap()
|
||||
.lint_source()
|
||||
.is_logical_lines()
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
format!("{prefix}{code}")
|
||||
})
|
||||
.chain(Linter::iter().filter_map(|l| {
|
||||
let prefix = l.common_prefix();
|
||||
(!prefix.is_empty()).then(|| prefix.to_string())
|
||||
})),
|
||||
)
|
||||
.sorted()
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ static PARTIAL_DICTIONARY_REGEX: Lazy<Regex> =
|
||||
static PRINT_RETURN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap());
|
||||
|
||||
/// Returns `true` if a comment contains Python code.
|
||||
pub fn comment_contains_code(line: &str, task_tags: &[String]) -> bool {
|
||||
pub(crate) fn comment_contains_code(line: &str, task_tags: &[String]) -> bool {
|
||||
let line = if let Some(line) = line.trim().strip_prefix('#') {
|
||||
line.trim_start_matches([' ', '#'])
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit};
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::settings::Settings;
|
||||
|
||||
use super::detection::comment_contains_code;
|
||||
|
||||
@@ -46,19 +46,22 @@ fn is_standalone_comment(line: &str) -> bool {
|
||||
}
|
||||
|
||||
/// ERA001
|
||||
pub fn commented_out_code(
|
||||
pub(crate) fn commented_out_code(
|
||||
locator: &Locator,
|
||||
range: TextRange,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Diagnostic> {
|
||||
let line = locator.full_lines(range);
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, range);
|
||||
if autofix.into() && settings.rules.should_fix(Rule::CommentedOutCode) {
|
||||
diagnostic.set_fix(Edit::range_deletion(locator.full_lines_range(range)));
|
||||
|
||||
if settings.rules.should_fix(Rule::CommentedOutCode) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_deletion(
|
||||
locator.full_lines_range(range),
|
||||
)));
|
||||
}
|
||||
Some(diagnostic)
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Located};
|
||||
use rustpython_parser::ast::{self, Attributed, Cmpop, Constant, Expr, ExprKind};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -121,19 +121,18 @@ fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
|
||||
}
|
||||
|
||||
/// YTT101, YTT102, YTT301, YTT303
|
||||
pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
if is_sys(checker, value, "version") {
|
||||
match &slice.node {
|
||||
ExprKind::Slice {
|
||||
ExprKind::Slice(ast::ExprSlice {
|
||||
lower: None,
|
||||
upper: Some(upper),
|
||||
step: None,
|
||||
..
|
||||
} => {
|
||||
if let ExprKind::Constant {
|
||||
}) => {
|
||||
if let ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} = &upper.node
|
||||
}) = &upper.node
|
||||
{
|
||||
if *i == BigInt::from(1)
|
||||
&& checker.settings.rules.enabled(Rule::SysVersionSlice1)
|
||||
@@ -151,10 +150,10 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
if *i == BigInt::from(2) && checker.settings.rules.enabled(Rule::SysVersion2) {
|
||||
checker
|
||||
.diagnostics
|
||||
@@ -173,23 +172,25 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
}
|
||||
|
||||
/// YTT103, YTT201, YTT203, YTT204, YTT302
|
||||
pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) {
|
||||
pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) {
|
||||
match &left.node {
|
||||
ExprKind::Subscript { value, slice, .. } if is_sys(checker, value, "version_info") => {
|
||||
if let ExprKind::Constant {
|
||||
ExprKind::Subscript(ast::ExprSubscript { value, slice, .. })
|
||||
if is_sys(checker, value, "version_info") =>
|
||||
{
|
||||
if let ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} = &slice.node
|
||||
}) = &slice.node
|
||||
{
|
||||
if *i == BigInt::from(0) {
|
||||
if let (
|
||||
[Cmpop::Eq | Cmpop::NotEq],
|
||||
[Located {
|
||||
[Attributed {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(n),
|
||||
..
|
||||
},
|
||||
}),
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
@@ -205,12 +206,12 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
|
||||
} else if *i == BigInt::from(1) {
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
[Attributed {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_),
|
||||
..
|
||||
},
|
||||
}),
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
@@ -225,17 +226,17 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Attribute { value, attr, .. }
|
||||
ExprKind::Attribute(ast::ExprAttribute { value, attr, .. })
|
||||
if is_sys(checker, value, "version_info") && attr == "minor" =>
|
||||
{
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
[Attributed {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_),
|
||||
..
|
||||
},
|
||||
}),
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
@@ -258,12 +259,12 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
|
||||
if is_sys(checker, left, "version") {
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
[Attributed {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(s),
|
||||
..
|
||||
},
|
||||
}),
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
@@ -284,7 +285,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
|
||||
}
|
||||
|
||||
/// YTT202
|
||||
pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
if checker
|
||||
.ctx
|
||||
.resolve_call_path(expr)
|
||||
|
||||
@@ -6,14 +6,18 @@ use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
/// ANN204
|
||||
pub fn add_return_annotation(locator: &Locator, stmt: &Stmt, annotation: &str) -> Result<Edit> {
|
||||
pub(crate) fn add_return_annotation(
|
||||
locator: &Locator,
|
||||
stmt: &Stmt,
|
||||
annotation: &str,
|
||||
) -> Result<Edit> {
|
||||
let contents = &locator.contents()[stmt.range()];
|
||||
|
||||
// Find the colon (following the `def` keyword).
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count: usize = 0;
|
||||
for (tok, range) in lexer::lex_located(contents, Mode::Module, stmt.start()).flatten() {
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, stmt.start()).flatten() {
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
return Ok(Edit::insertion(format!(" -> {annotation}"), range.start()));
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
use rustpython_parser::ast::{Arguments, Expr, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{self, Arguments, Expr, Stmt, StmtKind};
|
||||
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
|
||||
pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr>, &Vec<Stmt>) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
})
|
||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
} => (name, args, returns.as_ref().map(|expr| &**expr), body),
|
||||
}) => (name, args, returns.as_ref().map(|expr| &**expr), body),
|
||||
_ => panic!("Found non-FunctionDef in match_name"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = definition.kind
|
||||
pub(crate) fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
@@ -45,10 +47,16 @@ pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<Str
|
||||
|
||||
/// Return `true` if the definition is the implementation for an overloaded
|
||||
/// function.
|
||||
pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_name: &str) -> bool {
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = definition.kind
|
||||
pub(crate) fn is_overload_impl(
|
||||
checker: &Checker,
|
||||
definition: &Definition,
|
||||
overloaded_name: &str,
|
||||
) -> bool {
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
false
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt};
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::{cast, helpers};
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::analyze::visibility::Visibility;
|
||||
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||
use ruff_python_stdlib::typing::SIMPLE_MAGIC_RETURN_TYPES;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
use super::fixes;
|
||||
@@ -37,7 +37,7 @@ use super::helpers::match_function_def;
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeFunctionArgument {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingTypeFunctionArgument {
|
||||
@@ -69,7 +69,7 @@ impl Violation for MissingTypeFunctionArgument {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeArgs {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingTypeArgs {
|
||||
@@ -101,7 +101,7 @@ impl Violation for MissingTypeArgs {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeKwargs {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingTypeKwargs {
|
||||
@@ -138,7 +138,7 @@ impl Violation for MissingTypeKwargs {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeSelf {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingTypeSelf {
|
||||
@@ -177,7 +177,7 @@ impl Violation for MissingTypeSelf {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingTypeCls {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingTypeCls {
|
||||
@@ -209,7 +209,7 @@ impl Violation for MissingTypeCls {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeUndocumentedPublicFunction {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||
@@ -241,7 +241,7 @@ impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypePrivateFunction {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypePrivateFunction {
|
||||
@@ -286,7 +286,7 @@ impl Violation for MissingReturnTypePrivateFunction {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeSpecialMethod {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for MissingReturnTypeSpecialMethod {
|
||||
@@ -326,7 +326,7 @@ impl AlwaysAutofixableViolation for MissingReturnTypeSpecialMethod {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeStaticMethod {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeStaticMethod {
|
||||
@@ -362,7 +362,7 @@ impl Violation for MissingReturnTypeStaticMethod {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeClassMethod {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeClassMethod {
|
||||
@@ -403,7 +403,7 @@ impl Violation for MissingReturnTypeClassMethod {
|
||||
/// - [Mypy: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
|
||||
#[violation]
|
||||
pub struct AnyType {
|
||||
pub name: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for AnyType {
|
||||
@@ -416,16 +416,11 @@ impl Violation for AnyType {
|
||||
|
||||
fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
let mut visitor = ReturnStatementVisitor::default();
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
visitor.visit_body(body);
|
||||
for expr in visitor.returns.into_iter().flatten() {
|
||||
if !matches!(
|
||||
expr.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
..
|
||||
}
|
||||
ExprKind::Constant(ref constant) if constant.value.is_none()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -451,7 +446,7 @@ fn check_dynamically_typed<F>(
|
||||
}
|
||||
|
||||
/// Generate flake8-annotation checks for a given `Definition`.
|
||||
pub fn definition(
|
||||
pub(crate) fn definition(
|
||||
checker: &Checker,
|
||||
definition: &Definition,
|
||||
visibility: Visibility,
|
||||
@@ -459,285 +454,285 @@ pub fn definition(
|
||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||
// We could adhere more closely to `flake8-annotations` by defining public
|
||||
// vs. secret vs. protected.
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = &definition.kind
|
||||
let Definition::Member(Member {
|
||||
kind,
|
||||
stmt,
|
||||
..
|
||||
}) = definition else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let is_method = match kind {
|
||||
MemberKind::Method => true,
|
||||
MemberKind::Function | MemberKind::NestedFunction => false,
|
||||
_ => return vec![],
|
||||
};
|
||||
|
||||
let (name, args, returns, body) = match_function_def(stmt);
|
||||
// Keep track of whether we've seen any typed arguments or return values.
|
||||
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||
let mut has_typed_return = false; // Return value has been typed?
|
||||
let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument?
|
||||
|
||||
// Temporary storage for diagnostics; we emit them at the end
|
||||
// unless configured to suppress ANN* for declarations that are fully untyped.
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
// ANN001, ANN401
|
||||
for arg in args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(
|
||||
is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
|
||||
),
|
||||
)
|
||||
{
|
||||
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
|
||||
let (name, args, returns, body) = match_function_def(stmt);
|
||||
// Keep track of whether we've seen any typed arguments or return values.
|
||||
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||
let mut has_typed_return = false; // Return value has been typed?
|
||||
let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument?
|
||||
|
||||
// Temporary storage for diagnostics; we emit them at the end
|
||||
// unless configured to suppress ANN* for declarations that are fully untyped.
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
// ANN001, ANN401
|
||||
for arg in args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(
|
||||
is_method
|
||||
&& !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
|
||||
),
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
annotation,
|
||||
|| arg.node.arg.to_string(),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingTypeFunctionArgument)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeFunctionArgument {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN002, ANN401
|
||||
if let Some(arg) = &args.vararg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
expr,
|
||||
|| format!("*{name}"),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeArgs {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN003, ANN401
|
||||
if let Some(arg) = &args.kwarg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
expr,
|
||||
|| format!("**{name}"),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeKwargs {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
|
||||
if arg.node.annotation.is_none() {
|
||||
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeCls {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeSelf {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
has_typed_self_or_cls = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
has_typed_return = true;
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
annotation,
|
||||
|| arg.node.arg.to_string(),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
} else if !(
|
||||
// Allow omission of return annotation if the function only returns `None`
|
||||
// (explicitly or implicitly).
|
||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeClassMethod)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
} else if is_method
|
||||
&& visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt))
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeStaticMethod)
|
||||
.enabled(Rule::MissingTypeFunctionArgument)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
MissingTypeFunctionArgument {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||
// least one argument is typed.
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||
{
|
||||
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg)
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN002, ANN401
|
||||
if let Some(arg) = &args.vararg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeArgs {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN003, ANN401
|
||||
if let Some(arg) = &args.kwarg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
expr,
|
||||
|| format!("**{name}"),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeKwargs {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
|
||||
if arg.node.annotation.is_none() {
|
||||
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeCls {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(checker.locator, stmt, "None")
|
||||
});
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeSelf {
|
||||
name: arg.node.arg.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if is_method && visibility::is_magic(name) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||
{
|
||||
} else {
|
||||
has_typed_self_or_cls = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
has_typed_return = true;
|
||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
||||
}
|
||||
} else if !(
|
||||
// Allow omission of return annotation if the function only returns `None`
|
||||
// (explicitly or implicitly).
|
||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeClassMethod)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt))
|
||||
{
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeStaticMethod)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||
// least one argument is typed.
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||
{
|
||||
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
);
|
||||
let return_type = SIMPLE_MAGIC_RETURN_TYPES.get(name);
|
||||
if let Some(return_type) = return_type {
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(checker.locator, stmt, return_type)
|
||||
});
|
||||
}
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.try_set_fix_from_edit(|| {
|
||||
fixes::add_return_annotation(checker.locator, stmt, "None")
|
||||
});
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
} else {
|
||||
match visibility {
|
||||
Visibility::Public => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if is_method && visibility::is_magic(name) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
);
|
||||
let return_type = SIMPLE_MAGIC_RETURN_TYPES.get(name);
|
||||
if let Some(return_type) = return_type {
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.try_set_fix_from_edit(|| {
|
||||
fixes::add_return_annotation(checker.locator, stmt, return_type)
|
||||
});
|
||||
}
|
||||
Visibility::Private => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypePrivateFunction)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
} else {
|
||||
match visibility {
|
||||
Visibility::Public => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
Visibility::Private => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::MissingReturnTypePrivateFunction)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If settings say so, don't report any of the
|
||||
// diagnostics gathered here if there were no type annotations at all.
|
||||
if checker.settings.flake8_annotations.ignore_fully_untyped
|
||||
&& !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return)
|
||||
{
|
||||
vec![]
|
||||
} else {
|
||||
diagnostics
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// If settings say so, don't report any of the
|
||||
// diagnostics gathered here if there were no type annotations at all.
|
||||
if checker.settings.flake8_annotations.ignore_fully_untyped
|
||||
&& !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return)
|
||||
{
|
||||
vec![]
|
||||
} else {
|
||||
diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
//! Settings for the `flake-annotations` plugin.
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8AnnotationsOptions"
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
default = "false",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -8,23 +8,23 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(^|_)(?i)(pas+wo?r?d|pass(phrase)?|pwd|token|secrete?)($|_)").unwrap()
|
||||
});
|
||||
|
||||
pub fn string_literal(expr: &Expr) -> Option<&str> {
|
||||
pub(crate) fn string_literal(expr: &Expr) -> Option<&str> {
|
||||
match &expr.node {
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => Some(string),
|
||||
}) => Some(string),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_password_name(string: &str) -> bool {
|
||||
pub(crate) fn matches_password_name(string: &str) -> bool {
|
||||
PASSWORD_CANDIDATE_REGEX.is_match(string)
|
||||
}
|
||||
|
||||
pub fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
|
||||
pub(crate) fn is_untyped_exception(type_: Option<&Expr>, checker: &Checker) -> bool {
|
||||
type_.map_or(true, |type_| {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
if let ExprKind::Tuple(ast::ExprTuple { elts, .. }) = &type_.node {
|
||||
elts.iter().any(|type_| {
|
||||
checker
|
||||
.ctx
|
||||
|
||||
@@ -36,6 +36,6 @@ impl Violation for Assert {
|
||||
}
|
||||
|
||||
/// S101
|
||||
pub fn assert_used(stmt: &Stmt) -> Diagnostic {
|
||||
pub(crate) fn assert_used(stmt: &Stmt) -> Diagnostic {
|
||||
Diagnostic::new(Assert, TextRange::at(stmt.start(), "assert".text_len()))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use num_traits::ToPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, Operator};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword, Operator};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -12,7 +12,7 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct BadFilePermissions {
|
||||
pub mask: u16,
|
||||
mask: u16,
|
||||
}
|
||||
|
||||
impl Violation for BadFilePermissions {
|
||||
@@ -70,14 +70,14 @@ static PYSTAT_MAPPING: Lazy<FxHashMap<&'static str, u16>> = Lazy::new(|| {
|
||||
|
||||
fn get_int_value(expr: &Expr) -> Option<u16> {
|
||||
match &expr.node {
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
} => value.to_u16(),
|
||||
ExprKind::Attribute { .. } => {
|
||||
}) => value.to_u16(),
|
||||
ExprKind::Attribute(_) => {
|
||||
compose_call_path(expr).and_then(|path| PYSTAT_MAPPING.get(path.as_str()).copied())
|
||||
}
|
||||
ExprKind::BinOp { left, op, right } => {
|
||||
ExprKind::BinOp(ast::ExprBinOp { left, op, right }) => {
|
||||
if let (Some(left_value), Some(right_value)) =
|
||||
(get_int_value(left), get_int_value(right))
|
||||
{
|
||||
@@ -96,7 +96,7 @@ fn get_int_value(expr: &Expr) -> Option<u16> {
|
||||
}
|
||||
|
||||
/// S103
|
||||
pub fn bad_file_permissions(
|
||||
pub(crate) fn bad_file_permissions(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
use rustpython_parser::ast::{self, Expr, ExprKind};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -14,8 +14,8 @@ impl Violation for ExecBuiltin {
|
||||
}
|
||||
|
||||
/// S102
|
||||
pub fn exec_used(expr: &Expr, func: &Expr) -> Option<Diagnostic> {
|
||||
let ExprKind::Name { id, .. } = &func.node else {
|
||||
pub(crate) fn exec_used(expr: &Expr, func: &Expr) -> Option<Diagnostic> {
|
||||
let ExprKind::Name(ast::ExprName { id, .. }) = &func.node else {
|
||||
return None;
|
||||
};
|
||||
if id != "exec" {
|
||||
|
||||
@@ -13,7 +13,7 @@ impl Violation for HardcodedBindAllInterfaces {
|
||||
}
|
||||
|
||||
/// S104
|
||||
pub fn hardcoded_bind_all_interfaces(value: &str, range: TextRange) -> Option<Diagnostic> {
|
||||
pub(crate) fn hardcoded_bind_all_interfaces(value: &str, range: TextRange) -> Option<Diagnostic> {
|
||||
if value == "0.0.0.0" {
|
||||
Some(Diagnostic::new(HardcodedBindAllInterfaces, range))
|
||||
} else {
|
||||
|
||||
@@ -7,33 +7,36 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordDefault {
|
||||
pub string: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for HardcodedPasswordDefault {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let HardcodedPasswordDefault { string } = self;
|
||||
format!("Possible hardcoded password: \"{}\"", string.escape_debug())
|
||||
let HardcodedPasswordDefault { name } = self;
|
||||
format!(
|
||||
"Possible hardcoded password assigned to function default: \"{}\"",
|
||||
name.escape_debug()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
|
||||
let string = string_literal(default).filter(|string| !string.is_empty())?;
|
||||
string_literal(default).filter(|string| !string.is_empty())?;
|
||||
let kwarg_name = &arg.node.arg;
|
||||
if !matches_password_name(kwarg_name) {
|
||||
return None;
|
||||
}
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordDefault {
|
||||
string: string.to_string(),
|
||||
name: kwarg_name.to_string(),
|
||||
},
|
||||
default.range(),
|
||||
))
|
||||
}
|
||||
|
||||
/// S107
|
||||
pub fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnostic> {
|
||||
pub(crate) fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
let defaults_start =
|
||||
|
||||
@@ -7,30 +7,33 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordFuncArg {
|
||||
pub string: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for HardcodedPasswordFuncArg {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let HardcodedPasswordFuncArg { string } = self;
|
||||
format!("Possible hardcoded password: \"{}\"", string.escape_debug())
|
||||
let HardcodedPasswordFuncArg { name } = self;
|
||||
format!(
|
||||
"Possible hardcoded password assigned to argument: \"{}\"",
|
||||
name.escape_debug()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// S106
|
||||
pub fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnostic> {
|
||||
pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnostic> {
|
||||
keywords
|
||||
.iter()
|
||||
.filter_map(|keyword| {
|
||||
let string = string_literal(&keyword.node.value).filter(|string| !string.is_empty())?;
|
||||
string_literal(&keyword.node.value).filter(|string| !string.is_empty())?;
|
||||
let arg = keyword.node.arg.as_ref()?;
|
||||
if !matches_password_name(arg) {
|
||||
return None;
|
||||
}
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordFuncArg {
|
||||
string: string.to_string(),
|
||||
name: arg.to_string(),
|
||||
},
|
||||
keyword.range(),
|
||||
))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -7,49 +7,59 @@ use super::super::helpers::{matches_password_name, string_literal};
|
||||
|
||||
#[violation]
|
||||
pub struct HardcodedPasswordString {
|
||||
pub string: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for HardcodedPasswordString {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let HardcodedPasswordString { string } = self;
|
||||
format!("Possible hardcoded password: \"{}\"", string.escape_debug())
|
||||
let HardcodedPasswordString { name } = self;
|
||||
format!(
|
||||
"Possible hardcoded password assigned to: \"{}\"",
|
||||
name.escape_debug()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_password_target(target: &Expr) -> bool {
|
||||
fn password_target(target: &Expr) -> Option<&str> {
|
||||
let target_name = match &target.node {
|
||||
// variable = "s3cr3t"
|
||||
ExprKind::Name { id, .. } => id,
|
||||
ExprKind::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
// d["password"] = "s3cr3t"
|
||||
ExprKind::Subscript { slice, .. } => match &slice.node {
|
||||
ExprKind::Constant {
|
||||
ExprKind::Subscript(ast::ExprSubscript { slice, .. }) => match &slice.node {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => string,
|
||||
_ => return false,
|
||||
}) => string,
|
||||
_ => return None,
|
||||
},
|
||||
// obj.password = "s3cr3t"
|
||||
ExprKind::Attribute { attr, .. } => attr,
|
||||
_ => return false,
|
||||
ExprKind::Attribute(ast::ExprAttribute { attr, .. }) => attr,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
matches_password_name(target_name)
|
||||
if matches_password_name(target_name) {
|
||||
Some(target_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// S105
|
||||
pub fn compare_to_hardcoded_password_string(left: &Expr, comparators: &[Expr]) -> Vec<Diagnostic> {
|
||||
pub(crate) fn compare_to_hardcoded_password_string(
|
||||
left: &Expr,
|
||||
comparators: &[Expr],
|
||||
) -> Vec<Diagnostic> {
|
||||
comparators
|
||||
.iter()
|
||||
.filter_map(|comp| {
|
||||
let string = string_literal(comp).filter(|string| !string.is_empty())?;
|
||||
if !is_password_target(left) {
|
||||
string_literal(comp).filter(|string| !string.is_empty())?;
|
||||
let Some(name) = password_target(left) else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordString {
|
||||
string: string.to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
comp.range(),
|
||||
))
|
||||
@@ -58,13 +68,19 @@ pub fn compare_to_hardcoded_password_string(left: &Expr, comparators: &[Expr]) -
|
||||
}
|
||||
|
||||
/// S105
|
||||
pub fn assign_hardcoded_password_string(value: &Expr, targets: &[Expr]) -> Option<Diagnostic> {
|
||||
if let Some(string) = string_literal(value).filter(|string| !string.is_empty()) {
|
||||
pub(crate) fn assign_hardcoded_password_string(
|
||||
value: &Expr,
|
||||
targets: &[Expr],
|
||||
) -> Option<Diagnostic> {
|
||||
if string_literal(value)
|
||||
.filter(|string| !string.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
for target in targets {
|
||||
if is_password_target(target) {
|
||||
if let Some(name) = password_target(target) {
|
||||
return Some(Diagnostic::new(
|
||||
HardcodedPasswordString {
|
||||
string: string.to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
value.range(),
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Operator};
|
||||
use rustpython_parser::ast::{self, Expr, ExprKind, Operator};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -56,18 +56,18 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||
match &expr.node {
|
||||
// "select * from table where val = " + "str" + ...
|
||||
// "select * from table where val = %s" % ...
|
||||
ExprKind::BinOp {
|
||||
ExprKind::BinOp(ast::ExprBinOp {
|
||||
op: Operator::Add | Operator::Mod,
|
||||
..
|
||||
} => {
|
||||
let Some(parent) = checker.ctx.current_expr_parent() else {
|
||||
}) => {
|
||||
let Some(parent) = checker.ctx.expr_parent() else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(unparse_expr(expr, checker.stylist));
|
||||
}
|
||||
return None;
|
||||
};
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
let ExprKind::BinOp { .. } = &parent.node else {
|
||||
let ExprKind::BinOp(_ )= &parent.node else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(unparse_expr(expr, checker.stylist));
|
||||
}
|
||||
@@ -75,8 +75,8 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||
};
|
||||
None
|
||||
}
|
||||
ExprKind::Call { func, .. } => {
|
||||
let ExprKind::Attribute{ attr, value, .. } = &func.node else {
|
||||
ExprKind::Call(ast::ExprCall { func, .. }) => {
|
||||
let ExprKind::Attribute(ast::ExprAttribute { attr, value, .. }) = &func.node else {
|
||||
return None;
|
||||
};
|
||||
// "select * from table where val = {}".format(...)
|
||||
@@ -86,13 +86,13 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||
None
|
||||
}
|
||||
// f"select * from table where val = {val}"
|
||||
ExprKind::JoinedStr { .. } => Some(unparse_expr(expr, checker.stylist)),
|
||||
ExprKind::JoinedStr(_) => Some(unparse_expr(expr, checker.stylist)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// S608
|
||||
pub fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
|
||||
pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
|
||||
match unparse_string_format_expression(checker, expr) {
|
||||
Some(string) if matches_sql_statement(&string) => {
|
||||
checker
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
#[violation]
|
||||
pub struct HardcodedTempFile {
|
||||
pub string: String,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl Violation for HardcodedTempFile {
|
||||
@@ -20,7 +20,7 @@ impl Violation for HardcodedTempFile {
|
||||
}
|
||||
|
||||
/// S108
|
||||
pub fn hardcoded_tmp_directory(
|
||||
pub(crate) fn hardcoded_tmp_directory(
|
||||
expr: &Expr,
|
||||
value: &str,
|
||||
prefixes: &[String],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -10,7 +10,7 @@ use super::super::helpers::string_literal;
|
||||
|
||||
#[violation]
|
||||
pub struct HashlibInsecureHashFunction {
|
||||
pub string: String,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl Violation for HashlibInsecureHashFunction {
|
||||
@@ -27,10 +27,10 @@ fn is_used_for_security(call_args: &SimpleCallArgs) -> bool {
|
||||
match call_args.keyword_argument("usedforsecurity") {
|
||||
Some(expr) => !matches!(
|
||||
&expr.node,
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(false),
|
||||
..
|
||||
}
|
||||
})
|
||||
),
|
||||
_ => true,
|
||||
}
|
||||
@@ -42,7 +42,7 @@ enum HashlibCall {
|
||||
}
|
||||
|
||||
/// S324
|
||||
pub fn hashlib_insecure_hash_functions(
|
||||
pub(crate) fn hashlib_insecure_hash_functions(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -8,7 +8,7 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct Jinja2AutoescapeFalse {
|
||||
pub value: bool,
|
||||
value: bool,
|
||||
}
|
||||
|
||||
impl Violation for Jinja2AutoescapeFalse {
|
||||
@@ -30,7 +30,7 @@ impl Violation for Jinja2AutoescapeFalse {
|
||||
}
|
||||
|
||||
/// S701
|
||||
pub fn jinja2_autoescape_false(
|
||||
pub(crate) fn jinja2_autoescape_false(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
@@ -47,13 +47,13 @@ pub fn jinja2_autoescape_false(
|
||||
|
||||
if let Some(autoescape_arg) = call_args.keyword_argument("autoescape") {
|
||||
match &autoescape_arg.node {
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
..
|
||||
} => (),
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id.as_str() != "select_autoescape" {
|
||||
}) => (),
|
||||
ExprKind::Call(ast::ExprCall { func, .. }) => {
|
||||
if let ExprKind::Name(ast::ExprName { id, .. }) = &func.node {
|
||||
if id != "select_autoescape" {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
autoescape_arg.range(),
|
||||
|
||||
@@ -17,7 +17,7 @@ impl Violation for LoggingConfigInsecureListen {
|
||||
}
|
||||
|
||||
/// S612
|
||||
pub fn logging_config_insecure_listen(
|
||||
pub(crate) fn logging_config_insecure_listen(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
pub use assert_used::{assert_used, Assert};
|
||||
pub use bad_file_permissions::{bad_file_permissions, BadFilePermissions};
|
||||
pub use exec_used::{exec_used, ExecBuiltin};
|
||||
pub use hardcoded_bind_all_interfaces::{
|
||||
pub(crate) use assert_used::{assert_used, Assert};
|
||||
pub(crate) use bad_file_permissions::{bad_file_permissions, BadFilePermissions};
|
||||
pub(crate) use exec_used::{exec_used, ExecBuiltin};
|
||||
pub(crate) use hardcoded_bind_all_interfaces::{
|
||||
hardcoded_bind_all_interfaces, HardcodedBindAllInterfaces,
|
||||
};
|
||||
pub use hardcoded_password_default::{hardcoded_password_default, HardcodedPasswordDefault};
|
||||
pub use hardcoded_password_func_arg::{hardcoded_password_func_arg, HardcodedPasswordFuncArg};
|
||||
pub use hardcoded_password_string::{
|
||||
pub(crate) use hardcoded_password_default::{hardcoded_password_default, HardcodedPasswordDefault};
|
||||
pub(crate) use hardcoded_password_func_arg::{
|
||||
hardcoded_password_func_arg, HardcodedPasswordFuncArg,
|
||||
};
|
||||
pub(crate) use hardcoded_password_string::{
|
||||
assign_hardcoded_password_string, compare_to_hardcoded_password_string, HardcodedPasswordString,
|
||||
};
|
||||
pub use hardcoded_sql_expression::{hardcoded_sql_expression, HardcodedSQLExpression};
|
||||
pub use hardcoded_tmp_directory::{hardcoded_tmp_directory, HardcodedTempFile};
|
||||
pub use hashlib_insecure_hash_functions::{
|
||||
pub(crate) use hardcoded_sql_expression::{hardcoded_sql_expression, HardcodedSQLExpression};
|
||||
pub(crate) use hardcoded_tmp_directory::{hardcoded_tmp_directory, HardcodedTempFile};
|
||||
pub(crate) use hashlib_insecure_hash_functions::{
|
||||
hashlib_insecure_hash_functions, HashlibInsecureHashFunction,
|
||||
};
|
||||
pub use jinja2_autoescape_false::{jinja2_autoescape_false, Jinja2AutoescapeFalse};
|
||||
pub use logging_config_insecure_listen::{
|
||||
pub(crate) use jinja2_autoescape_false::{jinja2_autoescape_false, Jinja2AutoescapeFalse};
|
||||
pub(crate) use logging_config_insecure_listen::{
|
||||
logging_config_insecure_listen, LoggingConfigInsecureListen,
|
||||
};
|
||||
pub use request_with_no_cert_validation::{
|
||||
pub(crate) use request_with_no_cert_validation::{
|
||||
request_with_no_cert_validation, RequestWithNoCertValidation,
|
||||
};
|
||||
pub use request_without_timeout::{request_without_timeout, RequestWithoutTimeout};
|
||||
pub use shell_injection::{
|
||||
pub(crate) use request_without_timeout::{request_without_timeout, RequestWithoutTimeout};
|
||||
pub(crate) use shell_injection::{
|
||||
shell_injection, CallWithShellEqualsTrue, StartProcessWithAShell, StartProcessWithNoShell,
|
||||
StartProcessWithPartialPath, SubprocessPopenWithShellEqualsTrue,
|
||||
SubprocessWithoutShellEqualsTrue,
|
||||
};
|
||||
pub use snmp_insecure_version::{snmp_insecure_version, SnmpInsecureVersion};
|
||||
pub use snmp_weak_cryptography::{snmp_weak_cryptography, SnmpWeakCryptography};
|
||||
pub use suspicious_function_call::{
|
||||
pub(crate) use snmp_insecure_version::{snmp_insecure_version, SnmpInsecureVersion};
|
||||
pub(crate) use snmp_weak_cryptography::{snmp_weak_cryptography, SnmpWeakCryptography};
|
||||
pub(crate) use suspicious_function_call::{
|
||||
suspicious_function_call, SuspiciousEvalUsage, SuspiciousFTPLibUsage,
|
||||
SuspiciousInsecureCipherModeUsage, SuspiciousInsecureCipherUsage, SuspiciousInsecureHashUsage,
|
||||
SuspiciousMarkSafeUsage, SuspiciousMarshalUsage, SuspiciousMktempUsage,
|
||||
@@ -39,9 +41,9 @@ pub use suspicious_function_call::{
|
||||
SuspiciousXMLExpatReaderUsage, SuspiciousXMLMiniDOMUsage, SuspiciousXMLPullDOMUsage,
|
||||
SuspiciousXMLSaxUsage,
|
||||
};
|
||||
pub use try_except_continue::{try_except_continue, TryExceptContinue};
|
||||
pub use try_except_pass::{try_except_pass, TryExceptPass};
|
||||
pub use unsafe_yaml_load::{unsafe_yaml_load, UnsafeYAMLLoad};
|
||||
pub(crate) use try_except_continue::{try_except_continue, TryExceptContinue};
|
||||
pub(crate) use try_except_pass::{try_except_pass, TryExceptPass};
|
||||
pub(crate) use unsafe_yaml_load::{unsafe_yaml_load, UnsafeYAMLLoad};
|
||||
|
||||
mod assert_used;
|
||||
mod bad_file_permissions;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -8,7 +8,7 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct RequestWithNoCertValidation {
|
||||
pub string: String,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl Violation for RequestWithNoCertValidation {
|
||||
@@ -37,7 +37,7 @@ const HTTPX_METHODS: [&str; 11] = [
|
||||
];
|
||||
|
||||
/// S501
|
||||
pub fn request_with_no_cert_validation(
|
||||
pub(crate) fn request_with_no_cert_validation(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
@@ -56,10 +56,10 @@ pub fn request_with_no_cert_validation(
|
||||
}) {
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(verify_arg) = call_args.keyword_argument("verify") {
|
||||
if let ExprKind::Constant {
|
||||
if let ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(false),
|
||||
..
|
||||
} = &verify_arg.node
|
||||
}) = &verify_arg.node
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithNoCertValidation {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -27,7 +27,7 @@ impl Violation for RequestWithoutTimeout {
|
||||
const HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
|
||||
|
||||
/// S113
|
||||
pub fn request_without_timeout(
|
||||
pub(crate) fn request_without_timeout(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
@@ -45,10 +45,10 @@ pub fn request_without_timeout(
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(timeout_arg) = call_args.keyword_argument("timeout") {
|
||||
if let Some(timeout) = match &timeout_arg.node {
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: value @ Constant::None,
|
||||
..
|
||||
} => Some(unparse_constant(value, checker.stylist)),
|
||||
}) => Some(unparse_constant(value, checker.stylist)),
|
||||
_ => None,
|
||||
} {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Keyword};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -159,17 +159,17 @@ fn find_shell_keyword<'a>(ctx: &Context, keywords: &'a [Keyword]) -> Option<Shel
|
||||
fn shell_call_seems_safe(arg: &Expr) -> bool {
|
||||
matches!(
|
||||
arg.node,
|
||||
ExprKind::Constant {
|
||||
ExprKind::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the [`Expr`] as a string literal, if it's a string or a list of strings.
|
||||
fn try_string_literal(expr: &Expr) -> Option<&str> {
|
||||
match &expr.node {
|
||||
ExprKind::List { elts, .. } => {
|
||||
ExprKind::List(ast::ExprList { elts, .. }) => {
|
||||
if elts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -181,7 +181,12 @@ fn try_string_literal(expr: &Expr) -> Option<&str> {
|
||||
}
|
||||
|
||||
/// S602, S603, S604, S605, S606, S607
|
||||
pub fn shell_injection(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
|
||||
pub(crate) fn shell_injection(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let call_kind = get_call_kind(func, &checker.ctx);
|
||||
|
||||
if matches!(call_kind, Some(CallKind::Subprocess)) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user