Compare commits
44 Commits
charlie/qu
...
deps/parse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35cc48a64c | ||
|
|
0d4f1d86ad | ||
|
|
834910947e | ||
|
|
e34cfeb475 | ||
|
|
bfaa1f9530 | ||
|
|
52aa2fc875 | ||
|
|
e574a6a769 | ||
|
|
b9346a4fd6 | ||
|
|
8001a2f121 | ||
|
|
7dd30f0270 | ||
|
|
21063544f7 | ||
|
|
fb336898a5 | ||
|
|
f5f8eb31ed | ||
|
|
be6c744856 | ||
|
|
94998aedef | ||
|
|
1c0376a72d | ||
|
|
de2a13fcd7 | ||
|
|
cfec636046 | ||
|
|
ae431df146 | ||
|
|
2cd117ba81 | ||
|
|
a956226d95 | ||
|
|
1dd52ad139 | ||
|
|
d692ed0896 | ||
|
|
01b05fe247 | ||
|
|
59dfd0e793 | ||
|
|
c7ff743d30 | ||
|
|
b01a4d8446 | ||
|
|
f012ed2d77 | ||
|
|
06b5c6c06f | ||
|
|
4782675bf9 | ||
|
|
f2e995f78d | ||
|
|
6824b67f44 | ||
|
|
8ccd697020 | ||
|
|
2de6f30929 | ||
|
|
df2efe81c8 | ||
|
|
fa4855e6fe | ||
|
|
3cda89ecaf | ||
|
|
e1c119fde3 | ||
|
|
daa4b72d5f | ||
|
|
f029f8b784 | ||
|
|
bf248ede93 | ||
|
|
086f8a3c12 | ||
|
|
3dc73395ea | ||
|
|
7c32e98d10 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,7 +10,7 @@ schemastore
|
||||
# `maturin develop` and ecosystem_all_check.sh
|
||||
.venv*
|
||||
# Formatter debugging (crates/ruff_python_formatter/README.md)
|
||||
scratch.py
|
||||
scratch.*
|
||||
# Created by `perf` (CONTRIBUTING.md)
|
||||
perf.data
|
||||
perf.data.old
|
||||
|
||||
@@ -110,27 +110,35 @@ The vast majority of the code, including all lint rules, lives in the `ruff` cra
|
||||
At time of writing, the repository includes the following crates:
|
||||
|
||||
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
|
||||
If you're working on a rule, this is the crate for you.
|
||||
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
|
||||
- `crates/ruff_cache`: library crate for caching lint results.
|
||||
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
|
||||
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
|
||||
`cargo dev generate-all`).
|
||||
- `crates/ruff_diagnostics`: library crate for the lint diagnostics APIs.
|
||||
- `crates/ruff_formatter`: library crate for generic code formatting logic based on an intermediate
|
||||
representation.
|
||||
`cargo dev generate-all`), see the [`cargo dev`](#cargo-dev) section below.
|
||||
- `crates/ruff_diagnostics`: library crate for the rule-independent abstractions in the lint
|
||||
diagnostics APIs.
|
||||
- `crates/ruff_formatter`: library crate for language agnostic code formatting logic based on an
|
||||
intermediate representation. The backend for `ruff_python_formatter`.
|
||||
- `crates/ruff_index`: library crate inspired by `rustc_index`.
|
||||
- `crates/ruff_macros`: library crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_formatter`: library crate containing Python-specific code formatting logic.
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities. Note
|
||||
that the AST schema itself is defined in the
|
||||
[rustpython-ast](https://github.com/astral-sh/RustPython-Parser) crate.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||
line length.
|
||||
- `crates/ruff_python_semantic`: library crate containing Python-specific semantic analysis logic,
|
||||
including Ruff's semantic model.
|
||||
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data.
|
||||
including Ruff's semantic model. Used to resolve queries like "What import does this variable
|
||||
refer to?"
|
||||
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data, e.g.
|
||||
the names of all built-in exceptions and which standard library types are immutable.
|
||||
- `crates/ruff_python_whitespace`: library crate containing Python-specific whitespace analysis
|
||||
logic.
|
||||
logic (indentation and newlines).
|
||||
- `crates/ruff_rustpython`: library crate containing `RustPython`-specific utilities.
|
||||
- `crates/ruff_testing_macros`: library crate containing macros used for testing Ruff.
|
||||
- `crates/ruff_textwrap`: library crate to indent and dedent Python source code.
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module.
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module. Powers the
|
||||
[Ruff Playground](https://play.ruff.rs/).
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
@@ -411,6 +419,13 @@ Summary
|
||||
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
|
||||
```
|
||||
|
||||
To benchmark a subset of rules, e.g. `LineTooLong` and `DocLineTooLong`:
|
||||
|
||||
```shell
|
||||
cargo build --release && hyperfine --warmup 10 \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select W505,E501"
|
||||
```
|
||||
|
||||
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
|
||||
above. All reported benchmarks were computed using the versions specified by
|
||||
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
|
||||
|
||||
277
Cargo.lock
generated
277
Cargo.lock
generated
@@ -14,15 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
@@ -120,9 +111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -135,9 +126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.11"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151"
|
||||
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
@@ -279,9 +270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.11"
|
||||
version = "4.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d"
|
||||
checksum = "98330784c494e49850cb23b8e2afcca13587d2500b2e3f1f78ae20248059c9be"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -290,9 +281,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.11"
|
||||
version = "4.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b"
|
||||
checksum = "e182eb5f2562a67dda37e2c57af64d720a9e010c5e860ed87c056586aeafa52e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -343,14 +334,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.2"
|
||||
version = "4.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -534,21 +525,11 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -556,27 +537,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb"
|
||||
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -646,9 +627,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
@@ -677,9 +658,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
@@ -804,11 +785,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.20",
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
@@ -963,6 +944,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
@@ -985,9 +972,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.30.0"
|
||||
version = "1.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3"
|
||||
checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1033,12 +1020,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix 0.38.3",
|
||||
"rustix 0.38.4",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -1053,9 +1040,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
@@ -1380,20 +1367,11 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
@@ -1520,7 +1498,7 @@ dependencies = [
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1579,9 +1557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.3.3"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
|
||||
checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
@@ -1613,13 +1591,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
@@ -1649,9 +1625,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1671,12 +1647,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b909fe9bf2abb1e3d6a97c9189a37c8105c61d03dca9ce6aace023e7d682bd"
|
||||
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.0",
|
||||
"nextest-workspace-hack",
|
||||
"quick-xml",
|
||||
"thiserror",
|
||||
@@ -1685,18 +1661,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.26.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
|
||||
checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.29"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1769,11 +1745,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.2",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
@@ -1781,20 +1757,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.2",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "result-like"
|
||||
@@ -1987,6 +1963,7 @@ dependencies = [
|
||||
"clap",
|
||||
"ignore",
|
||||
"indicatif",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
@@ -2004,11 +1981,13 @@ dependencies = [
|
||||
"rustpython-format",
|
||||
"rustpython-parser",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2052,7 +2031,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_textwrap",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2155,7 +2134,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2220,9 +2199,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.3"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4"
|
||||
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
@@ -2233,13 +2212,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.2"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f"
|
||||
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.101.1",
|
||||
"sct",
|
||||
]
|
||||
|
||||
@@ -2253,10 +2232,20 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
@@ -2267,7 +2256,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-format"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"itertools",
|
||||
@@ -2279,7 +2268,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
@@ -2291,7 +2280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
@@ -2314,7 +2303,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=c174bbf1f29527edd43d432326327f16f47ab9e0#c174bbf1f29527edd43d432326327f16f47ab9e0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=126652b684910c29a7bcc32293d4ca0f81454e34#126652b684910c29a7bcc32293d4ca0f81454e34"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"memchr",
|
||||
@@ -2323,15 +2312,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -2374,9 +2363,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
@@ -2390,15 +2379,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.166"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
|
||||
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2416,13 +2405,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.166"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
|
||||
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2438,9 +2427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.100"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
||||
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2458,9 +2447,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
|
||||
checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
@@ -2469,19 +2458,19 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time 0.3.22",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
|
||||
checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2507,9 +2496,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@@ -2564,9 +2553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.23"
|
||||
version = "2.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
|
||||
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2676,7 +2665,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2722,9 +2711,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
|
||||
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -2740,9 +2729,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
|
||||
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -2783,9 +2772,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -2804,9 +2793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.11"
|
||||
version = "0.19.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
@@ -2836,7 +2825,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2926,9 +2915,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -2970,7 +2959,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.100.1",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
@@ -2995,9 +2984,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@@ -3057,7 +3046,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3091,7 +3080,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.26",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3142,7 +3131,7 @@ version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
|
||||
dependencies = [
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.100.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3339,9 +3328,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.7"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
||||
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -54,12 +54,12 @@ libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f
|
||||
|
||||
# Please tag the RustPython version every time you update its revision here and in fuzz/Cargo.toml
|
||||
# Tagging the version ensures that older ruff versions continue to build from source even when we rebase our RustPython fork.
|
||||
# Current tag: v0.0.7
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
# Note: As of tag v0.0.8 we are cherry-picking commits instead of rebasing so the tag is not necessary
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "126652b684910c29a7bcc32293d4ca0f81454e34" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
@@ -177,6 +177,9 @@ def str_okay(value=str("foo")):
|
||||
def bool_okay(value=bool("bar")):
|
||||
pass
|
||||
|
||||
# Allow immutable bytes() value
|
||||
def bytes_okay(value=bytes(1)):
|
||||
pass
|
||||
|
||||
# Allow immutable int() value
|
||||
def int_okay(value=int("12")):
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import module
|
||||
from module import Class
|
||||
|
||||
|
||||
def f(var: Class) -> Class:
|
||||
x: Class
|
||||
|
||||
|
||||
def f(var: module.Class) -> module.Class:
|
||||
x: module.Class
|
||||
|
||||
|
||||
def f():
|
||||
print(Class)
|
||||
|
||||
|
||||
def f():
|
||||
print(module.Class)
|
||||
@@ -1,6 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
Class = ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from module import Class
|
||||
27
crates/ruff/resources/test/fixtures/pandas_vet/PD101.py
vendored
Normal file
27
crates/ruff/resources/test/fixtures/pandas_vet/PD101.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
data = pd.Series(range(1000))
|
||||
|
||||
# PD101
|
||||
data.nunique() <= 1
|
||||
data.nunique(dropna=True) <= 1
|
||||
data.nunique(dropna=False) <= 1
|
||||
data.nunique() == 1
|
||||
data.nunique(dropna=True) == 1
|
||||
data.nunique(dropna=False) == 1
|
||||
data.nunique() != 1
|
||||
data.nunique(dropna=True) != 1
|
||||
data.nunique(dropna=False) != 1
|
||||
data.nunique() > 1
|
||||
data.dropna().nunique() == 1
|
||||
data[data.notnull()].nunique() == 1
|
||||
|
||||
# No violation of this rule
|
||||
data.nunique() == 0 # empty
|
||||
data.nunique() >= 1 # not-empty
|
||||
data.nunique() < 1 # empty
|
||||
data.nunique() == 2 # not constant
|
||||
data.unique() == 1 # not `nunique`
|
||||
|
||||
{"hello": "world"}.nunique() == 1 # no pd.Series
|
||||
20
crates/ruff/resources/test/fixtures/pandas_vet/pandas_use_of_dot_read_table.py
vendored
Normal file
20
crates/ruff/resources/test/fixtures/pandas_vet/pandas_use_of_dot_read_table.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import pandas as pd
|
||||
|
||||
# Errors.
|
||||
df = pd.read_table("data.csv", sep=",")
|
||||
df = pd.read_table("data.csv", sep=",", header=0)
|
||||
filename = "data.csv"
|
||||
df = pd.read_table(filename, sep=",")
|
||||
df = pd.read_table(filename, sep=",", header=0)
|
||||
|
||||
# Non-errors.
|
||||
df = pd.read_csv("data.csv")
|
||||
df = pd.read_table("data.tsv")
|
||||
df = pd.read_table("data.tsv", sep="\t")
|
||||
df = pd.read_table("data.tsv", sep=",,")
|
||||
df = pd.read_table("data.tsv", sep=", ")
|
||||
df = pd.read_table("data.tsv", sep=" ,")
|
||||
df = pd.read_table("data.tsv", sep=" , ")
|
||||
not_pd.read_table("data.csv", sep=",")
|
||||
data = read_table("data.csv", sep=",")
|
||||
data = read_table
|
||||
@@ -1,71 +1,101 @@
|
||||
some_dict = {"a": 12, "b": 32, "c": 44}
|
||||
|
||||
for _, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
def f():
|
||||
for _, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
|
||||
for key, _ in some_dict.items(): # PERF102
|
||||
print(key)
|
||||
def f():
|
||||
for key, _ in some_dict.items(): # PERF102
|
||||
print(key)
|
||||
|
||||
|
||||
for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
print(weird_arg_name)
|
||||
def f():
|
||||
for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
print(weird_arg_name)
|
||||
|
||||
|
||||
for name, (_, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for name, (_, _) in some_dict.items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
for name, (value1, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for name, (value1, _) in some_dict.items(): # OK
|
||||
print(name, value1)
|
||||
|
||||
|
||||
for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
print(key1)
|
||||
|
||||
|
||||
for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
|
||||
for (_, key2), (value1, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, key2), (value1, _) in some_dict.items(): # OK
|
||||
print(key2, value1)
|
||||
|
||||
|
||||
for ((_, key2), (value1, _)) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for ((_, key2), (value1, _)) in some_dict.items(): # OK
|
||||
print(key2, value1)
|
||||
|
||||
|
||||
for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
print(key2)
|
||||
|
||||
|
||||
for (_, _, _, variants), (r_language, _, _, _) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, _, _, variants), (r_language, _, _, _) in some_dict.items(): # OK
|
||||
print(variants, r_language)
|
||||
|
||||
|
||||
for (_, _, (_, variants)), (_, (_, (r_language, _))) in some_dict.items(): # OK
|
||||
pass
|
||||
def f():
|
||||
for (_, _, (_, variants)), (_, (_, (r_language, _))) in some_dict.items(): # OK
|
||||
print(variants, r_language)
|
||||
|
||||
|
||||
for key, value in some_dict.items(): # OK
|
||||
print(key, value)
|
||||
def f():
|
||||
for key, value in some_dict.items(): # OK
|
||||
print(key, value)
|
||||
|
||||
|
||||
for _, value in some_dict.items(12): # OK
|
||||
print(value)
|
||||
def f():
|
||||
for _, value in some_dict.items(12): # OK
|
||||
print(value)
|
||||
|
||||
|
||||
for key in some_dict.keys(): # OK
|
||||
print(key)
|
||||
def f():
|
||||
for key in some_dict.keys(): # OK
|
||||
print(key)
|
||||
|
||||
|
||||
for value in some_dict.values(): # OK
|
||||
print(value)
|
||||
def f():
|
||||
for value in some_dict.values(): # OK
|
||||
print(value)
|
||||
|
||||
|
||||
for name, (_, _) in (some_function()).items(): # PERF102
|
||||
pass
|
||||
def f():
|
||||
for name, (_, _) in (some_function()).items(): # PERF102
|
||||
print(name)
|
||||
|
||||
for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
pass
|
||||
|
||||
def f():
|
||||
for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
def f():
|
||||
for name, unused_value in some_dict.items(): # PERF102
|
||||
print(name)
|
||||
|
||||
|
||||
def f():
|
||||
for unused_name, value in some_dict.items(): # PERF102
|
||||
print(value)
|
||||
|
||||
@@ -80,3 +80,8 @@ def multiple_assignment():
|
||||
global CONSTANT # [global-statement]
|
||||
CONSTANT = 1
|
||||
CONSTANT = 2
|
||||
|
||||
|
||||
def no_assignment():
|
||||
"""Shouldn't warn"""
|
||||
global CONSTANT
|
||||
|
||||
@@ -4,23 +4,9 @@ import typing
|
||||
# with complex annotations
|
||||
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||
|
||||
# with default values as list
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str), ("c", list[bool])],
|
||||
defaults=["foo", [True]],
|
||||
)
|
||||
|
||||
# with namespace
|
||||
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||
|
||||
# too many default values (OK)
|
||||
MyType = NamedTuple(
|
||||
"MyType",
|
||||
[("a", int), ("b", str)],
|
||||
defaults=[1, "bar", "baz"],
|
||||
)
|
||||
|
||||
# invalid identifiers (OK)
|
||||
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||
|
||||
@@ -29,3 +15,10 @@ MyType = typing.NamedTuple("MyType")
|
||||
|
||||
# empty fields
|
||||
MyType = typing.NamedTuple("MyType", [])
|
||||
|
||||
# keywords
|
||||
MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||
|
||||
# unfixable
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
|
||||
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -115,7 +115,6 @@ pub(crate) fn check_physical_lines(
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +604,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(PandasVet, "012") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotReadTable),
|
||||
(PandasVet, "013") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfDotStack),
|
||||
(PandasVet, "015") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasUseOfPdMerge),
|
||||
(PandasVet, "101") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
|
||||
(PandasVet, "901") => (RuleGroup::Unspecified, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
|
||||
// flake8-errmsg
|
||||
@@ -707,7 +708,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8TypeChecking, "003") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport),
|
||||
(Flake8TypeChecking, "004") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
|
||||
(Flake8TypeChecking, "005") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
|
||||
(Flake8TypeChecking, "200") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::UnquotedAnnotation),
|
||||
|
||||
// tryceratops
|
||||
(Tryceratops, "002") => (RuleGroup::Unspecified, rules::tryceratops::rules::RaiseVanillaClass),
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -30,12 +29,7 @@ impl Violation for Jinja2AutoescapeFalse {
|
||||
}
|
||||
|
||||
/// S701
|
||||
pub(crate) fn jinja2_autoescape_false(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -43,10 +37,13 @@ pub(crate) fn jinja2_autoescape_false(
|
||||
matches!(call_path.as_slice(), ["jinja2", "Environment"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
if let Some(autoescape_arg) = call_args.keyword_argument("autoescape") {
|
||||
match autoescape_arg {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "autoescape")
|
||||
}) {
|
||||
match &keyword.value {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
..
|
||||
@@ -56,14 +53,14 @@ pub(crate) fn jinja2_autoescape_false(
|
||||
if id != "select_autoescape" {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
autoescape_arg.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: true },
|
||||
autoescape_arg.range(),
|
||||
keyword.range(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -20,7 +19,6 @@ impl Violation for LoggingConfigInsecureListen {
|
||||
pub(crate) fn logging_config_insecure_listen(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if checker
|
||||
@@ -30,12 +28,17 @@ pub(crate) fn logging_config_insecure_listen(
|
||||
matches!(call_path.as_slice(), ["logging", "config", "listen"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
if call_args.keyword_argument("verify").is_none() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingConfigInsecureListen, func.range()));
|
||||
if keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "verify")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingConfigInsecureListen, func.range()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,34 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_const_false, SimpleCallArgs};
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for HTTPS requests that disable SSL certificate checks.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If SSL certificates are not verified, an attacker could perform a "man in
|
||||
/// the middle" attack by intercepting and modifying traffic between the client
|
||||
/// and server.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import requests
|
||||
///
|
||||
/// requests.get("https://www.example.com", verify=False)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import requests
|
||||
///
|
||||
/// requests.get("https://www.example.com") # By default, `verify=True`.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-295](https://cwe.mitre.org/data/definitions/295.html)
|
||||
#[violation]
|
||||
pub struct RequestWithNoCertValidation {
|
||||
string: String,
|
||||
@@ -25,7 +49,6 @@ impl Violation for RequestWithNoCertValidation {
|
||||
pub(crate) fn request_with_no_cert_validation(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if let Some(target) = checker
|
||||
@@ -40,14 +63,18 @@ pub(crate) fn request_with_no_cert_validation(
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(verify_arg) = call_args.keyword_argument("verify") {
|
||||
if is_const_false(verify_arg) {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "verify")
|
||||
}) {
|
||||
if is_const_false(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithNoCertValidation {
|
||||
string: target.to_string(),
|
||||
},
|
||||
verify_arg.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{is_const_none, SimpleCallArgs};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -49,12 +49,7 @@ impl Violation for RequestWithoutTimeout {
|
||||
}
|
||||
|
||||
/// S113
|
||||
pub(crate) fn request_without_timeout(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -68,12 +63,16 @@ pub(crate) fn request_without_timeout(
|
||||
)
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(timeout) = call_args.keyword_argument("timeout") {
|
||||
if is_const_none(timeout) {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "timeout")
|
||||
}) {
|
||||
if is_const_none(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { implicit: false },
|
||||
timeout.range(),
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -3,10 +3,34 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of SNMPv1 or SNMPv2.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The SNMPv1 and SNMPv2 protocols are considered insecure as they do
|
||||
/// not support encryption. Instead, prefer SNMPv3, which supports
|
||||
/// encryption.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import CommunityData
|
||||
///
|
||||
/// CommunityData("public", mpModel=0)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import CommunityData
|
||||
///
|
||||
/// CommunityData("public", mpModel=2)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Cybersecurity and Infrastructure Security Agency (CISA): Alert TA17-156A](https://www.cisa.gov/news-events/alerts/2017/06/05/reducing-risk-snmp-abuse)
|
||||
/// - [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html)
|
||||
#[violation]
|
||||
pub struct SnmpInsecureVersion;
|
||||
|
||||
@@ -18,12 +42,7 @@ impl Violation for SnmpInsecureVersion {
|
||||
}
|
||||
|
||||
/// S508
|
||||
pub(crate) fn snmp_insecure_version(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn snmp_insecure_version(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -31,17 +50,21 @@ pub(crate) fn snmp_insecure_version(
|
||||
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "CommunityData"])
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(mp_model_arg) = call_args.keyword_argument("mpModel") {
|
||||
if let Some(keyword) = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "mpModel")
|
||||
}) {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) = &mp_model_arg
|
||||
}) = &keyword.value
|
||||
{
|
||||
if value.is_zero() || value.is_one() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, mp_model_arg.range()));
|
||||
.push(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,29 @@ use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the SNMPv3 protocol without encryption.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Unencrypted SNMPv3 communication can be intercepted and read by
|
||||
/// unauthorized parties. Instead, enable encryption when using SNMPv3.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import UsmUserData
|
||||
///
|
||||
/// UsmUserData("user")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pysnmp.hlapi import UsmUserData
|
||||
///
|
||||
/// UsmUserData("user", "authkey", "privkey")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html)
|
||||
#[violation]
|
||||
pub struct SnmpWeakCryptography;
|
||||
|
||||
|
||||
@@ -55,16 +55,14 @@ pub(crate) fn try_except_continue(
|
||||
checker: &mut Checker,
|
||||
except_handler: &ExceptHandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
check_typed_exception: bool,
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].is_continue_stmt()
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptContinue, except_handler.range()));
|
||||
if matches!(body, [Stmt::Continue(_)]) {
|
||||
if check_typed_exception || is_untyped_exception(type_, checker.semantic()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptContinue, except_handler.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,16 +51,14 @@ pub(crate) fn try_except_pass(
|
||||
checker: &mut Checker,
|
||||
except_handler: &ExceptHandler,
|
||||
type_: Option<&Expr>,
|
||||
_name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
check_typed_exception: bool,
|
||||
) {
|
||||
if body.len() == 1
|
||||
&& body[0].is_pass_stmt()
|
||||
&& (check_typed_exception || is_untyped_exception(type_, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptPass, except_handler.range()));
|
||||
if matches!(body, [Stmt::Pass(_)]) {
|
||||
if check_typed_exception || is_untyped_exception(type_, checker.semantic()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptPass, except_handler.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,35 @@ use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `yaml.load` function.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Running the `yaml.load` function over untrusted YAML files is insecure, as
|
||||
/// `yaml.load` allows for the creation of arbitrary Python objects, which can
|
||||
/// then be used to execute arbitrary code.
|
||||
///
|
||||
/// Instead, consider using `yaml.safe_load`, which allows for the creation of
|
||||
/// simple Python objects like integers and lists, but prohibits the creation of
|
||||
/// more complex objects like functions and classes.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import yaml
|
||||
///
|
||||
/// yaml.load(untrusted_yaml)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import yaml
|
||||
///
|
||||
/// yaml.safe_load(untrusted_yaml)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PyYAML documentation: Loading YAML](https://pyyaml.org/wiki/PyYAMLDocumentation)
|
||||
/// - [Common Weakness Enumeration: CWE-20](https://cwe.mitre.org/data/definitions/20.html)
|
||||
#[violation]
|
||||
pub struct UnsafeYAMLLoad {
|
||||
pub loader: Option<String>,
|
||||
|
||||
@@ -11,11 +11,11 @@ S113.py:3:1: S113 Probable use of requests call without timeout
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:4:43: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:4:35: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
3 | requests.get('https://gmail.com')
|
||||
4 | requests.get('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
6 | requests.post('https://gmail.com')
|
||||
|
|
||||
@@ -30,12 +30,12 @@ S113.py:6:1: S113 Probable use of requests call without timeout
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:7:44: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:7:36: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
5 | requests.get('https://gmail.com', timeout=5)
|
||||
6 | requests.post('https://gmail.com')
|
||||
7 | requests.post('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
9 | requests.put('https://gmail.com')
|
||||
|
|
||||
@@ -50,12 +50,12 @@ S113.py:9:1: S113 Probable use of requests call without timeout
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:10:43: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:10:35: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
8 | requests.post('https://gmail.com', timeout=5)
|
||||
9 | requests.put('https://gmail.com')
|
||||
10 | requests.put('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
12 | requests.delete('https://gmail.com')
|
||||
|
|
||||
@@ -70,12 +70,12 @@ S113.py:12:1: S113 Probable use of requests call without timeout
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:13:46: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:13:38: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
11 | requests.put('https://gmail.com', timeout=5)
|
||||
12 | requests.delete('https://gmail.com')
|
||||
13 | requests.delete('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
15 | requests.patch('https://gmail.com')
|
||||
|
|
||||
@@ -90,12 +90,12 @@ S113.py:15:1: S113 Probable use of requests call without timeout
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:16:45: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:16:37: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
14 | requests.delete('https://gmail.com', timeout=5)
|
||||
15 | requests.patch('https://gmail.com')
|
||||
16 | requests.patch('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
18 | requests.options('https://gmail.com')
|
||||
|
|
||||
@@ -110,12 +110,12 @@ S113.py:18:1: S113 Probable use of requests call without timeout
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:19:47: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:19:39: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
17 | requests.patch('https://gmail.com', timeout=5)
|
||||
18 | requests.options('https://gmail.com')
|
||||
19 | requests.options('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
21 | requests.head('https://gmail.com')
|
||||
|
|
||||
@@ -130,12 +130,12 @@ S113.py:21:1: S113 Probable use of requests call without timeout
|
||||
23 | requests.head('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
S113.py:22:44: S113 Probable use of requests call with timeout set to `None`
|
||||
S113.py:22:36: S113 Probable use of requests call with timeout set to `None`
|
||||
|
|
||||
20 | requests.options('https://gmail.com', timeout=5)
|
||||
21 | requests.head('https://gmail.com')
|
||||
22 | requests.head('https://gmail.com', timeout=None)
|
||||
| ^^^^ S113
|
||||
| ^^^^^^^^^^^^ S113
|
||||
23 | requests.head('https://gmail.com', timeout=5)
|
||||
|
|
||||
|
||||
|
||||
@@ -1,180 +1,180 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S501.py:5:54: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:5:47: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
4 | requests.get('https://gmail.com', timeout=30, verify=True)
|
||||
5 | requests.get('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
6 | requests.post('https://gmail.com', timeout=30, verify=True)
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:7:55: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:7:48: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
5 | requests.get('https://gmail.com', timeout=30, verify=False)
|
||||
6 | requests.post('https://gmail.com', timeout=30, verify=True)
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
8 | requests.put('https://gmail.com', timeout=30, verify=True)
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:9:54: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:9:47: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
7 | requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
8 | requests.put('https://gmail.com', timeout=30, verify=True)
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
10 | requests.delete('https://gmail.com', timeout=30, verify=True)
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:11:57: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:11:50: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
9 | requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
10 | requests.delete('https://gmail.com', timeout=30, verify=True)
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
12 | requests.patch('https://gmail.com', timeout=30, verify=True)
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:13:56: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:13:49: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
11 | requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
12 | requests.patch('https://gmail.com', timeout=30, verify=True)
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
14 | requests.options('https://gmail.com', timeout=30, verify=True)
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:15:58: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:15:51: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
13 | requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
14 | requests.options('https://gmail.com', timeout=30, verify=True)
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
16 | requests.head('https://gmail.com', timeout=30, verify=True)
|
||||
17 | requests.head('https://gmail.com', timeout=30, verify=False)
|
||||
|
|
||||
|
||||
S501.py:17:55: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:17:48: S501 Probable use of `requests` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
15 | requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
16 | requests.head('https://gmail.com', timeout=30, verify=True)
|
||||
17 | requests.head('https://gmail.com', timeout=30, verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
18 |
|
||||
19 | httpx.request('GET', 'https://gmail.com', verify=True)
|
||||
|
|
||||
|
||||
S501.py:20:50: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:20:43: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
19 | httpx.request('GET', 'https://gmail.com', verify=True)
|
||||
20 | httpx.request('GET', 'https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
21 | httpx.get('https://gmail.com', verify=True)
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:22:39: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:22:32: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
20 | httpx.request('GET', 'https://gmail.com', verify=False)
|
||||
21 | httpx.get('https://gmail.com', verify=True)
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
23 | httpx.options('https://gmail.com', verify=True)
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:24:43: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:24:36: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
22 | httpx.get('https://gmail.com', verify=False)
|
||||
23 | httpx.options('https://gmail.com', verify=True)
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
25 | httpx.head('https://gmail.com', verify=True)
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:26:40: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:26:33: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
24 | httpx.options('https://gmail.com', verify=False)
|
||||
25 | httpx.head('https://gmail.com', verify=True)
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
27 | httpx.post('https://gmail.com', verify=True)
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:28:40: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:28:33: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
26 | httpx.head('https://gmail.com', verify=False)
|
||||
27 | httpx.post('https://gmail.com', verify=True)
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
29 | httpx.put('https://gmail.com', verify=True)
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:30:39: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:30:32: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
28 | httpx.post('https://gmail.com', verify=False)
|
||||
29 | httpx.put('https://gmail.com', verify=True)
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
31 | httpx.patch('https://gmail.com', verify=True)
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:32:41: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:32:34: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
30 | httpx.put('https://gmail.com', verify=False)
|
||||
31 | httpx.patch('https://gmail.com', verify=True)
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
33 | httpx.delete('https://gmail.com', verify=True)
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:34:42: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:34:35: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
32 | httpx.patch('https://gmail.com', verify=False)
|
||||
33 | httpx.delete('https://gmail.com', verify=True)
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
35 | httpx.stream('https://gmail.com', verify=True)
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
|
|
||||
|
||||
S501.py:36:42: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:36:35: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
34 | httpx.delete('https://gmail.com', verify=False)
|
||||
35 | httpx.stream('https://gmail.com', verify=True)
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
37 | httpx.Client()
|
||||
38 | httpx.Client(verify=False)
|
||||
|
|
||||
|
||||
S501.py:38:21: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:38:14: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
36 | httpx.stream('https://gmail.com', verify=False)
|
||||
37 | httpx.Client()
|
||||
38 | httpx.Client(verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
39 | httpx.AsyncClient()
|
||||
40 | httpx.AsyncClient(verify=False)
|
||||
|
|
||||
|
||||
S501.py:40:26: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
S501.py:40:19: S501 Probable use of `httpx` call with `verify=False` disabling SSL certificate checks
|
||||
|
|
||||
38 | httpx.Client(verify=False)
|
||||
39 | httpx.AsyncClient()
|
||||
40 | httpx.AsyncClient(verify=False)
|
||||
| ^^^^^ S501
|
||||
| ^^^^^^^^^^^^ S501
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S508.py:3:33: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
S508.py:3:25: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
|
|
||||
1 | from pysnmp.hlapi import CommunityData
|
||||
2 |
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
| ^ S508
|
||||
| ^^^^^^^^^ S508
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
|
|
||||
|
||||
S508.py:4:33: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
S508.py:4:25: S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able.
|
||||
|
|
||||
3 | CommunityData("public", mpModel=0) # S508
|
||||
4 | CommunityData("public", mpModel=1) # S508
|
||||
| ^ S508
|
||||
| ^^^^^^^^^ S508
|
||||
5 |
|
||||
6 | CommunityData("public", mpModel=2) # OK
|
||||
|
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S701.py:9:68: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:9:57: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
7 | templateEnv = jinja2.Environment(autoescape=True,
|
||||
8 | loader=templateLoader )
|
||||
9 | Environment(loader=templateLoader, load=templateLoader, autoescape=something) # S701
|
||||
| ^^^^^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^^^^^ S701
|
||||
10 | templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) # S701
|
||||
11 | Environment(loader=templateLoader,
|
||||
|
|
||||
|
||||
S701.py:10:45: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:10:34: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
8 | loader=templateLoader )
|
||||
9 | Environment(loader=templateLoader, load=templateLoader, autoescape=something) # S701
|
||||
10 | templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) # S701
|
||||
| ^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^ S701
|
||||
11 | Environment(loader=templateLoader,
|
||||
12 | load=templateLoader,
|
||||
|
|
||||
|
||||
S701.py:13:24: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:13:13: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
11 | Environment(loader=templateLoader,
|
||||
12 | load=templateLoader,
|
||||
13 | autoescape=False) # S701
|
||||
| ^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^ S701
|
||||
14 |
|
||||
15 | Environment(loader=templateLoader, # S701
|
||||
|
|
||||
@@ -40,12 +40,12 @@ S701.py:15:1: S701 By default, jinja2 sets `autoescape` to `False`. Consider usi
|
||||
16 | load=templateLoader)
|
||||
|
|
||||
|
||||
S701.py:29:47: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
S701.py:29:36: S701 Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function.
|
||||
|
|
||||
27 | def fake_func():
|
||||
28 | return 'foobar'
|
||||
29 | Environment(loader=templateLoader, autoescape=fake_func()) # S701
|
||||
| ^^^^^^^^^^^ S701
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ S701
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,12 +37,7 @@ impl Violation for NoExplicitStacklevel {
|
||||
}
|
||||
|
||||
/// B028
|
||||
pub(crate) fn no_explicit_stacklevel(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
@@ -54,10 +48,12 @@ pub(crate) fn no_explicit_stacklevel(
|
||||
return;
|
||||
}
|
||||
|
||||
if SimpleCallArgs::new(args, keywords)
|
||||
.keyword_argument("stacklevel")
|
||||
.is_some()
|
||||
{
|
||||
if keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "stacklevel")
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `hasattr` to test if an object is callable (e.g.,
|
||||
@@ -35,13 +36,19 @@ use crate::checkers::ast::Checker;
|
||||
pub struct UnreliableCallableCheck;
|
||||
|
||||
impl Violation for UnreliableCallableCheck {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use \
|
||||
"Using `hasattr(x, \"__call__\")` to test if x is callable is unreliable. Use \
|
||||
`callable(x)` for consistent results."
|
||||
)
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `callable()`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// B004
|
||||
@@ -54,23 +61,33 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if id != "getattr" && id != "hasattr" {
|
||||
if !matches!(id.as_str(), "hasattr" | "getattr") {
|
||||
return;
|
||||
}
|
||||
if args.len() < 2 {
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(s),
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) = &args[1]
|
||||
}) = attr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if s != "__call__" {
|
||||
if string != "__call__" {
|
||||
return;
|
||||
}
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UnreliableCallableCheck, expr.range()));
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if id == "hasattr" {
|
||||
if checker.semantic().is_builtin("callable") {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
format!("callable({})", checker.locator.slice(obj.range())),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -154,20 +154,23 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
if let Some(rename) = rename {
|
||||
if certainty.into() && checker.patch(diagnostic.kind.rule()) {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.all(|binding| !binding.is_used())
|
||||
{
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
rename,
|
||||
expr.range(),
|
||||
)));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(rename) = rename {
|
||||
if certainty.into() {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.filter(|binding| binding.range.start() >= expr.range().start())
|
||||
.all(|binding| !binding.is_used())
|
||||
{
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
rename,
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B004.py:3:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
B004.py:3:8: B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
|
|
||||
1 | def this_is_a_bug():
|
||||
2 | o = object()
|
||||
@@ -10,8 +10,18 @@ B004.py:3:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unr
|
||||
4 | print("Ooh, callable! Or is it?")
|
||||
5 | if getattr(o, "__call__", False):
|
||||
|
|
||||
= help: Replace with `callable()`
|
||||
|
||||
B004.py:5:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
ℹ Fix
|
||||
1 1 | def this_is_a_bug():
|
||||
2 2 | o = object()
|
||||
3 |- if hasattr(o, "__call__"):
|
||||
3 |+ if callable(o):
|
||||
4 4 | print("Ooh, callable! Or is it?")
|
||||
5 5 | if getattr(o, "__call__", False):
|
||||
6 6 | print("Ooh, callable! Or is it?")
|
||||
|
||||
B004.py:5:8: B004 Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
|
|
||||
3 | if hasattr(o, "__call__"):
|
||||
4 | print("Ooh, callable! Or is it?")
|
||||
@@ -19,5 +29,6 @@ B004.py:5:8: B004 Using `hasattr(x, '__call__')` to test if x is callable is unr
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B004
|
||||
6 | print("Ooh, callable! Or is it?")
|
||||
|
|
||||
= help: Replace with `callable()`
|
||||
|
||||
|
||||
|
||||
@@ -72,42 +72,42 @@ B006_B008.py:100:33: B006 Do not use mutable data structures for argument defaul
|
||||
101 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:218:20: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
216 | # B006 and B008
|
||||
217 | # We should handle arbitrary nesting of these B008.
|
||||
218 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
|
||||
219 | pass
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:251:27: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:254:27: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
250 | def mutable_annotations(
|
||||
251 | a: list[int] | None = [],
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
| ^^ B006
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
|
||||
B006_B008.py:252:35: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:255:35: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
250 | def mutable_annotations(
|
||||
251 | a: list[int] | None = [],
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^ B006
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | ):
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
257 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:253:62: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:256:62: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
251 | a: list[int] | None = [],
|
||||
252 | b: Optional[Dict[int, int]] = {},
|
||||
253 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
254 | ):
|
||||
255 | pass
|
||||
257 | ):
|
||||
258 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -46,38 +46,38 @@ B006_B008.py:120:30: B008 Do not perform function call in argument defaults
|
||||
121 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:218:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:221:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
216 | # B006 and B008
|
||||
217 | # We should handle arbitrary nesting of these B008.
|
||||
218 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
219 | # B006 and B008
|
||||
220 | # We should handle arbitrary nesting of these B008.
|
||||
221 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
219 | pass
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:224:22: B008 Do not perform function call `map` in argument defaults
|
||||
B006_B008.py:227:22: B008 Do not perform function call `map` in argument defaults
|
||||
|
|
||||
222 | # Don't flag nested B006 since we can't guarantee that
|
||||
223 | # it isn't made mutable by the outer operation.
|
||||
224 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
225 | # Don't flag nested B006 since we can't guarantee that
|
||||
226 | # it isn't made mutable by the outer operation.
|
||||
227 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
225 | pass
|
||||
228 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:229:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
B006_B008.py:232:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
|
|
||||
228 | # B008-ception.
|
||||
229 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
|
||||
230 | pass
|
||||
233 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:229:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:232:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
|
|
||||
228 | # B008-ception.
|
||||
229 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
231 | # B008-ception.
|
||||
232 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
230 | pass
|
||||
233 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,44 +1,5 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{ExceptHandler, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::identifier::{Identifier, TryIdentifier};
|
||||
use ruff_python_stdlib::builtins::BUILTINS;
|
||||
use ruff_python_stdlib::builtins::is_builtin;
|
||||
|
||||
pub(super) fn shadows_builtin(name: &str, ignorelist: &[String]) -> bool {
|
||||
BUILTINS.contains(&name) && ignorelist.iter().all(|ignore| ignore != name)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub(crate) enum AnyShadowing<'a> {
|
||||
Expression(&'a Expr),
|
||||
Statement(&'a Stmt),
|
||||
ExceptHandler(&'a ExceptHandler),
|
||||
}
|
||||
|
||||
impl Identifier for AnyShadowing<'_> {
|
||||
fn identifier(&self) -> TextRange {
|
||||
match self {
|
||||
AnyShadowing::Expression(expr) => expr.range(),
|
||||
AnyShadowing::Statement(stmt) => stmt.identifier(),
|
||||
AnyShadowing::ExceptHandler(handler) => handler.try_identifier().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Stmt> for AnyShadowing<'a> {
|
||||
fn from(value: &'a Stmt) -> Self {
|
||||
AnyShadowing::Statement(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Expr> for AnyShadowing<'a> {
|
||||
fn from(value: &'a Expr) -> Self {
|
||||
AnyShadowing::Expression(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ExceptHandler> for AnyShadowing<'a> {
|
||||
fn from(value: &'a ExceptHandler) -> Self {
|
||||
AnyShadowing::ExceptHandler(value)
|
||||
}
|
||||
is_builtin(name) && ignorelist.iter().all(|ignore| ignore != name)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::super::helpers::{shadows_builtin, AnyShadowing};
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for any class attributes that use the same name as a builtin.
|
||||
@@ -67,7 +67,7 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
name: &str,
|
||||
shadowing: AnyShadowing,
|
||||
range: TextRange,
|
||||
) {
|
||||
if shadows_builtin(name, &checker.settings.flake8_builtins.builtins_ignorelist) {
|
||||
// Ignore shadowing within `TypedDict` definitions, since these are only accessible through
|
||||
@@ -84,7 +84,7 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
BuiltinAttributeShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.identifier(),
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::super::helpers::{shadows_builtin, AnyShadowing};
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for variable (and function) assignments that use the same name
|
||||
@@ -59,17 +59,13 @@ impl Violation for BuiltinVariableShadowing {
|
||||
}
|
||||
|
||||
/// A001
|
||||
pub(crate) fn builtin_variable_shadowing(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
shadowing: AnyShadowing,
|
||||
) {
|
||||
pub(crate) fn builtin_variable_shadowing(checker: &mut Checker, name: &str, range: TextRange) {
|
||||
if shadows_builtin(name, &checker.settings.flake8_builtins.builtins_ignorelist) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BuiltinVariableShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.identifier(),
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A001.py:1:1: A001 Variable `sum` is shadowing a Python builtin
|
||||
A001.py:1:16: A001 Variable `sum` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
| ^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:2:1: A001 Variable `int` is shadowing a Python builtin
|
||||
A001.py:2:27: A001 Variable `int` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:3:1: A001 Variable `dir` is shadowing a Python builtin
|
||||
A001.py:3:30: A001 Variable `dir` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
4 |
|
||||
5 | print = 1
|
||||
|
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A001.py:1:1: A001 Variable `sum` is shadowing a Python builtin
|
||||
A001.py:1:16: A001 Variable `sum` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
| ^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
2 | from some import other as int
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
A001.py:2:1: A001 Variable `int` is shadowing a Python builtin
|
||||
A001.py:2:27: A001 Variable `int` is shadowing a Python builtin
|
||||
|
|
||||
1 | import some as sum
|
||||
2 | from some import other as int
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A001
|
||||
| ^^^ A001
|
||||
3 | from directory import new as dir
|
||||
|
|
||||
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
//! Rules from [flake8-gettext](https://pypi.org/project/flake8-gettext/).
|
||||
use rustpython_parser::ast::{self, Expr};
|
||||
|
||||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
@@ -5,6 +5,40 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for f-strings in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with an f-string as its argument can cause unexpected
|
||||
/// behavior. Since the f-string is resolved before the function call, the
|
||||
/// translation catalog will look up the formatted string, rather than the
|
||||
/// f-string template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _(f"Hello, {name}!") # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FStringInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -5,6 +5,40 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `str.format` calls in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with a formatted string as its argument can cause
|
||||
/// unexpected behavior. Since the formatted string is resolved before the
|
||||
/// function call, the translation catalog will look up the formatted string,
|
||||
/// rather than the `str.format`-style template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!" % name) # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FormatInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use rustpython_parser::ast::{self, Expr};
|
||||
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
pub(crate) use f_string_in_gettext_func_call::*;
|
||||
pub(crate) use format_in_gettext_func_call::*;
|
||||
pub(crate) use is_gettext_func_call::*;
|
||||
pub(crate) use printf_in_gettext_func_call::*;
|
||||
|
||||
mod f_string_in_gettext_func_call;
|
||||
mod format_in_gettext_func_call;
|
||||
mod is_gettext_func_call;
|
||||
mod printf_in_gettext_func_call;
|
||||
|
||||
@@ -4,6 +4,40 @@ use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for printf-style formatted strings in `gettext` function calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the `gettext` API, the `gettext` function (often aliased to `_`) returns
|
||||
/// a translation of its input argument by looking it up in a translation
|
||||
/// catalog.
|
||||
///
|
||||
/// Calling `gettext` with a formatted string as its argument can cause
|
||||
/// unexpected behavior. Since the formatted string is resolved before the
|
||||
/// function call, the translation catalog will look up the formatted string,
|
||||
/// rather than the printf-style template.
|
||||
///
|
||||
/// Instead, format the value returned by the function call, rather than
|
||||
/// its argument.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, {}!".format(name)) # Looks for "Hello, Maria!".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from gettext import gettext as _
|
||||
///
|
||||
/// name = "Maria"
|
||||
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct PrintfInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -208,12 +208,7 @@ fn check_positional_args(
|
||||
(ErrorKind::ThirdArgBadAnnotation, is_traceback_type),
|
||||
];
|
||||
|
||||
for (arg, (error_info, predicate)) in positional_args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.take(3)
|
||||
.zip(validations.into_iter())
|
||||
{
|
||||
for (arg, (error_info, predicate)) in positional_args.iter().skip(1).take(3).zip(validations) {
|
||||
let Some(annotation) = arg.def.annotation.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ use rustpython_parser::ast::{self, BoolOp, ExceptHandler, Expr, Keyword, Ranged,
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{has_comments_in, Truthiness};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{visitor, whitespace};
|
||||
@@ -197,7 +197,7 @@ pub(crate) fn unittest_assertion(
|
||||
if checker.semantic().stmt().is_expr_stmt()
|
||||
&& checker.semantic().expr_parent().is_none()
|
||||
&& !checker.semantic().scope().kind.is_lambda()
|
||||
&& !has_comments_in(expr.range(), checker.locator)
|
||||
&& !checker.indexer.comment_ranges().intersects(expr.range())
|
||||
{
|
||||
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
@@ -483,7 +483,7 @@ pub(crate) fn composite_condition(
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if matches!(composite, CompositionKind::Simple)
|
||||
&& msg.is_none()
|
||||
&& !has_comments_in(stmt.range(), checker.locator)
|
||||
&& !checker.indexer.comment_ranges().intersects(stmt.range())
|
||||
{
|
||||
#[allow(deprecated)]
|
||||
diagnostic.try_set_fix_from_edit(|| {
|
||||
|
||||
@@ -505,7 +505,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
|
||||
}
|
||||
|
||||
// Avoid removing comments.
|
||||
if has_comments(expr, checker.locator) {
|
||||
if has_comments(expr, checker.locator, checker.indexer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ use rustpython_parser::ast::{self, CmpOp, Constant, Expr, ExprContext, Identifie
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::{ComparableConstant, ComparableExpr, ComparableStmt};
|
||||
use ruff_python_ast::helpers::{
|
||||
any_over_expr, contains_effect, first_colon_range, has_comments, has_comments_in,
|
||||
};
|
||||
use ruff_python_ast::helpers::{any_over_expr, contains_effect, first_colon_range, has_comments};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_whitespace::UniversalNewlines;
|
||||
|
||||
@@ -378,10 +376,11 @@ pub(crate) fn nested_if_statements(
|
||||
// The fixer preserves comments in the nested body, but removes comments between
|
||||
// the outer and inner if statements.
|
||||
let nested_if = &body[0];
|
||||
if !has_comments_in(
|
||||
TextRange::new(stmt.start(), nested_if.start()),
|
||||
checker.locator,
|
||||
) {
|
||||
if !checker
|
||||
.indexer
|
||||
.comment_ranges()
|
||||
.intersects(TextRange::new(stmt.start(), nested_if.start()))
|
||||
{
|
||||
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, stmt) {
|
||||
Ok(edit) => {
|
||||
if edit
|
||||
@@ -464,7 +463,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if matches!(if_return, Bool::True)
|
||||
&& matches!(else_return, Bool::False)
|
||||
&& !has_comments(stmt, checker.locator)
|
||||
&& !has_comments(stmt, checker.locator, checker.indexer)
|
||||
&& (test.is_compare_expr() || checker.semantic().is_builtin("bool"))
|
||||
{
|
||||
if test.is_compare_expr() {
|
||||
@@ -650,7 +649,7 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: O
|
||||
stmt.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if !has_comments(stmt, checker.locator) {
|
||||
if !has_comments(stmt, checker.locator, checker.indexer) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
contents,
|
||||
stmt.range(),
|
||||
@@ -1050,7 +1049,7 @@ pub(crate) fn use_dict_get_with_default(
|
||||
stmt.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if !has_comments(stmt, checker.locator) {
|
||||
if !has_comments(stmt, checker.locator, checker.indexer) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
contents,
|
||||
stmt.range(),
|
||||
|
||||
@@ -5,7 +5,7 @@ use rustpython_parser::ast::{self, Ranged, Stmt, WithItem};
|
||||
use ruff_diagnostics::{AutofixKind, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{first_colon_range, has_comments_in};
|
||||
use ruff_python_ast::helpers::first_colon_range;
|
||||
use ruff_python_whitespace::UniversalNewlines;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -129,10 +129,11 @@ pub(crate) fn multiple_with_statements(
|
||||
),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if !has_comments_in(
|
||||
TextRange::new(with_stmt.start(), with_body[0].start()),
|
||||
checker.locator,
|
||||
) {
|
||||
if !checker
|
||||
.indexer
|
||||
.comment_ranges()
|
||||
.intersects(TextRange::new(with_stmt.start(), with_body[0].start()))
|
||||
{
|
||||
match fix_with::fix_multiple_with_statements(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
|
||||
@@ -117,7 +117,7 @@ pub(crate) fn suppressible_exception(
|
||||
stmt.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if !has_comments(stmt, checker.locator) {
|
||||
if !has_comments(stmt, checker.locator, checker.indexer) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer.get_or_import_symbol(
|
||||
&ImportRequest::import("contextlib", "suppress"),
|
||||
|
||||
@@ -15,13 +15,10 @@ mod tests {
|
||||
use crate::test::{test_path, test_snippet};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"))]
|
||||
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TCH001.py"))]
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("TCH002.py"))]
|
||||
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("TCH003.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_1.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_14.pyi"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_2.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_3.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_4.py"))]
|
||||
@@ -30,12 +27,13 @@ mod tests {
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_7.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_8.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_9.py"))]
|
||||
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TCH001.py"))]
|
||||
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("TCH003.py"))]
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("TCH002.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_14.pyi"))]
|
||||
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"))]
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"))]
|
||||
#[test_case(Rule::UnquotedAnnotation, Path::new("TCH200_0.py"))]
|
||||
#[test_case(Rule::UnquotedAnnotation, Path::new("TCH200_1.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
pub(crate) use empty_type_checking_block::*;
|
||||
pub(crate) use quoted_annotation::*;
|
||||
pub(crate) use runtime_import_in_type_checking_block::*;
|
||||
pub(crate) use typing_only_runtime_import::*;
|
||||
|
||||
mod empty_type_checking_block;
|
||||
mod quoted_annotation;
|
||||
mod runtime_import_in_type_checking_block;
|
||||
mod typing_only_runtime_import;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::BindingId;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of unnecessary quotes in type annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, type annotations can be quoted to avoid forward references.
|
||||
/// However, if `from __future__ import annotations` is present, Python
|
||||
/// will always evaluate type annotations in a deferred manner, making
|
||||
/// the quotes unnecessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
///
|
||||
/// def foo(bar: "Bar") -> "Bar":
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
///
|
||||
/// def foo(bar: Bar) -> Bar:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563](https://peps.python.org/pep-0563/)
|
||||
/// - [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)
|
||||
#[violation]
|
||||
pub struct UnquotedAnnotation {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for UnquotedAnnotation {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnquotedAnnotation { name } = self;
|
||||
format!("Typing-only variable referenced in runtime annotation: `{name}`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some("Add quotes".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCH200
|
||||
pub(crate) fn unquoted_annotation(checker: &mut Checker, binding_id: BindingId, expr: &Expr) {
|
||||
// If we're already in a quoted annotation, skip.
|
||||
if checker.semantic().in_deferred_type_definition() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in a typing-only context, skip.
|
||||
if checker.semantic().execution_context().is_typing() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the reference resolved to a typing-only import, flag.
|
||||
if checker.semantic().bindings[binding_id].context.is_typing() {
|
||||
// Expand any attribute chains (e.g., flag `typing.List` in `typing.List[int]`).
|
||||
let mut expr = expr;
|
||||
for parent in checker.semantic().expr_ancestors() {
|
||||
if parent.is_attribute_expr() {
|
||||
expr = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnquotedAnnotation {
|
||||
name: checker.locator.slice(expr.range()).to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// We can only _fix_ this if we're in a type annotation.
|
||||
if checker.semantic().in_runtime_annotation() {
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
format!("\"{}\"", checker.locator.slice(expr.range()).to_string()),
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TCH200_0.py:8:12: TCH200 [*] Typing-only variable referenced in runtime annotation: `Class`
|
||||
|
|
||||
8 | def f(var: Class) -> Class:
|
||||
| ^^^^^ TCH200
|
||||
9 | x: Class
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
ℹ Fix
|
||||
5 5 | from module import Class
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 |-def f(var: Class) -> Class:
|
||||
8 |+def f(var: "Class") -> Class:
|
||||
9 9 | x: Class
|
||||
10 10 |
|
||||
11 11 |
|
||||
|
||||
TCH200_0.py:8:22: TCH200 [*] Typing-only variable referenced in runtime annotation: `Class`
|
||||
|
|
||||
8 | def f(var: Class) -> Class:
|
||||
| ^^^^^ TCH200
|
||||
9 | x: Class
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
ℹ Fix
|
||||
5 5 | from module import Class
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 |-def f(var: Class) -> Class:
|
||||
8 |+def f(var: Class) -> "Class":
|
||||
9 9 | x: Class
|
||||
10 10 |
|
||||
11 11 |
|
||||
|
||||
TCH200_0.py:12:12: TCH200 [*] Typing-only variable referenced in runtime annotation: `module.Class`
|
||||
|
|
||||
12 | def f(var: module.Class) -> module.Class:
|
||||
| ^^^^^^^^^^^^ TCH200
|
||||
13 | x: module.Class
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
ℹ Fix
|
||||
9 9 | x: Class
|
||||
10 10 |
|
||||
11 11 |
|
||||
12 |-def f(var: module.Class) -> module.Class:
|
||||
12 |+def f(var: "module.Class") -> module.Class:
|
||||
13 13 | x: module.Class
|
||||
14 14 |
|
||||
15 15 |
|
||||
|
||||
TCH200_0.py:12:29: TCH200 [*] Typing-only variable referenced in runtime annotation: `module.Class`
|
||||
|
|
||||
12 | def f(var: module.Class) -> module.Class:
|
||||
| ^^^^^^^^^^^^ TCH200
|
||||
13 | x: module.Class
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
ℹ Fix
|
||||
9 9 | x: Class
|
||||
10 10 |
|
||||
11 11 |
|
||||
12 |-def f(var: module.Class) -> module.Class:
|
||||
12 |+def f(var: module.Class) -> "module.Class":
|
||||
13 13 | x: module.Class
|
||||
14 14 |
|
||||
15 15 |
|
||||
|
||||
TCH200_0.py:17:11: TCH200 Typing-only variable referenced in runtime annotation: `Class`
|
||||
|
|
||||
16 | def f():
|
||||
17 | print(Class)
|
||||
| ^^^^^ TCH200
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
TCH200_0.py:21:11: TCH200 Typing-only variable referenced in runtime annotation: `module.Class`
|
||||
|
|
||||
20 | def f():
|
||||
21 | print(module.Class)
|
||||
| ^^^^^^^^^^^^ TCH200
|
||||
|
|
||||
= help: Add quotes
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
|
||||
@@ -777,6 +777,35 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("comment.py"))]
|
||||
#[test_case(Path::new("docstring.py"))]
|
||||
#[test_case(Path::new("docstring.pyi"))]
|
||||
#[test_case(Path::new("docstring_only.py"))]
|
||||
#[test_case(Path::new("docstring_with_continuation.py"))]
|
||||
#[test_case(Path::new("docstring_with_semicolon.py"))]
|
||||
#[test_case(Path::new("empty.py"))]
|
||||
#[test_case(Path::new("existing_import.py"))]
|
||||
#[test_case(Path::new("multiline_docstring.py"))]
|
||||
#[test_case(Path::new("off.py"))]
|
||||
fn required_import_with_alias(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("required_import_with_alias_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort/required_imports").join(path).as_path(),
|
||||
&Settings {
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
isort: super::settings::Settings {
|
||||
required_imports: BTreeSet::from([
|
||||
"from __future__ import annotations as _annotations".to_string(),
|
||||
]),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rule(Rule::MissingRequiredImport)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("docstring.py"))]
|
||||
#[test_case(Path::new("docstring.pyi"))]
|
||||
#[test_case(Path::new("docstring_only.py"))]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
comment.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | #!/usr/bin/env python3
|
||||
| I002
|
||||
2 |
|
||||
3 | x = 1
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | #!/usr/bin/env python3
|
||||
2 |+from __future__ import annotations as _annotations
|
||||
2 3 |
|
||||
3 4 | x = 1
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
docstring.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | """Hello, world!"""
|
||||
| I002
|
||||
2 |
|
||||
3 | x = 1
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | """Hello, world!"""
|
||||
2 |+from __future__ import annotations as _annotations
|
||||
2 3 |
|
||||
3 4 | x = 1
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
docstring_with_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | """Hello, world!"""; x = \
|
||||
| I002
|
||||
2 | 1; y = 2
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 |-"""Hello, world!"""; x = \
|
||||
1 |+"""Hello, world!"""; from __future__ import annotations as _annotations; x = \
|
||||
2 2 | 1; y = 2
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
docstring_with_semicolon.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | """Hello, world!"""; x = 1
|
||||
| I002
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 |-"""Hello, world!"""; x = 1
|
||||
1 |+"""Hello, world!"""; from __future__ import annotations as _annotations; x = 1
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
existing_import.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | from __future__ import generator_stop
|
||||
| I002
|
||||
2 | import os
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 |+from __future__ import annotations as _annotations
|
||||
1 2 | from __future__ import generator_stop
|
||||
2 3 | import os
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
multiline_docstring.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | """a
|
||||
| I002
|
||||
2 | b"""
|
||||
3 | # b
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | """a
|
||||
2 2 | b"""
|
||||
3 3 | # b
|
||||
4 |+from __future__ import annotations as _annotations
|
||||
4 5 | import os
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
off.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
|
|
||||
1 | # isort: off
|
||||
| I002
|
||||
2 |
|
||||
3 | x = 1
|
||||
|
|
||||
= help: Insert required import: `from future import annotations as _annotations`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # isort: off
|
||||
2 |+from __future__ import annotations as _annotations
|
||||
2 3 |
|
||||
3 4 | x = 1
|
||||
4 5 | # isort: on
|
||||
|
||||
|
||||
@@ -237,27 +237,6 @@ mod tests {
|
||||
"#,
|
||||
"PD011_pass_node_name"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
import pandas as pd
|
||||
employees = pd.read_csv(input_file)
|
||||
"#,
|
||||
"PD012_pass_read_csv"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
import pandas as pd
|
||||
employees = pd.read_table(input_file)
|
||||
"#,
|
||||
"PD012_fail_read_table"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
import pandas as pd
|
||||
employees = read_table
|
||||
"#,
|
||||
"PD012_node_Name_pass"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
import pandas as pd
|
||||
@@ -360,7 +339,12 @@ mod tests {
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::PandasUseOfDotReadTable,
|
||||
Path::new("pandas_use_of_dot_read_table.py")
|
||||
)]
|
||||
#[test_case(Rule::PandasUseOfInplaceArgument, Path::new("PD002.py"))]
|
||||
#[test_case(Rule::PandasNuniqueConstantSeriesCheck, Path::new("PD101.py"))]
|
||||
fn paths(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -126,18 +126,6 @@ impl Violation for PandasUseOfDotPivotOrUnstack {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(tjkuson): Add documentation for this rule once clarified.
|
||||
// https://github.com/astral-sh/ruff/issues/5628
|
||||
#[violation]
|
||||
pub struct PandasUseOfDotReadTable;
|
||||
|
||||
impl Violation for PandasUseOfDotReadTable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`.read_csv` is preferred to `.read_table`; provides same functionality")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `.stack` on Pandas objects.
|
||||
///
|
||||
@@ -193,14 +181,6 @@ pub(crate) fn call(checker: &mut Checker, func: &Expr) {
|
||||
{
|
||||
PandasUseOfDotPivotOrUnstack.into()
|
||||
}
|
||||
"read_table"
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(Rule::PandasUseOfDotReadTable) =>
|
||||
{
|
||||
PandasUseOfDotReadTable.into()
|
||||
}
|
||||
"stack" if checker.settings.rules.enabled(Rule::PandasUseOfDotStack) => {
|
||||
PandasUseOfDotStack.into()
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ pub(crate) use assignment_to_df::*;
|
||||
pub(crate) use attr::*;
|
||||
pub(crate) use call::*;
|
||||
pub(crate) use inplace_argument::*;
|
||||
pub(crate) use nunique_constant_series_check::*;
|
||||
pub(crate) use pd_merge::*;
|
||||
pub(crate) use read_table::*;
|
||||
pub(crate) use subscript::*;
|
||||
|
||||
pub(crate) mod assignment_to_df;
|
||||
pub(crate) mod attr;
|
||||
pub(crate) mod call;
|
||||
pub(crate) mod inplace_argument;
|
||||
pub(crate) mod nunique_constant_series_check;
|
||||
pub(crate) mod pd_merge;
|
||||
pub(crate) mod read_table;
|
||||
pub(crate) mod subscript;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
use num_traits::One;
|
||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pandas_vet::helpers::{test_expression, Resolution};
|
||||
|
||||
/// ## What it does
|
||||
/// Check for uses of `.nunique()` to check if a Pandas Series is constant
|
||||
/// (i.e., contains only one unique value).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `.nunique()` is computationally inefficient for checking if a Series is
|
||||
/// constant.
|
||||
///
|
||||
/// Consider, for example, a Series of length `n` that consists of increasing
|
||||
/// integer values (e.g., 1, 2, 3, 4). The `.nunique()` method will iterate
|
||||
/// over the entire Series to count the number of unique values. But in this
|
||||
/// case, we can detect that the Series is non-constant after visiting the
|
||||
/// first two values, which are non-equal.
|
||||
///
|
||||
/// In general, `.nunique()` requires iterating over the entire Series, while a
|
||||
/// more efficient approach allows short-circuiting the operation as soon as a
|
||||
/// non-equal value is found.
|
||||
///
|
||||
/// Instead of calling `.nunique()`, convert the Series to a NumPy array, and
|
||||
/// check if all values in the array are equal to the first observed value.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pandas as pd
|
||||
///
|
||||
/// data = pd.Series(range(1000))
|
||||
/// if data.nunique() <= 1:
|
||||
/// print("Series is constant")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pandas as pd
|
||||
///
|
||||
/// data = pd.Series(range(1000))
|
||||
/// array = data.to_numpy()
|
||||
/// if array.shape[0] == 0 or (array[0] == array).all():
|
||||
/// print("Series is constant")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Pandas Cookbook: "Constant Series"](https://pandas.pydata.org/docs/user_guide/cookbook.html#constant-series)
|
||||
/// - [Pandas documentation: `nunique`](https://pandas.pydata.org/docs/reference/api/pandas.Series.nunique.html)
|
||||
#[violation]
|
||||
pub struct PandasNuniqueConstantSeriesCheck;
|
||||
|
||||
impl Violation for PandasNuniqueConstantSeriesCheck {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `series.nunique()` for checking that a series is constant is inefficient")
|
||||
}
|
||||
}
|
||||
|
||||
/// PD101
|
||||
pub(crate) fn nunique_constant_series_check(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
ops: &[CmpOp],
|
||||
comparators: &[Expr],
|
||||
) {
|
||||
let ([op], [right]) = (ops, comparators) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Operators may be ==, !=, <=, >.
|
||||
if !matches!(op, CmpOp::Eq | CmpOp::NotEq | CmpOp::LtE | CmpOp::Gt,) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Right should be the integer 1.
|
||||
if !is_constant_one(right) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if call is `.nuniuqe()`.
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = left else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if attr.as_str() != "nunique" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid flagging on non-Series (e.g., `{"a": 1}.at[0]`).
|
||||
if !matches!(
|
||||
test_expression(value, checker.semantic()),
|
||||
Resolution::RelevantLocal
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
PandasNuniqueConstantSeriesCheck,
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Return `true` if an [`Expr`] is a constant `1`.
|
||||
fn is_constant_one(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Int(int) => int.is_one(),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
76
crates/ruff/src/rules/pandas_vet/rules/read_table.rs
Normal file
76
crates/ruff/src/rules/pandas_vet/rules/read_table.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use rustpython_parser::ast;
|
||||
use rustpython_parser::ast::{Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `pd.read_table` to read CSV files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In the Pandas API, `pd.read_csv` and `pd.read_table` are equivalent apart
|
||||
/// from their default separator: `pd.read_csv` defaults to a comma (`,`),
|
||||
/// while `pd.read_table` defaults to a tab (`\t`) as the default separator.
|
||||
///
|
||||
/// Prefer `pd.read_csv` over `pd.read_table` when reading comma-separated
|
||||
/// data (like CSV files), as it is more idiomatic.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pandas as pd
|
||||
///
|
||||
/// cities_df = pd.read_table("cities.csv", sep=",")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pandas as pd
|
||||
///
|
||||
/// cities_df = pd.read_csv("cities.csv")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Pandas documentation: `read_csv`](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html#pandas.read_csv)
|
||||
/// - [Pandas documentation: `read_table`](https://pandas.pydata.org/docs/reference/api/pandas.read_table.html#pandas.read_table)
|
||||
#[violation]
|
||||
pub struct PandasUseOfDotReadTable;
|
||||
|
||||
impl Violation for PandasUseOfDotReadTable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use `.read_csv` instead of `.read_table` to read CSV files")
|
||||
}
|
||||
}
|
||||
|
||||
/// PD012
|
||||
pub(crate) fn use_of_read_table(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pandas", "read_table"])
|
||||
})
|
||||
{
|
||||
if let Some(Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
})) = keywords
|
||||
.iter()
|
||||
.find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |keyword| keyword.as_str() == "sep")
|
||||
})
|
||||
.map(|keyword| &keyword.value)
|
||||
{
|
||||
if value.as_str() == "," {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(PandasUseOfDotReadTable, func.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pandas_vet/mod.rs
|
||||
---
|
||||
<filename>:3:13: PD012 `.read_csv` is preferred to `.read_table`; provides same functionality
|
||||
|
|
||||
2 | import pandas as pd
|
||||
3 | employees = pd.read_table(input_file)
|
||||
| ^^^^^^^^^^^^^ PD012
|
||||
|
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pandas_vet/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pandas_vet/mod.rs
|
||||
---
|
||||
pandas_use_of_dot_read_table.py:4:6: PD012 Use `.read_csv` instead of `.read_table` to read CSV files
|
||||
|
|
||||
3 | # Errors.
|
||||
4 | df = pd.read_table("data.csv", sep=",")
|
||||
| ^^^^^^^^^^^^^ PD012
|
||||
5 | df = pd.read_table("data.csv", sep=",", header=0)
|
||||
6 | filename = "data.csv"
|
||||
|
|
||||
|
||||
pandas_use_of_dot_read_table.py:5:6: PD012 Use `.read_csv` instead of `.read_table` to read CSV files
|
||||
|
|
||||
3 | # Errors.
|
||||
4 | df = pd.read_table("data.csv", sep=",")
|
||||
5 | df = pd.read_table("data.csv", sep=",", header=0)
|
||||
| ^^^^^^^^^^^^^ PD012
|
||||
6 | filename = "data.csv"
|
||||
7 | df = pd.read_table(filename, sep=",")
|
||||
|
|
||||
|
||||
pandas_use_of_dot_read_table.py:7:6: PD012 Use `.read_csv` instead of `.read_table` to read CSV files
|
||||
|
|
||||
5 | df = pd.read_table("data.csv", sep=",", header=0)
|
||||
6 | filename = "data.csv"
|
||||
7 | df = pd.read_table(filename, sep=",")
|
||||
| ^^^^^^^^^^^^^ PD012
|
||||
8 | df = pd.read_table(filename, sep=",", header=0)
|
||||
|
|
||||
|
||||
pandas_use_of_dot_read_table.py:8:6: PD012 Use `.read_csv` instead of `.read_table` to read CSV files
|
||||
|
|
||||
6 | filename = "data.csv"
|
||||
7 | df = pd.read_table(filename, sep=",")
|
||||
8 | df = pd.read_table(filename, sep=",", header=0)
|
||||
| ^^^^^^^^^^^^^ PD012
|
||||
9 |
|
||||
10 | # Non-errors.
|
||||
|
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pandas_vet/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pandas_vet/mod.rs
|
||||
---
|
||||
PD101.py:7:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
6 | # PD101
|
||||
7 | data.nunique() <= 1
|
||||
| ^^^^^^^^^^^^^^^^^^^ PD101
|
||||
8 | data.nunique(dropna=True) <= 1
|
||||
9 | data.nunique(dropna=False) <= 1
|
||||
|
|
||||
|
||||
PD101.py:8:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
6 | # PD101
|
||||
7 | data.nunique() <= 1
|
||||
8 | data.nunique(dropna=True) <= 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
9 | data.nunique(dropna=False) <= 1
|
||||
10 | data.nunique() == 1
|
||||
|
|
||||
|
||||
PD101.py:9:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
7 | data.nunique() <= 1
|
||||
8 | data.nunique(dropna=True) <= 1
|
||||
9 | data.nunique(dropna=False) <= 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
10 | data.nunique() == 1
|
||||
11 | data.nunique(dropna=True) == 1
|
||||
|
|
||||
|
||||
PD101.py:10:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
8 | data.nunique(dropna=True) <= 1
|
||||
9 | data.nunique(dropna=False) <= 1
|
||||
10 | data.nunique() == 1
|
||||
| ^^^^^^^^^^^^^^^^^^^ PD101
|
||||
11 | data.nunique(dropna=True) == 1
|
||||
12 | data.nunique(dropna=False) == 1
|
||||
|
|
||||
|
||||
PD101.py:11:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
9 | data.nunique(dropna=False) <= 1
|
||||
10 | data.nunique() == 1
|
||||
11 | data.nunique(dropna=True) == 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
12 | data.nunique(dropna=False) == 1
|
||||
13 | data.nunique() != 1
|
||||
|
|
||||
|
||||
PD101.py:12:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
10 | data.nunique() == 1
|
||||
11 | data.nunique(dropna=True) == 1
|
||||
12 | data.nunique(dropna=False) == 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
13 | data.nunique() != 1
|
||||
14 | data.nunique(dropna=True) != 1
|
||||
|
|
||||
|
||||
PD101.py:13:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
11 | data.nunique(dropna=True) == 1
|
||||
12 | data.nunique(dropna=False) == 1
|
||||
13 | data.nunique() != 1
|
||||
| ^^^^^^^^^^^^^^^^^^^ PD101
|
||||
14 | data.nunique(dropna=True) != 1
|
||||
15 | data.nunique(dropna=False) != 1
|
||||
|
|
||||
|
||||
PD101.py:14:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
12 | data.nunique(dropna=False) == 1
|
||||
13 | data.nunique() != 1
|
||||
14 | data.nunique(dropna=True) != 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
15 | data.nunique(dropna=False) != 1
|
||||
16 | data.nunique() > 1
|
||||
|
|
||||
|
||||
PD101.py:15:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
13 | data.nunique() != 1
|
||||
14 | data.nunique(dropna=True) != 1
|
||||
15 | data.nunique(dropna=False) != 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
16 | data.nunique() > 1
|
||||
17 | data.dropna().nunique() == 1
|
||||
|
|
||||
|
||||
PD101.py:16:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
14 | data.nunique(dropna=True) != 1
|
||||
15 | data.nunique(dropna=False) != 1
|
||||
16 | data.nunique() > 1
|
||||
| ^^^^^^^^^^^^^^^^^^ PD101
|
||||
17 | data.dropna().nunique() == 1
|
||||
18 | data[data.notnull()].nunique() == 1
|
||||
|
|
||||
|
||||
PD101.py:17:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
15 | data.nunique(dropna=False) != 1
|
||||
16 | data.nunique() > 1
|
||||
17 | data.dropna().nunique() == 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
18 | data[data.notnull()].nunique() == 1
|
||||
|
|
||||
|
||||
PD101.py:18:1: PD101 Using `series.nunique()` for checking that a series is constant is inefficient
|
||||
|
|
||||
16 | data.nunique() > 1
|
||||
17 | data.dropna().nunique() == 1
|
||||
18 | data[data.notnull()].nunique() == 1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PD101
|
||||
19 |
|
||||
20 | # No violation of this rule
|
||||
|
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Expr, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -53,7 +53,6 @@ impl Violation for MixedCaseVariableInClassScope {
|
||||
pub(crate) fn mixed_case_variable_in_class_scope(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
) {
|
||||
@@ -66,15 +65,22 @@ pub(crate) fn mixed_case_variable_in_class_scope(
|
||||
{
|
||||
return;
|
||||
}
|
||||
if helpers::is_mixed_case(name)
|
||||
&& !helpers::is_named_tuple_assignment(stmt, checker.semantic())
|
||||
&& !helpers::is_typed_dict_class(bases, checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
MixedCaseVariableInClassScope {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
if !helpers::is_mixed_case(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().stmt();
|
||||
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|
||||
|| helpers::is_typed_dict_class(bases, checker.semantic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
MixedCaseVariableInClassScope {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Expr, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -60,12 +60,7 @@ impl Violation for MixedCaseVariableInGlobalScope {
|
||||
}
|
||||
|
||||
/// N816
|
||||
pub(crate) fn mixed_case_variable_in_global_scope(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
) {
|
||||
pub(crate) fn mixed_case_variable_in_global_scope(checker: &mut Checker, expr: &Expr, name: &str) {
|
||||
if checker
|
||||
.settings
|
||||
.pep8_naming
|
||||
@@ -75,13 +70,20 @@ pub(crate) fn mixed_case_variable_in_global_scope(
|
||||
{
|
||||
return;
|
||||
}
|
||||
if helpers::is_mixed_case(name) && !helpers::is_named_tuple_assignment(stmt, checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
MixedCaseVariableInGlobalScope {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
|
||||
if !helpers::is_mixed_case(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().stmt();
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
MixedCaseVariableInGlobalScope {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Expr, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -50,12 +50,7 @@ impl Violation for NonLowercaseVariableInFunction {
|
||||
}
|
||||
|
||||
/// N806
|
||||
pub(crate) fn non_lowercase_variable_in_function(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
) {
|
||||
pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &Expr, name: &str) {
|
||||
if checker
|
||||
.settings
|
||||
.pep8_naming
|
||||
@@ -66,16 +61,22 @@ pub(crate) fn non_lowercase_variable_in_function(
|
||||
return;
|
||||
}
|
||||
|
||||
if !str::is_lowercase(name)
|
||||
&& !helpers::is_named_tuple_assignment(stmt, checker.semantic())
|
||||
&& !helpers::is_typed_dict_assignment(stmt, checker.semantic())
|
||||
&& !helpers::is_type_var_assignment(stmt, checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonLowercaseVariableInFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
if str::is_lowercase(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().stmt();
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|
||||
|| helpers::is_typed_dict_assignment(parent, checker.semantic())
|
||||
|| helpers::is_type_var_assignment(parent, checker.semantic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonLowercaseVariableInFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::fmt;
|
||||
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast;
|
||||
use rustpython_parser::ast::Expr;
|
||||
use rustpython_parser::ast::Ranged;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -28,16 +28,16 @@ use crate::registry::AsRule;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// some_dict = {"a": 1, "b": 2}
|
||||
/// for _, val in some_dict.items():
|
||||
/// print(val)
|
||||
/// obj = {"a": 1, "b": 2}
|
||||
/// for key, value in obj.items():
|
||||
/// print(value)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// some_dict = {"a": 1, "b": 2}
|
||||
/// for val in some_dict.values():
|
||||
/// print(val)
|
||||
/// obj = {"a": 1, "b": 2}
|
||||
/// for value in obj.values():
|
||||
/// print(value)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct IncorrectDictIterator {
|
||||
@@ -79,8 +79,8 @@ pub(crate) fn incorrect_dict_iterator(checker: &mut Checker, target: &Expr, iter
|
||||
}
|
||||
|
||||
match (
|
||||
is_ignored_tuple_or_name(key, &checker.settings.dummy_variable_rgx),
|
||||
is_ignored_tuple_or_name(value, &checker.settings.dummy_variable_rgx),
|
||||
is_unused(key, checker.semantic()),
|
||||
is_unused(value, checker.semantic()),
|
||||
) {
|
||||
(true, true) => {
|
||||
// Both the key and the value are unused.
|
||||
@@ -142,13 +142,33 @@ impl fmt::Display for DictSubset {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is either an ignored value or a tuple of ignored values.
|
||||
fn is_ignored_tuple_or_name(expr: &Expr, dummy_variable_rgx: &Regex) -> bool {
|
||||
/// Returns `true` if the given expression is either an unused value or a tuple of unused values.
|
||||
fn is_unused(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
match expr {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts
|
||||
.iter()
|
||||
.all(|expr| is_ignored_tuple_or_name(expr, dummy_variable_rgx)),
|
||||
Expr::Name(ast::ExprName { id, .. }) => dummy_variable_rgx.is_match(id.as_str()),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().all(|expr| is_unused(expr, model)),
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
// Treat a variable as used if it has any usages, _or_ it's shadowed by another variable
|
||||
// with usages.
|
||||
//
|
||||
// If we don't respect shadowing, we'll incorrectly flag `bar` as unused in:
|
||||
// ```python
|
||||
// from random import random
|
||||
//
|
||||
// for bar in range(10):
|
||||
// if random() > 0.5:
|
||||
// break
|
||||
// else:
|
||||
// bar = 1
|
||||
//
|
||||
// print(bar)
|
||||
// ```
|
||||
let scope = model.scope();
|
||||
scope
|
||||
.get_all(id)
|
||||
.map(|binding_id| model.binding(binding_id))
|
||||
.filter(|binding| binding.range.start() >= expr.range().start())
|
||||
.all(|binding| !binding.is_used())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,167 +1,211 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/perflint/mod.rs
|
||||
---
|
||||
PERF102.py:3:17: PERF102 [*] When using only the values of a dict use the `values()` method
|
||||
PERF102.py:5:21: PERF102 [*] When using only the values of a dict use the `values()` method
|
||||
|
|
||||
1 | some_dict = {"a": 12, "b": 32, "c": 44}
|
||||
2 |
|
||||
3 | for _, value in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
4 | print(value)
|
||||
4 | def f():
|
||||
5 | for _, value in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
6 | print(value)
|
||||
|
|
||||
= help: Replace `.items()` with `.values()`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | some_dict = {"a": 12, "b": 32, "c": 44}
|
||||
2 2 |
|
||||
3 |-for _, value in some_dict.items(): # PERF102
|
||||
3 |+for value in some_dict.values(): # PERF102
|
||||
4 4 | print(value)
|
||||
5 5 |
|
||||
6 6 |
|
||||
3 3 |
|
||||
4 4 | def f():
|
||||
5 |- for _, value in some_dict.items(): # PERF102
|
||||
5 |+ for value in some_dict.values(): # PERF102
|
||||
6 6 | print(value)
|
||||
7 7 |
|
||||
8 8 |
|
||||
|
||||
PERF102.py:7:15: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
7 | for key, _ in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
8 | print(key)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
4 4 | print(value)
|
||||
5 5 |
|
||||
6 6 |
|
||||
7 |-for key, _ in some_dict.items(): # PERF102
|
||||
7 |+for key in some_dict.keys(): # PERF102
|
||||
8 8 | print(key)
|
||||
9 9 |
|
||||
10 10 |
|
||||
|
||||
PERF102.py:11:26: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
PERF102.py:10:19: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
11 | for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
12 | print(weird_arg_name)
|
||||
9 | def f():
|
||||
10 | for key, _ in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
11 | print(key)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | print(key)
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 |-for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
11 |+for weird_arg_name in some_dict.keys(): # PERF102
|
||||
12 12 | print(weird_arg_name)
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 9 | def f():
|
||||
10 |- for key, _ in some_dict.items(): # PERF102
|
||||
10 |+ for key in some_dict.keys(): # PERF102
|
||||
11 11 | print(key)
|
||||
12 12 |
|
||||
13 13 |
|
||||
14 14 |
|
||||
|
||||
PERF102.py:15:21: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
PERF102.py:15:30: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
15 | for name, (_, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
16 | pass
|
||||
14 | def f():
|
||||
15 | for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
16 | print(weird_arg_name)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
12 12 | print(weird_arg_name)
|
||||
12 12 |
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 |-for name, (_, _) in some_dict.items(): # PERF102
|
||||
15 |+for name in some_dict.keys(): # PERF102
|
||||
16 16 | pass
|
||||
14 14 | def f():
|
||||
15 |- for weird_arg_name, _ in some_dict.items(): # PERF102
|
||||
15 |+ for weird_arg_name in some_dict.keys(): # PERF102
|
||||
16 16 | print(weird_arg_name)
|
||||
17 17 |
|
||||
18 18 |
|
||||
|
||||
PERF102.py:23:26: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
PERF102.py:20:25: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
23 | for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
24 | pass
|
||||
19 | def f():
|
||||
20 | for name, (_, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
21 | print(name)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
20 20 | pass
|
||||
21 21 |
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | def f():
|
||||
20 |- for name, (_, _) in some_dict.items(): # PERF102
|
||||
20 |+ for name in some_dict.keys(): # PERF102
|
||||
21 21 | print(name)
|
||||
22 22 |
|
||||
23 |-for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
23 |+for (key1, _) in some_dict.keys(): # PERF102
|
||||
24 24 | pass
|
||||
25 25 |
|
||||
26 26 |
|
||||
23 23 |
|
||||
|
||||
PERF102.py:27:32: PERF102 [*] When using only the values of a dict use the `values()` method
|
||||
PERF102.py:30:30: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
27 | for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
28 | pass
|
||||
29 | def f():
|
||||
30 | for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
31 | print(key1)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
27 27 |
|
||||
28 28 |
|
||||
29 29 | def f():
|
||||
30 |- for (key1, _), (_, _) in some_dict.items(): # PERF102
|
||||
30 |+ for (key1, _) in some_dict.keys(): # PERF102
|
||||
31 31 | print(key1)
|
||||
32 32 |
|
||||
33 33 |
|
||||
|
||||
PERF102.py:35:36: PERF102 [*] When using only the values of a dict use the `values()` method
|
||||
|
|
||||
34 | def f():
|
||||
35 | for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
36 | print(value)
|
||||
|
|
||||
= help: Replace `.items()` with `.values()`
|
||||
|
||||
ℹ Suggested fix
|
||||
24 24 | pass
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 |-for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
27 |+for (value, _) in some_dict.values(): # PERF102
|
||||
28 28 | pass
|
||||
29 29 |
|
||||
30 30 |
|
||||
|
||||
PERF102.py:39:28: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
39 | for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
40 | pass
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
36 36 | pass
|
||||
32 32 |
|
||||
33 33 |
|
||||
34 34 | def f():
|
||||
35 |- for (_, (_, _)), (value, _) in some_dict.items(): # PERF102
|
||||
35 |+ for (value, _) in some_dict.values(): # PERF102
|
||||
36 36 | print(value)
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 |-for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
39 |+for (_, key2) in some_dict.keys(): # PERF102
|
||||
40 40 | pass
|
||||
41 41 |
|
||||
42 42 |
|
||||
|
||||
PERF102.py:67:21: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
PERF102.py:50:32: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
67 | for name, (_, _) in (some_function()).items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ PERF102
|
||||
68 | pass
|
||||
49 | def f():
|
||||
50 | for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
51 | print(key2)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
64 64 | print(value)
|
||||
65 65 |
|
||||
66 66 |
|
||||
67 |-for name, (_, _) in (some_function()).items(): # PERF102
|
||||
67 |+for name in (some_function()).keys(): # PERF102
|
||||
68 68 | pass
|
||||
69 69 |
|
||||
70 70 | for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
47 47 |
|
||||
48 48 |
|
||||
49 49 | def f():
|
||||
50 |- for ((_, key2), (_, _)) in some_dict.items(): # PERF102
|
||||
50 |+ for (_, key2) in some_dict.keys(): # PERF102
|
||||
51 51 | print(key2)
|
||||
52 52 |
|
||||
53 53 |
|
||||
|
||||
PERF102.py:70:21: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
PERF102.py:85:25: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
68 | pass
|
||||
69 |
|
||||
70 | for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF102
|
||||
71 | pass
|
||||
84 | def f():
|
||||
85 | for name, (_, _) in (some_function()).items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ PERF102
|
||||
86 | print(name)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
67 67 | for name, (_, _) in (some_function()).items(): # PERF102
|
||||
68 68 | pass
|
||||
69 69 |
|
||||
70 |-for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
70 |+for name in (some_function().some_attribute).keys(): # PERF102
|
||||
71 71 | pass
|
||||
82 82 |
|
||||
83 83 |
|
||||
84 84 | def f():
|
||||
85 |- for name, (_, _) in (some_function()).items(): # PERF102
|
||||
85 |+ for name in (some_function()).keys(): # PERF102
|
||||
86 86 | print(name)
|
||||
87 87 |
|
||||
88 88 |
|
||||
|
||||
PERF102.py:90:25: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
89 | def f():
|
||||
90 | for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF102
|
||||
91 | print(name)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
87 87 |
|
||||
88 88 |
|
||||
89 89 | def f():
|
||||
90 |- for name, (_, _) in (some_function().some_attribute).items(): # PERF102
|
||||
90 |+ for name in (some_function().some_attribute).keys(): # PERF102
|
||||
91 91 | print(name)
|
||||
92 92 |
|
||||
93 93 |
|
||||
|
||||
PERF102.py:95:31: PERF102 [*] When using only the keys of a dict use the `keys()` method
|
||||
|
|
||||
94 | def f():
|
||||
95 | for name, unused_value in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
96 | print(name)
|
||||
|
|
||||
= help: Replace `.items()` with `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
92 92 |
|
||||
93 93 |
|
||||
94 94 | def f():
|
||||
95 |- for name, unused_value in some_dict.items(): # PERF102
|
||||
95 |+ for name in some_dict.keys(): # PERF102
|
||||
96 96 | print(name)
|
||||
97 97 |
|
||||
98 98 |
|
||||
|
||||
PERF102.py:100:31: PERF102 [*] When using only the values of a dict use the `values()` method
|
||||
|
|
||||
99 | def f():
|
||||
100 | for unused_name, value in some_dict.items(): # PERF102
|
||||
| ^^^^^^^^^^^^^^^ PERF102
|
||||
101 | print(value)
|
||||
|
|
||||
= help: Replace `.items()` with `.values()`
|
||||
|
||||
ℹ Suggested fix
|
||||
97 97 |
|
||||
98 98 |
|
||||
99 99 | def f():
|
||||
100 |- for unused_name, value in some_dict.items(): # PERF102
|
||||
100 |+ for value in some_dict.values(): # PERF102
|
||||
101 101 | print(value)
|
||||
|
||||
|
||||
|
||||
@@ -53,16 +53,13 @@ pub(super) fn is_overlong(
|
||||
task_tags: &[String],
|
||||
tab_size: TabSize,
|
||||
) -> Option<Overlong> {
|
||||
let mut start_offset = line.start();
|
||||
let mut width = LineWidth::new(tab_size);
|
||||
|
||||
for c in line.chars() {
|
||||
if width < limit {
|
||||
start_offset += c.text_len();
|
||||
}
|
||||
width = width.add_char(c);
|
||||
// Each character is between 1-4 bytes. If the number of bytes is smaller than the limit, it cannot be overlong.
|
||||
if line.len() < limit.get() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut width = LineWidth::new(tab_size);
|
||||
width = width.add_str(line.as_str());
|
||||
if width <= limit {
|
||||
return None;
|
||||
}
|
||||
@@ -91,6 +88,17 @@ pub(super) fn is_overlong(
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain the start offset of the part of the line that exceeds the limit
|
||||
let mut start_offset = line.start();
|
||||
let mut start_width = LineWidth::new(tab_size);
|
||||
for c in line.chars() {
|
||||
if start_width < limit {
|
||||
start_offset += c.text_len();
|
||||
start_width = start_width.add_char(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(Overlong {
|
||||
range: TextRange::new(start_offset, line.end()),
|
||||
width: width.get(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{Identifier, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -35,14 +35,11 @@ impl Violation for AmbiguousClassName {
|
||||
}
|
||||
|
||||
/// E742
|
||||
pub(crate) fn ambiguous_class_name<F>(name: &str, locate: F) -> Option<Diagnostic>
|
||||
where
|
||||
F: FnOnce() -> TextRange,
|
||||
{
|
||||
pub(crate) fn ambiguous_class_name(name: &Identifier) -> Option<Diagnostic> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Diagnostic::new(
|
||||
AmbiguousClassName(name.to_string()),
|
||||
locate(),
|
||||
name.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{Identifier, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -35,14 +35,11 @@ impl Violation for AmbiguousFunctionName {
|
||||
}
|
||||
|
||||
/// E743
|
||||
pub(crate) fn ambiguous_function_name<F>(name: &str, locate: F) -> Option<Diagnostic>
|
||||
where
|
||||
F: FnOnce() -> TextRange,
|
||||
{
|
||||
pub(crate) fn ambiguous_function_name(name: &Identifier) -> Option<Diagnostic> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Diagnostic::new(
|
||||
AmbiguousFunctionName(name.to_string()),
|
||||
locate(),
|
||||
name.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -225,6 +225,7 @@ fn function(
|
||||
decorator_list: vec![],
|
||||
returns: Some(Box::new(return_type)),
|
||||
type_comment: None,
|
||||
type_params: vec![],
|
||||
range: TextRange::default(),
|
||||
});
|
||||
return generator.stmt(&func);
|
||||
@@ -237,6 +238,7 @@ fn function(
|
||||
decorator_list: vec![],
|
||||
returns: None,
|
||||
type_comment: None,
|
||||
type_params: vec![],
|
||||
range: TextRange::default(),
|
||||
});
|
||||
generator.stmt(&func)
|
||||
|
||||
@@ -1187,19 +1187,22 @@ impl AlwaysAutofixableViolation for SectionNameEndsInColon {
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
#[violation]
|
||||
pub struct UndocumentedParam {
|
||||
pub names: Vec<String>,
|
||||
/// The name of the function being documented.
|
||||
definition: String,
|
||||
/// The names of the undocumented parameters.
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
impl Violation for UndocumentedParam {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UndocumentedParam { names } = self;
|
||||
let UndocumentedParam { definition, names } = self;
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
format!("Missing argument description in the docstring: `{name}`")
|
||||
format!("Missing argument description in the docstring for `{definition}`: `{name}`")
|
||||
} else {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("Missing argument descriptions in the docstring: {names}")
|
||||
format!("Missing argument descriptions in the docstring for `{definition}`: {names}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1779,11 +1782,16 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||
}
|
||||
|
||||
if !missing_arg_names.is_empty() {
|
||||
let names = missing_arg_names.into_iter().sorted().collect();
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UndocumentedParam { names },
|
||||
stmt.identifier(),
|
||||
));
|
||||
if let Some(definition) = docstring.definition.name() {
|
||||
let names = missing_arg_names.into_iter().sorted().collect();
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UndocumentedParam {
|
||||
definition: definition.to_string(),
|
||||
names,
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,32 @@ pub struct Options {
|
||||
)]
|
||||
/// Whether to use Google-style or NumPy-style conventions or the PEP257
|
||||
/// defaults when analyzing docstring sections.
|
||||
///
|
||||
/// Enabling a convention will force-disable any rules that are not
|
||||
/// included in the specified convention. As such, the intended use is
|
||||
/// to enable a convention and then selectively disable any additional
|
||||
/// rules on top of it.
|
||||
///
|
||||
/// For example, to use Google-style conventions but avoid requiring
|
||||
/// documentation for every function parameter:
|
||||
///
|
||||
/// ```toml
|
||||
/// [tool.ruff]
|
||||
/// # Enable all `pydocstyle` rules, limiting to those that adhere to the
|
||||
/// # Google convention via `convention = "google"`, below.
|
||||
/// select = ["D"]
|
||||
///
|
||||
/// # On top of the Google convention, disable `D417`, which requires
|
||||
/// # documentation for every function parameter.
|
||||
/// ignore = ["D417"]
|
||||
///
|
||||
/// [tool.ruff.pydocstyle]
|
||||
/// convention = "google"
|
||||
/// ```
|
||||
///
|
||||
/// As conventions force-disable all rules not included in the convention,
|
||||
/// enabling _additional_ rules on top of a convention is currently
|
||||
/// unsupported.
|
||||
pub convention: Option<Convention>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
sections.py:292:9: D417 Missing argument description in the docstring: `y`
|
||||
sections.py:292:9: D417 Missing argument description in the docstring for `bar`: `y`
|
||||
|
|
||||
290 | x = 1
|
||||
291 |
|
||||
@@ -10,7 +10,7 @@ sections.py:292:9: D417 Missing argument description in the docstring: `y`
|
||||
293 | """Nested function test for docstrings.
|
||||
|
|
||||
|
||||
sections.py:309:5: D417 Missing argument description in the docstring: `y`
|
||||
sections.py:309:5: D417 Missing argument description in the docstring for `test_missing_google_args`: `y`
|
||||
|
|
||||
307 | "(argument(s) y are missing descriptions in "
|
||||
308 | "'test_missing_google_args' docstring)")
|
||||
@@ -19,7 +19,7 @@ sections.py:309:5: D417 Missing argument description in the docstring: `y`
|
||||
310 | """Toggle the gizmo.
|
||||
|
|
||||
|
||||
sections.py:333:9: D417 Missing argument descriptions in the docstring: `test`, `y`, `z`
|
||||
sections.py:333:9: D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
|
||||
|
|
||||
331 | "(argument(s) test, y, z are missing descriptions in "
|
||||
332 | "'test_missing_args' docstring)", arg_count=5)
|
||||
@@ -28,7 +28,7 @@ sections.py:333:9: D417 Missing argument descriptions in the docstring: `test`,
|
||||
334 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:345:9: D417 Missing argument descriptions in the docstring: `test`, `y`, `z`
|
||||
sections.py:345:9: D417 Missing argument descriptions in the docstring for `test_missing_args_class_method`: `test`, `y`, `z`
|
||||
|
|
||||
343 | "(argument(s) test, y, z are missing descriptions in "
|
||||
344 | "'test_missing_args_class_method' docstring)", arg_count=5)
|
||||
@@ -37,7 +37,7 @@ sections.py:345:9: D417 Missing argument descriptions in the docstring: `test`,
|
||||
346 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:358:9: D417 Missing argument descriptions in the docstring: `a`, `y`, `z`
|
||||
sections.py:358:9: D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `y`, `z`
|
||||
|
|
||||
356 | "(argument(s) a, y, z are missing descriptions in "
|
||||
357 | "'test_missing_args_static_method' docstring)", arg_count=4)
|
||||
@@ -46,7 +46,7 @@ sections.py:358:9: D417 Missing argument descriptions in the docstring: `a`, `y`
|
||||
359 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:370:9: D417 Missing argument descriptions in the docstring: `a`, `b`
|
||||
sections.py:370:9: D417 Missing argument descriptions in the docstring for `test_missing_docstring`: `a`, `b`
|
||||
|
|
||||
368 | "(argument(s) a, b are missing descriptions in "
|
||||
369 | "'test_missing_docstring' docstring)", arg_count=2)
|
||||
@@ -55,7 +55,7 @@ sections.py:370:9: D417 Missing argument descriptions in the docstring: `a`, `b`
|
||||
371 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:398:5: D417 Missing argument description in the docstring: `y`
|
||||
sections.py:398:5: D417 Missing argument description in the docstring for `test_missing_numpy_args`: `y`
|
||||
|
|
||||
396 | "(argument(s) y are missing descriptions in "
|
||||
397 | "'test_missing_numpy_args' docstring)")
|
||||
@@ -64,7 +64,7 @@ sections.py:398:5: D417 Missing argument description in the docstring: `y`
|
||||
399 | """Toggle the gizmo.
|
||||
|
|
||||
|
||||
sections.py:434:9: D417 Missing argument descriptions in the docstring: `test`, `y`, `z`
|
||||
sections.py:434:9: D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
|
||||
|
|
||||
432 | "(argument(s) test, y, z are missing descriptions in "
|
||||
433 | "'test_missing_args' docstring)", arg_count=5)
|
||||
@@ -73,7 +73,7 @@ sections.py:434:9: D417 Missing argument descriptions in the docstring: `test`,
|
||||
435 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:449:9: D417 Missing argument descriptions in the docstring: `test`, `y`, `z`
|
||||
sections.py:449:9: D417 Missing argument descriptions in the docstring for `test_missing_args_class_method`: `test`, `y`, `z`
|
||||
|
|
||||
447 | "(argument(s) test, y, z are missing descriptions in "
|
||||
448 | "'test_missing_args_class_method' docstring)", arg_count=4)
|
||||
@@ -82,7 +82,7 @@ sections.py:449:9: D417 Missing argument descriptions in the docstring: `test`,
|
||||
450 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:468:9: D417 Missing argument descriptions in the docstring: `a`, `z`
|
||||
sections.py:468:9: D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `z`
|
||||
|
|
||||
466 | "(argument(s) a, z are missing descriptions in "
|
||||
467 | "'test_missing_args_static_method' docstring)", arg_count=3)
|
||||
@@ -91,7 +91,7 @@ sections.py:468:9: D417 Missing argument descriptions in the docstring: `a`, `z`
|
||||
469 | """Test a valid args section.
|
||||
|
|
||||
|
||||
sections.py:498:9: D417 Missing argument description in the docstring: `y`
|
||||
sections.py:498:9: D417 Missing argument description in the docstring for `test_incorrect_indent`: `y`
|
||||
|
|
||||
496 | "(argument(s) y are missing descriptions in "
|
||||
497 | "'test_incorrect_indent' docstring)", arg_count=3)
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D417.py:1:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
1 | def f(x, y, z):
|
||||
| ^ D417
|
||||
2 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:14:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
14 | def f(x, y, z):
|
||||
| ^ D417
|
||||
15 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:27:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
27 | def f(x, y, z):
|
||||
| ^ D417
|
||||
28 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:39:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
39 | def f(x, y, z):
|
||||
| ^ D417
|
||||
40 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:52:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
52 | def f(x, y, z):
|
||||
| ^ D417
|
||||
53 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:65:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
65 | def f(x, y, z):
|
||||
| ^ D417
|
||||
66 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:77:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
77 | def f(x, y, z):
|
||||
| ^ D417
|
||||
78 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:98:5: D417 Missing argument description in the docstring: `x`
|
||||
D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x`
|
||||
|
|
||||
98 | def f(x, *args, **kwargs):
|
||||
| ^ D417
|
||||
99 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:108:5: D417 Missing argument description in the docstring: `*args`
|
||||
D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*args`
|
||||
|
|
||||
108 | def f(x, *args, **kwargs):
|
||||
| ^ D417
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D417.py:1:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
1 | def f(x, y, z):
|
||||
| ^ D417
|
||||
2 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:14:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
14 | def f(x, y, z):
|
||||
| ^ D417
|
||||
15 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:27:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
27 | def f(x, y, z):
|
||||
| ^ D417
|
||||
28 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:39:5: D417 Missing argument descriptions in the docstring: `y`, `z`
|
||||
D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
||||
|
|
||||
39 | def f(x, y, z):
|
||||
| ^ D417
|
||||
40 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:52:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
52 | def f(x, y, z):
|
||||
| ^ D417
|
||||
53 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:65:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
65 | def f(x, y, z):
|
||||
| ^ D417
|
||||
66 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:77:5: D417 Missing argument description in the docstring: `y`
|
||||
D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y`
|
||||
|
|
||||
77 | def f(x, y, z):
|
||||
| ^ D417
|
||||
78 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:98:5: D417 Missing argument description in the docstring: `x`
|
||||
D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x`
|
||||
|
|
||||
98 | def f(x, *args, **kwargs):
|
||||
| ^ D417
|
||||
99 | """Do something.
|
||||
|
|
||||
|
||||
D417.py:108:5: D417 Missing argument description in the docstring: `*args`
|
||||
D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*args`
|
||||
|
|
||||
108 | def f(x, *args, **kwargs):
|
||||
| ^ D417
|
||||
|
||||
@@ -33,7 +33,6 @@ pub(crate) fn break_outside_loop<'a>(
|
||||
stmt: &'a Stmt,
|
||||
parents: &mut impl Iterator<Item = &'a Stmt>,
|
||||
) -> Option<Diagnostic> {
|
||||
let mut allowed: bool = false;
|
||||
let mut child = stmt;
|
||||
for parent in parents {
|
||||
match parent {
|
||||
@@ -41,8 +40,7 @@ pub(crate) fn break_outside_loop<'a>(
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { orelse, .. }) => {
|
||||
if !orelse.contains(child) {
|
||||
allowed = true;
|
||||
break;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
@@ -53,9 +51,5 @@ pub(crate) fn break_outside_loop<'a>(
|
||||
child = parent;
|
||||
}
|
||||
|
||||
if allowed {
|
||||
None
|
||||
} else {
|
||||
Some(Diagnostic::new(BreakOutsideLoop, stmt.range()))
|
||||
}
|
||||
Some(Diagnostic::new(BreakOutsideLoop, stmt.range()))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ pub(crate) fn continue_outside_loop<'a>(
|
||||
stmt: &'a Stmt,
|
||||
parents: &mut impl Iterator<Item = &'a Stmt>,
|
||||
) -> Option<Diagnostic> {
|
||||
let mut allowed: bool = false;
|
||||
let mut child = stmt;
|
||||
for parent in parents {
|
||||
match parent {
|
||||
@@ -41,8 +40,7 @@ pub(crate) fn continue_outside_loop<'a>(
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { orelse, .. }) => {
|
||||
if !orelse.contains(child) {
|
||||
allowed = true;
|
||||
break;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
@@ -53,9 +51,5 @@ pub(crate) fn continue_outside_loop<'a>(
|
||||
child = parent;
|
||||
}
|
||||
|
||||
if allowed {
|
||||
None
|
||||
} else {
|
||||
Some(Diagnostic::new(ContinueOutsideLoop, stmt.range()))
|
||||
}
|
||||
Some(Diagnostic::new(ContinueOutsideLoop, stmt.range()))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use rustpython_parser::ast::Ranged;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
@@ -55,21 +53,14 @@ impl Violation for GlobalStatement {
|
||||
|
||||
/// PLW0603
|
||||
pub(crate) fn global_statement(checker: &mut Checker, name: &str) {
|
||||
let scope = checker.semantic().scope();
|
||||
if let Some(binding_id) = scope.get(name) {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
if binding.is_global() {
|
||||
if let Some(source) = binding.source {
|
||||
let source = checker.semantic().stmts[source];
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
GlobalStatement {
|
||||
name: name.to_string(),
|
||||
},
|
||||
// Match Pylint's behavior by reporting on the `global` statement`, rather
|
||||
// than the variable usage.
|
||||
source.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(range) = checker.semantic().global(name) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
GlobalStatement {
|
||||
name: name.to_string(),
|
||||
},
|
||||
// Match Pylint's behavior by reporting on the `global` statement`, rather
|
||||
// than the variable usage.
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool {
|
||||
Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"),
|
||||
Constant::Int(value) => !matches!(value.try_into(), Ok(0 | 1)),
|
||||
Constant::Bytes(_) => true,
|
||||
Constant::Tuple(_) => true,
|
||||
Constant::Float(_) => true,
|
||||
Constant::Complex { .. } => true,
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ pub(crate) fn nested_min_max(
|
||||
}) {
|
||||
let mut diagnostic = Diagnostic::new(NestedMinMax { func: min_max }, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if !has_comments(expr, checker.locator) {
|
||||
if !has_comments(expr, checker.locator, checker.indexer) {
|
||||
let flattened_expr = Expr::Call(ast::ExprCall {
|
||||
func: Box::new(func.clone()),
|
||||
args: collect_nested_args(min_max, args, checker.semantic()),
|
||||
|
||||
@@ -28,7 +28,6 @@ impl TryFrom<&Constant> for ConstantType {
|
||||
Constant::Float(..) => Ok(Self::Float),
|
||||
Constant::Int(..) => Ok(Self::Int),
|
||||
Constant::Str(..) => Ok(Self::Str),
|
||||
Constant::Tuple(..) => Ok(Self::Tuple),
|
||||
Constant::Bool(..) | Constant::Ellipsis | Constant::None => {
|
||||
Err(anyhow!("Singleton constants are unsupported"))
|
||||
}
|
||||
|
||||
@@ -89,12 +89,13 @@ global_statement.py:80:5: PLW0603 Using the global statement to update `CONSTANT
|
||||
82 | CONSTANT = 2
|
||||
|
|
||||
|
||||
global_statement.py:81:5: PLW0603 Using the global statement to update `CONSTANT` is discouraged
|
||||
global_statement.py:80:5: PLW0603 Using the global statement to update `CONSTANT` is discouraged
|
||||
|
|
||||
78 | def multiple_assignment():
|
||||
79 | """Should warn on every assignment."""
|
||||
80 | global CONSTANT # [global-statement]
|
||||
| ^^^^^^^^^^^^^^^ PLW0603
|
||||
81 | CONSTANT = 1
|
||||
| ^^^^^^^^^^^^ PLW0603
|
||||
82 | CONSTANT = 2
|
||||
|
|
||||
|
||||
|
||||
@@ -93,11 +93,7 @@ fn match_named_tuple_assign<'a>(
|
||||
|
||||
/// Generate a `Stmt::AnnAssign` representing the provided property
|
||||
/// definition.
|
||||
fn create_property_assignment_stmt(
|
||||
property: &str,
|
||||
annotation: &Expr,
|
||||
value: Option<&Expr>,
|
||||
) -> Stmt {
|
||||
fn create_property_assignment_stmt(property: &str, annotation: &Expr) -> Stmt {
|
||||
ast::StmtAnnAssign {
|
||||
target: Box::new(
|
||||
ast::ExprName {
|
||||
@@ -108,40 +104,15 @@ fn create_property_assignment_stmt(
|
||||
.into(),
|
||||
),
|
||||
annotation: Box::new(annotation.clone()),
|
||||
value: value.map(|value| Box::new(value.clone())),
|
||||
value: None,
|
||||
simple: true,
|
||||
range: TextRange::default(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Match the `defaults` keyword in a `NamedTuple(...)` call.
|
||||
fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
|
||||
let defaults = keywords.iter().find(|keyword| {
|
||||
if let Some(arg) = &keyword.arg {
|
||||
arg == "defaults"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
match defaults {
|
||||
Some(defaults) => match &defaults.value {
|
||||
Expr::List(ast::ExprList { elts, .. }) => Ok(elts),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => Ok(elts),
|
||||
_ => bail!("Expected defaults to be `Expr::List` | `Expr::Tuple`"),
|
||||
},
|
||||
None => Ok(&[]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a list of property assignments from the `NamedTuple` arguments.
|
||||
fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
let Some(fields) = args.get(1) else {
|
||||
let node = Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
});
|
||||
return Ok(vec![node]);
|
||||
};
|
||||
/// Create a list of property assignments from the `NamedTuple` fields argument.
|
||||
fn create_properties_from_fields_arg(fields: &Expr) -> Result<Vec<Stmt>> {
|
||||
let Expr::List(ast::ExprList { elts, .. }) = &fields else {
|
||||
bail!("Expected argument to be `Expr::List`");
|
||||
};
|
||||
@@ -151,16 +122,8 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||
});
|
||||
return Ok(vec![node]);
|
||||
}
|
||||
let padded_defaults = if elts.len() >= defaults.len() {
|
||||
std::iter::repeat(None)
|
||||
.take(elts.len() - defaults.len())
|
||||
.chain(defaults.iter().map(Some))
|
||||
} else {
|
||||
bail!("Defaults must be `None` or an iterable of at least the number of fields")
|
||||
};
|
||||
elts.iter()
|
||||
.zip(padded_defaults)
|
||||
.map(|(field, default)| {
|
||||
.map(|field| {
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &field else {
|
||||
bail!("Expected `field` to be `Expr::Tuple`")
|
||||
};
|
||||
@@ -180,9 +143,21 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||
if is_dunder(property) {
|
||||
bail!("Cannot use dunder property name: {}", property)
|
||||
}
|
||||
Ok(create_property_assignment_stmt(
|
||||
property, annotation, default,
|
||||
))
|
||||
Ok(create_property_assignment_stmt(property, annotation))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Create a list of property assignments from the `NamedTuple` keyword arguments.
|
||||
fn create_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
keywords
|
||||
.iter()
|
||||
.map(|keyword| {
|
||||
let Keyword { arg, value, .. } = keyword;
|
||||
let Some(arg) = arg else {
|
||||
bail!("Expected `keyword` to have an `arg`")
|
||||
};
|
||||
Ok(create_property_assignment_stmt(arg.as_str(), value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -196,6 +171,7 @@ fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) ->
|
||||
keywords: vec![],
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
type_params: vec![],
|
||||
range: TextRange::default(),
|
||||
}
|
||||
.into()
|
||||
@@ -228,12 +204,32 @@ pub(crate) fn convert_named_tuple_functional_to_class(
|
||||
return;
|
||||
};
|
||||
|
||||
let properties = match match_defaults(keywords)
|
||||
.and_then(|defaults| create_properties_from_args(args, defaults))
|
||||
{
|
||||
Ok(properties) => properties,
|
||||
Err(err) => {
|
||||
debug!("Skipping `NamedTuple` \"{typename}\": {err}");
|
||||
let properties = match (&args[1..], keywords) {
|
||||
// Ex) NamedTuple("MyType")
|
||||
([], []) => vec![Stmt::Pass(ast::StmtPass {
|
||||
range: TextRange::default(),
|
||||
})],
|
||||
// Ex) NamedTuple("MyType", [("a", int), ("b", str)])
|
||||
([fields], []) => {
|
||||
if let Ok(properties) = create_properties_from_fields_arg(fields) {
|
||||
properties
|
||||
} else {
|
||||
debug!("Skipping `NamedTuple` \"{typename}\": unable to parse fields");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Ex) NamedTuple("MyType", a=int, b=str)
|
||||
([], keywords) => {
|
||||
if let Ok(properties) = create_properties_from_keywords(keywords) {
|
||||
properties
|
||||
} else {
|
||||
debug!("Skipping `NamedTuple` \"{typename}\": unable to parse keywords");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Unfixable
|
||||
_ => {
|
||||
debug!("Skipping `NamedTuple` \"{typename}\": mixed fields and keywords");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,6 +128,7 @@ fn create_class_def_stmt(
|
||||
keywords,
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
type_params: vec![],
|
||||
range: TextRange::default(),
|
||||
}
|
||||
.into()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user