Compare commits
2 Commits
v0.3.7
...
cjm/colors
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab9b38c0f1 | ||
|
|
af12ee1763 |
@@ -17,4 +17,4 @@ MD013: false
|
||||
# MD024/no-duplicate-heading
|
||||
MD024:
|
||||
# Allow when nested under different parents e.g. CHANGELOG.md
|
||||
siblings_only: true
|
||||
allow_different_nesting: true
|
||||
|
||||
@@ -13,7 +13,7 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.16
|
||||
rev: v0.15
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
@@ -31,7 +31,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.39.0
|
||||
rev: v0.37.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: |
|
||||
@@ -41,7 +41,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.20.4
|
||||
rev: v1.16.22
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -55,7 +55,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.3.5
|
||||
rev: v0.1.4
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -70,7 +70,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.1.0
|
||||
rev: v3.0.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,63 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.7
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Implement `loop-iterator-mutation` (`B909`) ([#9578](https://github.com/astral-sh/ruff/pull/9578))
|
||||
- \[`pylint`\] Implement rule to prefer augmented assignment (`PLR6104`) ([#9932](https://github.com/astral-sh/ruff/pull/9932))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid TOCTOU errors in cache initialization ([#10884](https://github.com/astral-sh/ruff/pull/10884))
|
||||
- \[`pylint`\] Recode `nan-comparison` rule to `W0177` ([#10894](https://github.com/astral-sh/ruff/pull/10894))
|
||||
- \[`pylint`\] Reverse min-max logic in `if-stmt-min-max` ([#10890](https://github.com/astral-sh/ruff/pull/10890))
|
||||
|
||||
## 0.3.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pylint`\] Implement `bad-staticmethod-argument` (`PLW0211`) ([#10781](https://github.com/astral-sh/ruff/pull/10781))
|
||||
- \[`pylint`\] Implement `if-stmt-min-max` (`PLR1730`, `PLR1731`) ([#10002](https://github.com/astral-sh/ruff/pull/10002))
|
||||
- \[`pyupgrade`\] Replace `str,Enum` multiple inheritance with `StrEnum` `UP042` ([#10713](https://github.com/astral-sh/ruff/pull/10713))
|
||||
- \[`refurb`\] Implement `if-expr-instead-of-or-operator` (`FURB110`) ([#10687](https://github.com/astral-sh/ruff/pull/10687))
|
||||
- \[`refurb`\] Implement `int-on-sliced-str` (`FURB166`) ([#10650](https://github.com/astral-sh/ruff/pull/10650))
|
||||
- \[`refurb`\] Implement `write-whole-file` (`FURB103`) ([#10802](https://github.com/astral-sh/ruff/pull/10802))
|
||||
- \[`refurb`\] Support `itemgetter` in `reimplemented-operator` (`FURB118`) ([#10526](https://github.com/astral-sh/ruff/pull/10526))
|
||||
- \[`flake8_comprehensions`\] Add `sum`/`min`/`max` to unnecessary comprehension check (`C419`) ([#10759](https://github.com/astral-sh/ruff/pull/10759))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pydocstyle`\] Require capitalizing docstrings where the first sentence is a single word (`D403`) ([#10776](https://github.com/astral-sh/ruff/pull/10776))
|
||||
- \[`pycodestyle`\] Ignore annotated lambdas in class scopes (`E731`) ([#10720](https://github.com/astral-sh/ruff/pull/10720))
|
||||
- \[`flake8-pyi`\] Various improvements to PYI034 ([#10807](https://github.com/astral-sh/ruff/pull/10807))
|
||||
- \[`flake8-slots`\] Flag subclasses of call-based `typing.NamedTuple`s as well as subclasses of `collections.namedtuple()` (`SLOT002`) ([#10808](https://github.com/astral-sh/ruff/pull/10808))
|
||||
- \[`pyflakes`\] Allow forward references in class bases in stub files (`F821`) ([#10779](https://github.com/astral-sh/ruff/pull/10779))
|
||||
- \[`pygrep-hooks`\] Improve `blanket-noqa` error message (`PGH004`) ([#10851](https://github.com/astral-sh/ruff/pull/10851))
|
||||
|
||||
### CLI
|
||||
|
||||
- Support `FORCE_COLOR` env var ([#10839](https://github.com/astral-sh/ruff/pull/10839))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Support negated patterns in `[extend-]per-file-ignores` ([#10852](https://github.com/astral-sh/ruff/pull/10852))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-import-conventions`\] Accept non-aliased (but correct) import in `unconventional-import-alias` (`ICN001`) ([#10729](https://github.com/astral-sh/ruff/pull/10729))
|
||||
- \[`flake8-quotes`\] Add semantic model flag when inside f-string replacement field ([#10766](https://github.com/astral-sh/ruff/pull/10766))
|
||||
- \[`pep8-naming`\] Recursively resolve `TypeDicts` for N815 violations ([#10719](https://github.com/astral-sh/ruff/pull/10719))
|
||||
- \[`flake8-quotes`\] Respect `Q00*` ignores in `flake8-quotes` rules ([#10728](https://github.com/astral-sh/ruff/pull/10728))
|
||||
- \[`flake8-simplify`\] Show negated condition in `needless-bool` diagnostics (`SIM103`) ([#10854](https://github.com/astral-sh/ruff/pull/10854))
|
||||
- \[`ruff`\] Use within-scope shadowed bindings in `asyncio-dangling-task` (`RUF006`) ([#10793](https://github.com/astral-sh/ruff/pull/10793))
|
||||
- \[`flake8-pytest-style`\] Fix single-tuple conversion in `pytest-parametrize-values-wrong-type` (`PT007`) ([#10862](https://github.com/astral-sh/ruff/pull/10862))
|
||||
- \[`flake8-return`\] Ignore assignments to annotated variables in `unnecessary-assign` (`RET504`) ([#10741](https://github.com/astral-sh/ruff/pull/10741))
|
||||
- \[`refurb`\] Do not allow any keyword arguments for `read-whole-file` in `rb` mode (`FURB101`) ([#10803](https://github.com/astral-sh/ruff/pull/10803))
|
||||
- \[`pylint`\] Don't recommend decorating staticmethods with `@singledispatch` (`PLE1519`, `PLE1520`) ([#10637](https://github.com/astral-sh/ruff/pull/10637))
|
||||
- \[`pydocstyle`\] Use section name range for all section-related docstring diagnostics ([#10740](https://github.com/astral-sh/ruff/pull/10740))
|
||||
- Respect `# noqa` directives on `__all__` openers ([#10798](https://github.com/astral-sh/ruff/pull/10798))
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Preview features
|
||||
|
||||
116
Cargo.lock
generated
116
Cargo.lock
generated
@@ -244,12 +244,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chic"
|
||||
version = "1.2.2"
|
||||
@@ -371,7 +365,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -382,9 +376,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
version = "3.0.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c"
|
||||
checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"terminfo",
|
||||
@@ -602,7 +596,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -613,7 +607,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1114,7 +1108,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1277,9 +1271,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.3.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f1e25d1b119ab5c2f15a6e081bb94a8d547c5c2ad065f5fd0dbb683f31ced91"
|
||||
checksum = "890ee958b936e712c6f1c184f208176973e73c2e4f8d3cf499f94eb112f647f9"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"libcst_derive",
|
||||
@@ -1292,12 +1286,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.3.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5011f2d59093de14a4a90e01b9d85dee9276e58a25f0107dcee167dd601be0"
|
||||
checksum = "1dbd2f3cd9346422ebdc3a614aed6969d4e0b3e9c10517f33b30326acf894c11"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1444,14 +1438,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
name = "nextest-workspace-hack"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1561,6 +1560,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@@ -1758,7 +1763,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1813,12 +1818,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.6"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1a341ae463320e9f8f34adda49c8a85d81d4e8f34cce4397fb0350481552224"
|
||||
checksum = "1b9599bffc2cd7511355996e0cfd979266b2cfa3f3ff5247d07a3a6e1ded6158"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap",
|
||||
"nextest-workspace-hack",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
@@ -1975,7 +1981,7 @@ dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1995,8 +2001,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.7"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"bincode",
|
||||
@@ -2006,7 +2013,6 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"filetime",
|
||||
"ignore",
|
||||
"insta",
|
||||
@@ -2017,6 +2023,7 @@ dependencies = [
|
||||
"mimalloc",
|
||||
"notify",
|
||||
"num_cpus",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
@@ -2157,15 +2164,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.7"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
"anstream",
|
||||
"anyhow",
|
||||
"bitflags 2.5.0",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -2179,6 +2186,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"natord",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs 0.5.0",
|
||||
@@ -2225,7 +2233,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2429,7 +2437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.3.7"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2493,7 +2501,6 @@ name = "ruff_workspace"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored",
|
||||
"dirs 5.0.1",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -2501,6 +2508,7 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"owo-colors",
|
||||
"path-absolutize",
|
||||
"pep440_rs 0.5.0",
|
||||
"regex",
|
||||
@@ -2671,7 +2679,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2704,7 +2712,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2745,7 +2753,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2855,7 +2863,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2877,9 +2885,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
version = "2.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2950,7 +2958,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2961,7 +2969,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2982,7 +2990,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3103,7 +3111,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3339,7 +3347,7 @@ checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3424,7 +3432,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3458,7 +3466,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3491,7 +3499,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3515,14 +3523,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "6.0.1"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3715,12 +3723,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
@@ -3753,7 +3755,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -14,6 +14,7 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anstream = { version = "0.6.13" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
bincode = { version = "1.3.3" }
|
||||
@@ -23,9 +24,8 @@ cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "3.0.0" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
@@ -65,6 +65,7 @@ natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
num_cpus = { version = "1.16.0" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
owo-colors = { version = "4.0.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.5.0", features = ["serde"] }
|
||||
|
||||
@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.3.7
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
|
||||
|
||||
[default.extend-words]
|
||||
"arange" = "arange" # e.g. `numpy.arange`
|
||||
hel = "hel"
|
||||
whos = "whos"
|
||||
spawnve = "spawnve"
|
||||
ned = "ned"
|
||||
pn = "pn" # `import panel as pd` is a thing
|
||||
poit = "poit"
|
||||
BA = "BA" # acronym for "Bad Allowed", used in testing.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.3.7"
|
||||
version = "0.3.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -25,6 +25,7 @@ ruff_source_file = { path = "../ruff_source_file" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_workspace = { path = "../ruff_workspace" }
|
||||
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
@@ -34,7 +35,6 @@ chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
clearscreen = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
@@ -42,6 +42,7 @@ itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
num_cpus = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -62,8 +63,6 @@ wild = { workspace = true }
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use anyhow::bail;
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -1053,7 +1053,6 @@ pub enum FormatRangeParseError {
|
||||
|
||||
impl std::fmt::Display for FormatRangeParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let tip = " tip:".bold().green();
|
||||
match self {
|
||||
FormatRangeParseError::StartGreaterThanEnd(start, end) => {
|
||||
write!(
|
||||
@@ -1062,7 +1061,8 @@ impl std::fmt::Display for FormatRangeParseError {
|
||||
start_invalid=start.to_string().bold().yellow(),
|
||||
end_invalid=end.to_string().bold().yellow(),
|
||||
start=start.to_string().green().bold(),
|
||||
end=end.to_string().green().bold()
|
||||
end=end.to_string().green().bold(),
|
||||
tip=" tip:".bold().green()
|
||||
)
|
||||
}
|
||||
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
|
||||
@@ -1148,7 +1148,8 @@ pub enum LineColumnParseError {
|
||||
|
||||
impl LineColumnParseError {
|
||||
fn write(&self, f: &mut std::fmt::Formatter, start_range: bool) -> std::fmt::Result {
|
||||
let tip = "tip:".bold().green();
|
||||
let tip = "tip:".bold();
|
||||
let tip = tip.green();
|
||||
|
||||
let range = if start_range { "start" } else { "end" };
|
||||
|
||||
|
||||
@@ -375,17 +375,15 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
fs::create_dir_all(path.join(VERSION))?;
|
||||
|
||||
// Add the CACHEDIR.TAG.
|
||||
cachedir::ensure_tag(path)?;
|
||||
if !cachedir::is_tagged(path)? {
|
||||
cachedir::add_tag(path)?;
|
||||
}
|
||||
|
||||
// Add the .gitignore.
|
||||
match fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path.join(".gitignore"))
|
||||
{
|
||||
Ok(mut file) => file.write_all(b"# Automatically created by ruff.\n*\n")?,
|
||||
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
|
||||
Err(err) => return Err(err.into()),
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
let mut file = fs::File::create(gitignore_path)?;
|
||||
file.write_all(b"# Automatically created by ruff.\n*\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use ignore::Error;
|
||||
use log::{debug, error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -226,6 +226,7 @@ mod test {
|
||||
use rustc_hash::FxHashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
@@ -280,7 +281,7 @@ mod test {
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
let mut output = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
@@ -291,7 +292,7 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let messages = String::from_utf8(output).unwrap();
|
||||
let messages = String::from_utf8(output.into_inner()).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
omit_expression => true,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fs::remove_dir_all;
|
||||
use std::io::{self, BufWriter, Write};
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use path_absolutize::path_dedot;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rustc_hash::FxHashSet;
|
||||
@@ -16,6 +16,7 @@ use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
use ruff_diagnostics::SourceMap;
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::logging::{DisplayParseError, LogLevel};
|
||||
use ruff_linter::registry::Rule;
|
||||
@@ -189,10 +190,10 @@ pub(crate) fn format(
|
||||
match mode {
|
||||
FormatMode::Write => {}
|
||||
FormatMode::Check => {
|
||||
results.write_changed(&mut stdout().lock())?;
|
||||
results.write_changed(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
FormatMode::Diff => {
|
||||
results.write_diff(&mut stdout().lock())?;
|
||||
results.write_diff(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,9 +201,9 @@ pub(crate) fn format(
|
||||
if config_arguments.log_level >= LogLevel::Default {
|
||||
if mode.is_diff() {
|
||||
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||
results.write_summary(&mut stderr().lock())?;
|
||||
results.write_summary(&mut colors::auto(stderr()).lock())?;
|
||||
} else {
|
||||
results.write_summary(&mut stdout().lock())?;
|
||||
results.write_summary(&mut colors::auto(stdout()).lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||
@@ -111,7 +112,7 @@ fn format_source_code(
|
||||
match &formatted {
|
||||
FormattedSource::Formatted(formatted) => match mode {
|
||||
FormatMode::Write => {
|
||||
let mut writer = stdout().lock();
|
||||
let mut writer = colors::auto(stdout()).lock();
|
||||
formatted
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
@@ -120,7 +121,7 @@ fn format_source_code(
|
||||
FormatMode::Diff => {
|
||||
use std::io::Write;
|
||||
write!(
|
||||
&mut stdout().lock(),
|
||||
&mut colors::auto(stdout()).lock(),
|
||||
"{}",
|
||||
source_kind.diff(formatted, path).unwrap()
|
||||
)
|
||||
@@ -130,7 +131,7 @@ fn format_source_code(
|
||||
FormattedSource::Unchanged => {
|
||||
// Write to stdout regardless of whether the source was formatted
|
||||
if mode.is_write() {
|
||||
let mut writer = stdout().lock();
|
||||
let mut writer = colors::auto(stdout()).lock();
|
||||
source_kind
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
|
||||
@@ -8,11 +8,12 @@ use std::ops::{Add, AddAssign};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use log::{debug, error, warn};
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
|
||||
use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
@@ -444,7 +445,7 @@ pub(crate) fn lint_stdin(
|
||||
// But only write a diff if it's non-empty.
|
||||
if !fixed.is_empty() {
|
||||
write!(
|
||||
&mut io::stdout().lock(),
|
||||
&mut colors::auto(io::stdout()).lock(),
|
||||
"{}",
|
||||
source_kind.diff(&transformed, path).unwrap()
|
||||
)?;
|
||||
|
||||
@@ -10,10 +10,11 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_linter::colors;
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use ruff_linter::settings::flags::FixMode;
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
@@ -145,17 +146,6 @@ pub fn run(
|
||||
}));
|
||||
}
|
||||
|
||||
// Enabled ANSI colors on Windows 10.
|
||||
#[cfg(windows)]
|
||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||
|
||||
// support FORCE_COLOR env var
|
||||
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
|
||||
if force_color.len() > 0 {
|
||||
colored::control::set_override(true);
|
||||
}
|
||||
}
|
||||
|
||||
set_up_logging(global_options.log_level())?;
|
||||
|
||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
||||
@@ -232,13 +222,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
|
||||
let mut writer: Box<dyn Write> = match cli.output_file {
|
||||
Some(path) if !cli.watch => {
|
||||
colored::control::set_override(false);
|
||||
let file = File::create(path)?;
|
||||
Box::new(BufWriter::new(file))
|
||||
Box::new(BufWriter::new(colors::none(file)))
|
||||
}
|
||||
_ => Box::new(BufWriter::new(io::stdout())),
|
||||
_ => Box::new(BufWriter::new(colors::auto(io::stdout()))),
|
||||
};
|
||||
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
|
||||
let stderr_writer = Box::new(BufWriter::new(colors::auto(io::stderr())));
|
||||
|
||||
let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref());
|
||||
let files = resolve_default_files(cli.files, is_stdin);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anstream::eprintln;
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff::args::{Args, Command};
|
||||
use ruff::{run, ExitStatus};
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use itertools::{iterate, Itertools};
|
||||
use owo_colors::OwoColorize;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_linter::fs::relativize_path;
|
||||
|
||||
@@ -1168,119 +1168,3 @@ def func():
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Per-file selects via ! negation in per-file-ignores
|
||||
#[test]
|
||||
fn negated_per_file_ignores() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"!selected.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let selected = tempdir.path().join("selected.py");
|
||||
fs::write(selected, "")?;
|
||||
let ignored = tempdir.path().join("ignored.py");
|
||||
fs::write(ignored, "")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negated_per_file_ignores_absolute() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"!src/**.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let src_dir = tempdir.path().join("src");
|
||||
fs::create_dir(&src_dir)?;
|
||||
let selected = src_dir.join("selected.py");
|
||||
fs::write(selected, "")?;
|
||||
let ignored = tempdir.path().join("ignored.py");
|
||||
fs::write(ignored, "")?;
|
||||
|
||||
insta::with_settings!({filters => vec![
|
||||
// Replace windows paths
|
||||
(r"\\", "/"),
|
||||
]}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
src/selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// patterns are additive, can't use negative patterns to "un-ignore"
|
||||
#[test]
|
||||
fn negated_per_file_ignores_overlap() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.per-file-ignores]
|
||||
"*.py" = ["RUF"]
|
||||
"!foo.py" = ["RUF"]
|
||||
"#,
|
||||
)?;
|
||||
let foo_file = tempdir.path().join("foo.py");
|
||||
fs::write(foo_file, "")?;
|
||||
let bar_file = tempdir.path().join("bar.py");
|
||||
fs::write(bar_file, "")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("--select")
|
||||
.arg("RUF901")
|
||||
.current_dir(&tempdir)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ file_resolver.exclude = [
|
||||
"venv",
|
||||
]
|
||||
file_resolver.extend_exclude = [
|
||||
"crates/ruff/resources/",
|
||||
"crates/ruff_linter/resources/",
|
||||
"crates/ruff_python_formatter/resources/",
|
||||
]
|
||||
|
||||
@@ -71,14 +71,6 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_parent(mut self, parent: TextSize) -> Self {
|
||||
self.set_parent(parent);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the location of the diagnostic's parent node.
|
||||
#[inline]
|
||||
pub fn set_parent(&mut self, parent: TextSize) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.7"
|
||||
version = "0.3.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -30,11 +30,11 @@ ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
aho-corasick = { workspace = true }
|
||||
annotate-snippets = { workspace = true, features = ["color"] }
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "string"], optional = true }
|
||||
colored = { workspace = true }
|
||||
fern = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
@@ -47,6 +47,7 @@ log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
"once_cell_cache",
|
||||
"use_unix_paths_on_wasm",
|
||||
@@ -75,8 +76,6 @@ url = { workspace = true }
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
"""
|
||||
Should emit:
|
||||
B909 - on lines 11, 25, 26, 40, 46
|
||||
"""
|
||||
|
||||
# lists
|
||||
|
||||
some_list = [1, 2, 3]
|
||||
some_other_list = [1, 2, 3]
|
||||
for elem in some_list:
|
||||
# errors
|
||||
some_list.remove(0)
|
||||
del some_list[2]
|
||||
some_list.append(elem)
|
||||
some_list.sort()
|
||||
some_list.reverse()
|
||||
some_list.clear()
|
||||
some_list.extend([1, 2])
|
||||
some_list.insert(1, 1)
|
||||
some_list.pop(1)
|
||||
some_list.pop()
|
||||
|
||||
# conditional break should error
|
||||
if elem == 2:
|
||||
some_list.remove(0)
|
||||
if elem == 3:
|
||||
break
|
||||
|
||||
# non-errors
|
||||
some_other_list.remove(elem)
|
||||
del some_list
|
||||
del some_other_list
|
||||
found_idx = some_list.index(elem)
|
||||
some_list = 3
|
||||
|
||||
# unconditional break should not error
|
||||
if elem == 2:
|
||||
some_list.remove(elem)
|
||||
break
|
||||
|
||||
|
||||
# dicts
|
||||
mydicts = {"a": {"foo": 1, "bar": 2}}
|
||||
|
||||
for elem in mydicts:
|
||||
# errors
|
||||
mydicts.popitem()
|
||||
mydicts.setdefault("foo", 1)
|
||||
mydicts.update({"foo": "bar"})
|
||||
|
||||
# no errors
|
||||
elem.popitem()
|
||||
elem.setdefault("foo", 1)
|
||||
elem.update({"foo": "bar"})
|
||||
|
||||
# sets
|
||||
|
||||
myset = {1, 2, 3}
|
||||
|
||||
for _ in myset:
|
||||
# errors
|
||||
myset.update({4, 5})
|
||||
myset.intersection_update({4, 5})
|
||||
myset.difference_update({4, 5})
|
||||
myset.symmetric_difference_update({4, 5})
|
||||
myset.add(4)
|
||||
myset.discard(3)
|
||||
|
||||
# no errors
|
||||
del myset
|
||||
|
||||
|
||||
# members
|
||||
class A:
|
||||
some_list: list
|
||||
|
||||
def __init__(self, ls):
|
||||
self.some_list = list(ls)
|
||||
|
||||
|
||||
a = A((1, 2, 3))
|
||||
# ensure member accesses are handled as errors
|
||||
for elem in a.some_list:
|
||||
a.some_list.remove(0)
|
||||
del a.some_list[2]
|
||||
|
||||
|
||||
# Augassign should error
|
||||
|
||||
foo = [1, 2, 3]
|
||||
bar = [4, 5, 6]
|
||||
for _ in foo:
|
||||
foo *= 2
|
||||
foo += bar
|
||||
foo[1] = 9
|
||||
foo[1:2] = bar
|
||||
foo[1:2:3] = bar
|
||||
|
||||
foo = {1, 2, 3}
|
||||
bar = {4, 5, 6}
|
||||
for _ in foo: # should error
|
||||
foo |= bar
|
||||
foo &= bar
|
||||
foo -= bar
|
||||
foo ^= bar
|
||||
|
||||
|
||||
# more tests for unconditional breaks - should not error
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
for _ in bar:
|
||||
bar.remove(1)
|
||||
break
|
||||
break
|
||||
|
||||
# should not error
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
for _ in bar:
|
||||
...
|
||||
break
|
||||
|
||||
# should error (?)
|
||||
for _ in foo:
|
||||
foo.remove(1)
|
||||
if bar:
|
||||
bar.remove(1)
|
||||
break
|
||||
break
|
||||
|
||||
# should error
|
||||
for _ in foo:
|
||||
if bar:
|
||||
pass
|
||||
else:
|
||||
foo.remove(1)
|
||||
|
||||
# should error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
pass
|
||||
|
||||
# should not error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
break
|
||||
|
||||
# should error
|
||||
for elem in some_list:
|
||||
if some_list.pop() == 2:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
# should not error
|
||||
for elem in some_list:
|
||||
del some_list[elem]
|
||||
some_list[elem] = 1
|
||||
some_list.remove(elem)
|
||||
some_list.discard(elem)
|
||||
@@ -195,13 +195,6 @@ class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
def __aiter__(self) -> typing.AsyncIterator[str]:
|
||||
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
|
||||
class SubclassOfBadIterator3(BadIterator3):
|
||||
def __iter__(self) -> Iterator[int]: # Y034
|
||||
...
|
||||
|
||||
class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
|
||||
...
|
||||
|
||||
class AsyncIteratorReturningAsyncIterable:
|
||||
def __aiter__(self) -> AsyncIterable[str]:
|
||||
@@ -232,11 +225,6 @@ class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
|
||||
class SubclassOfMetaclassInWhichSelfCannotBeUsed(MetaclassInWhichSelfCannotBeUsed4):
|
||||
def __new__(cls) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __enter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
async def __aenter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __isub__(self, other: SubclassOfMetaclassInWhichSelfCannotBeUsed) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
|
||||
@@ -80,13 +80,5 @@ def test_single_list_of_lists(param):
|
||||
@pytest.mark.parametrize("a", [1, 2])
|
||||
@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
@pytest.mark.parametrize("d", [3,])
|
||||
@pytest.mark.parametrize(
|
||||
"d",
|
||||
[("3", "4")],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"e",
|
||||
[("3", "4"),],
|
||||
)
|
||||
def test_multiple_decorators(a, b, c, d, e):
|
||||
def test_multiple_decorators(a, b, c):
|
||||
pass
|
||||
|
||||
@@ -5,5 +5,3 @@ this_should_be_linted = f'double {"quote"} string'
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10546
|
||||
x: "Literal['foo', 'bar']"
|
||||
# https://github.com/astral-sh/ruff/issues/10761
|
||||
f"Before {f'x {x}' if y else f'foo {z}'} after"
|
||||
|
||||
@@ -52,32 +52,32 @@ def f():
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103 (but not fixable)
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
if a:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# OK
|
||||
def bool():
|
||||
return False
|
||||
if a:
|
||||
@@ -86,14 +86,6 @@ def f():
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if keys is not None and notice.key not in keys:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
###
|
||||
# Positive cases (preview)
|
||||
###
|
||||
|
||||
@@ -6,14 +6,6 @@ class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
|
||||
pass
|
||||
|
||||
|
||||
class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
|
||||
pass
|
||||
|
||||
|
||||
class UnusualButOkay(NamedTuple("foo", [("x", int, "y", int)])):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class Good(namedtuple("foo", ["str", "int"])): # OK
|
||||
__slots__ = ("foo",)
|
||||
|
||||
|
||||
@@ -25,9 +25,3 @@ def non_ascii():
|
||||
|
||||
def all_caps():
|
||||
"""th•s is not capitalized."""
|
||||
|
||||
def single_word():
|
||||
"""singleword."""
|
||||
|
||||
def single_word_no_dot():
|
||||
"""singleword"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
|
||||
|
||||
from typing import Generic, NewType, Optional, TypeAlias, TypeVar, Union
|
||||
from typing import Optional, TypeAlias, Union
|
||||
|
||||
__version__: str
|
||||
__author__: str
|
||||
@@ -33,19 +33,6 @@ class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
# Generic bases can have forward references in stubs
|
||||
class Foo(Generic[T]): ...
|
||||
T = TypeVar("T")
|
||||
class Bar(Foo[Baz]): ...
|
||||
class Baz: ...
|
||||
|
||||
# bases in general can be forward references in stubs
|
||||
class Eggs(Spam): ...
|
||||
class Spam: ...
|
||||
|
||||
# NewType can have forward references
|
||||
MyNew = NewType("MyNew", MyClass)
|
||||
|
||||
# Annotations are treated as assignments in .pyi files, but not in .py files
|
||||
class MyClass:
|
||||
foo: int
|
||||
@@ -55,6 +42,3 @@ class MyClass:
|
||||
baz: MyClass
|
||||
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
|
||||
eggs = "baz" # always okay
|
||||
|
||||
class Blah:
|
||||
class Blah2(Blah): ...
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
"""Respect `# noqa` directives on `__all__` definitions."""
|
||||
|
||||
__all__ = [ # noqa: F822
|
||||
"Bernoulli",
|
||||
"Beta",
|
||||
"Binomial",
|
||||
]
|
||||
|
||||
|
||||
__all__ += [
|
||||
"ContinuousBernoulli", # noqa: F822
|
||||
"ExponentialFamily",
|
||||
]
|
||||
@@ -9,22 +9,3 @@ x = 1
|
||||
x = 1 # noqa: F401, W203
|
||||
# noqa: F401
|
||||
# noqa: F401, W203
|
||||
|
||||
# OK
|
||||
x = 2 # noqa: X100
|
||||
x = 2 # noqa:X100
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa X100
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa X100, X200
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa : X300
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa : X400
|
||||
|
||||
# PGH004
|
||||
x = 2 # noqa :X500
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
class Wolf:
|
||||
@staticmethod
|
||||
def eat(self): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
|
||||
class Wolf:
|
||||
@staticmethod
|
||||
def eat(sheep):
|
||||
pass
|
||||
|
||||
|
||||
class Sheep:
|
||||
@staticmethod
|
||||
def eat(cls, x, y, z): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def sleep(self, x, y, z): # [bad-staticmethod-argument]
|
||||
pass
|
||||
|
||||
def grow(self, x, y, z):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def graze(cls, x, y, z):
|
||||
pass
|
||||
|
||||
|
||||
class Foo:
|
||||
@staticmethod
|
||||
def eat(x, self, z):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def sleep(x, cls, z):
|
||||
pass
|
||||
|
||||
def grow(self, x, y, z):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def graze(cls, x, y, z):
|
||||
pass
|
||||
@@ -1,136 +0,0 @@
|
||||
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, redefined-outer-name
|
||||
|
||||
value = 10
|
||||
value2 = 0
|
||||
value3 = 3
|
||||
|
||||
# Positive
|
||||
if value < 10: # [max-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value <= 10: # [max-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value < value2: # [max-instead-of-if]
|
||||
value = value2
|
||||
|
||||
if value > 10: # [min-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value >= 10: # [min-instead-of-if]
|
||||
value = 10
|
||||
|
||||
if value > value2: # [min-instead-of-if]
|
||||
value = value2
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.value = 13
|
||||
|
||||
|
||||
A1 = A()
|
||||
if A1.value < 10: # [max-instead-of-if]
|
||||
A1.value = 10
|
||||
|
||||
if A1.value > 10: # [min-instead-of-if]
|
||||
A1.value = 10
|
||||
|
||||
|
||||
class AA:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __gt__(self, b):
|
||||
return self.value > b
|
||||
|
||||
def __ge__(self, b):
|
||||
return self.value >= b
|
||||
|
||||
def __lt__(self, b):
|
||||
return self.value < b
|
||||
|
||||
def __le__(self, b):
|
||||
return self.value <= b
|
||||
|
||||
|
||||
A1 = AA(0)
|
||||
A2 = AA(3)
|
||||
|
||||
if A2 < A1: # [max-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 <= A1: # [max-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 > A1: # [min-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
if A2 >= A1: # [min-instead-of-if]
|
||||
A2 = A1
|
||||
|
||||
# Negative
|
||||
if value < 10:
|
||||
value = 2
|
||||
|
||||
if value <= 3:
|
||||
value = 5
|
||||
|
||||
if value < 10:
|
||||
value = 2
|
||||
value2 = 3
|
||||
|
||||
if value < value2:
|
||||
value = value3
|
||||
|
||||
if value < 5:
|
||||
value = value3
|
||||
|
||||
if 2 < value <= 3:
|
||||
value = 1
|
||||
|
||||
if value < 10:
|
||||
value = 10
|
||||
else:
|
||||
value = 3
|
||||
|
||||
if value <= 3:
|
||||
value = 5
|
||||
elif value == 3:
|
||||
value = 2
|
||||
|
||||
if value > 10:
|
||||
value = 2
|
||||
|
||||
if value >= 3:
|
||||
value = 5
|
||||
|
||||
if value > 10:
|
||||
value = 2
|
||||
value2 = 3
|
||||
|
||||
if value > value2:
|
||||
value = value3
|
||||
|
||||
if value > 5:
|
||||
value = value3
|
||||
|
||||
if 2 > value >= 3:
|
||||
value = 1
|
||||
|
||||
if value > 10:
|
||||
value = 10
|
||||
else:
|
||||
value = 3
|
||||
|
||||
if value >= 3:
|
||||
value = 5
|
||||
elif value == 3:
|
||||
value = 2
|
||||
|
||||
# Parenthesized expressions
|
||||
if value.attr > 3:
|
||||
(
|
||||
value.
|
||||
attr
|
||||
) = 3
|
||||
@@ -1,55 +0,0 @@
|
||||
# Errors
|
||||
some_string = "some string"
|
||||
index, a_number, to_multiply, to_divide, to_cube, timeDiffSeconds, flags = (
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
0x3,
|
||||
)
|
||||
a_list = [1, 2]
|
||||
some_set = {"elem"}
|
||||
mat1, mat2 = None, None
|
||||
|
||||
some_string = some_string + "a very long end of string"
|
||||
index = index - 1
|
||||
a_list = a_list + ["to concat"]
|
||||
some_set = some_set | {"to concat"}
|
||||
to_multiply = to_multiply * 5
|
||||
to_divide = to_divide / 5
|
||||
to_divide = to_divide // 5
|
||||
to_cube = to_cube**3
|
||||
to_cube = 3**to_cube
|
||||
to_cube = to_cube**to_cube
|
||||
timeDiffSeconds = timeDiffSeconds % 60
|
||||
flags = flags & 0x1
|
||||
flags = flags | 0x1
|
||||
flags = flags ^ 0x1
|
||||
flags = flags << 1
|
||||
flags = flags >> 1
|
||||
mat1 = mat1 @ mat2
|
||||
a_list[1] = a_list[1] + 1
|
||||
|
||||
a_list[0:2] = a_list[0:2] * 3
|
||||
a_list[:2] = a_list[:2] * 3
|
||||
a_list[1:] = a_list[1:] * 3
|
||||
a_list[:] = a_list[:] * 3
|
||||
|
||||
index = index * (index + 10)
|
||||
|
||||
|
||||
class T:
|
||||
def t(self):
|
||||
self.a = self.a + 1
|
||||
|
||||
|
||||
obj = T()
|
||||
obj.a = obj.a + 1
|
||||
|
||||
# OK
|
||||
a_list[0] = a_list[:] * 3
|
||||
index = a_number = a_number + 1
|
||||
a_number = index = a_number + 1
|
||||
index = index * index + 10
|
||||
@@ -1,13 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class A(str, Enum): ...
|
||||
|
||||
|
||||
class B(Enum, str): ...
|
||||
|
||||
|
||||
class D(int, str, Enum): ...
|
||||
|
||||
|
||||
class E(str, int, Enum): ...
|
||||
@@ -28,6 +28,10 @@ with open("file.txt", encoding="utf8") as f:
|
||||
with open("file.txt", errors="ignore") as f:
|
||||
x = f.read()
|
||||
|
||||
# FURB101
|
||||
with open("file.txt", errors="ignore", mode="rb") as f:
|
||||
x = f.read()
|
||||
|
||||
# FURB101
|
||||
with open("file.txt", mode="r") as f: # noqa: FURB120
|
||||
x = f.read()
|
||||
@@ -56,11 +60,6 @@ with foo() as a, open("file.txt") as b, foo() as c:
|
||||
|
||||
# Non-errors.
|
||||
|
||||
# Path.read_bytes does not support any kwargs
|
||||
with open("file.txt", errors="ignore", mode="rb") as f:
|
||||
x = f.read()
|
||||
|
||||
|
||||
f2 = open("file2.txt")
|
||||
with open("file.txt") as f:
|
||||
x = f2.read()
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
def foo():
|
||||
...
|
||||
|
||||
|
||||
def bar(x):
|
||||
...
|
||||
|
||||
|
||||
# Errors.
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w") as f:
|
||||
f.write("test")
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "wb") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", mode="wb") as f:
|
||||
f.write(b"abc")
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", encoding="utf8") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", errors="ignore") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", mode="w") as f:
|
||||
f.write(foobar)
|
||||
|
||||
# FURB103
|
||||
with open(foo(), "wb") as f:
|
||||
# The body of `with` is non-trivial, but the recommendation holds.
|
||||
bar("pre")
|
||||
f.write(bar())
|
||||
bar("post")
|
||||
print("Done")
|
||||
|
||||
# FURB103
|
||||
with open("a.txt", "w") as a, open("b.txt", "wb") as b:
|
||||
a.write(x)
|
||||
b.write(y)
|
||||
|
||||
# FURB103
|
||||
with foo() as a, open("file.txt", "w") as b, foo() as c:
|
||||
# We have other things in here, multiple with items, but the user
|
||||
# writes a single time to file and that bit they can replace.
|
||||
bar(a)
|
||||
b.write(bar(bar(a + x)))
|
||||
bar(c)
|
||||
|
||||
|
||||
# FURB103
|
||||
with open("file.txt", "w", newline="\r\n") as f:
|
||||
f.write(foobar)
|
||||
|
||||
|
||||
# Non-errors.
|
||||
|
||||
with open("file.txt", errors="ignore", mode="wb") as f:
|
||||
# Path.write_bytes() does not support errors
|
||||
f.write(foobar)
|
||||
|
||||
f2 = open("file2.txt", "w")
|
||||
with open("file.txt", "w") as f:
|
||||
f2.write(x)
|
||||
|
||||
# mode is dynamic
|
||||
with open("file.txt", foo()) as f:
|
||||
f.write(x)
|
||||
|
||||
# keyword mode is incorrect
|
||||
with open("file.txt", mode="a+") as f:
|
||||
f.write(x)
|
||||
|
||||
# enables line buffering, not supported in write_text()
|
||||
with open("file.txt", buffering=1) as f:
|
||||
f.write(x)
|
||||
|
||||
# dont mistake "newline" for "mode"
|
||||
with open("file.txt", newline="wb") as f:
|
||||
f.write(x)
|
||||
|
||||
# I guess we can possibly also report this case, but the question
|
||||
# is why the user would put "w+" here in the first place.
|
||||
with open("file.txt", "w+") as f:
|
||||
f.write(x)
|
||||
|
||||
# Even though we write the whole file, we do other things.
|
||||
with open("file.txt", "w") as f:
|
||||
f.write(x)
|
||||
f.seek(0)
|
||||
x += f.read(100)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open(*filename, mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open(**kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open("file.txt", **kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
|
||||
with open("file.txt", mode="w", **kwargs) as f:
|
||||
f.write(x)
|
||||
|
||||
# This could error (but doesn't), since it can't contain unsupported arguments, like
|
||||
# `buffering`.
|
||||
with open(*filename, mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# This could error (but doesn't), since it can't contain unsupported arguments, like
|
||||
# `buffering`.
|
||||
with open(*filename, file="file.txt", mode="w") as f:
|
||||
f.write(x)
|
||||
|
||||
# Loops imply multiple writes
|
||||
with open("file.txt", "w") as f:
|
||||
while x < 0:
|
||||
f.write(foobar)
|
||||
|
||||
with open("file.txt", "w") as f:
|
||||
for line in text:
|
||||
f.write(line)
|
||||
@@ -1,40 +0,0 @@
|
||||
z = x if x else y # FURB110
|
||||
|
||||
z = x \
|
||||
if x else y # FURB110
|
||||
|
||||
z = x if x \
|
||||
else \
|
||||
y # FURB110
|
||||
|
||||
z = x() if x() else y() # FURB110
|
||||
|
||||
# FURB110
|
||||
z = x if (
|
||||
# Test for x.
|
||||
x
|
||||
) else (
|
||||
# Test for y.
|
||||
y
|
||||
)
|
||||
|
||||
# FURB110
|
||||
z = (
|
||||
x if (
|
||||
# Test for x.
|
||||
x
|
||||
) else (
|
||||
# Test for y.
|
||||
y
|
||||
)
|
||||
)
|
||||
|
||||
# FURB110
|
||||
z = (
|
||||
x if
|
||||
# If true, use x.
|
||||
x
|
||||
# Otherwise, use y.
|
||||
else
|
||||
y
|
||||
)
|
||||
@@ -10,7 +10,7 @@ op_mult = lambda x, y: x * y
|
||||
op_matmutl = lambda x, y: x @ y
|
||||
op_truediv = lambda x, y: x / y
|
||||
op_mod = lambda x, y: x % y
|
||||
op_pow = lambda x, y: x**y
|
||||
op_pow = lambda x, y: x ** y
|
||||
op_lshift = lambda x, y: x << y
|
||||
op_rshift = lambda x, y: x >> y
|
||||
op_bitor = lambda x, y: x | y
|
||||
@@ -27,10 +27,6 @@ op_gte = lambda x, y: x >= y
|
||||
op_is = lambda x, y: x is y
|
||||
op_isnot = lambda x, y: x is not y
|
||||
op_in = lambda x, y: y in x
|
||||
op_itemgetter = lambda x: x[0]
|
||||
op_itemgetter = lambda x: (x[0], x[1], x[2])
|
||||
op_itemgetter = lambda x: (x[1:], x[2])
|
||||
op_itemgetter = lambda x: x[:]
|
||||
|
||||
|
||||
def op_not2(x):
|
||||
@@ -45,30 +41,21 @@ class Adder:
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
|
||||
# OK.
|
||||
op_add3 = lambda x, y=1: x + y
|
||||
op_add3 = lambda x, y = 1: x + y
|
||||
op_neg2 = lambda x, y: y - x
|
||||
op_notin = lambda x, y: y not in x
|
||||
op_and = lambda x, y: y and x
|
||||
op_or = lambda x, y: y or x
|
||||
op_in = lambda x, y: x in y
|
||||
op_itemgetter = lambda x: (1, x[1], x[2])
|
||||
op_itemgetter = lambda x: (x.y, x[1], x[2])
|
||||
op_itemgetter = lambda x, y: (x[0], y[0])
|
||||
op_itemgetter = lambda x, y: (x[0], y[0])
|
||||
op_itemgetter = lambda x: ()
|
||||
op_itemgetter = lambda x: (*x[0], x[1])
|
||||
|
||||
|
||||
def op_neg3(x, y):
|
||||
return y - x
|
||||
|
||||
|
||||
def op_add4(x, y=1):
|
||||
def op_add4(x, y = 1):
|
||||
return x + y
|
||||
|
||||
|
||||
def op_add5(x, y):
|
||||
print("op_add5")
|
||||
return x + y
|
||||
|
||||
@@ -30,9 +30,6 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EnumerateForLoop) {
|
||||
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
|
||||
}
|
||||
if checker.enabled(Rule::LoopIteratorMutation) {
|
||||
flake8_bugbear::rules::loop_iterator_mutation(checker, stmt_for);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,15 @@ use crate::rules::{
|
||||
pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::AsyncioDanglingTask,
|
||||
Rule::BadStaticmethodArgument,
|
||||
Rule::BuiltinAttributeShadowing,
|
||||
Rule::GlobalVariableNotAssigned,
|
||||
Rule::ImportPrivateName,
|
||||
Rule::ImportShadowedByLoopVar,
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::NoSelfUse,
|
||||
Rule::RedefinedArgumentFromLocal,
|
||||
Rule::RedefinedWhileUnused,
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::SingledispatchMethod,
|
||||
Rule::SingledispatchmethodFunction,
|
||||
Rule::TooManyLocals,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
@@ -35,16 +31,19 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UndefinedLocal,
|
||||
Rule::UnusedAnnotation,
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::BuiltinAttributeShadowing,
|
||||
Rule::UnusedFunctionArgument,
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedLambdaArgument,
|
||||
Rule::UnusedMethodArgument,
|
||||
Rule::UnusedPrivateProtocol,
|
||||
Rule::UnusedPrivateTypeAlias,
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
Rule::SingledispatchMethod,
|
||||
Rule::SingledispatchmethodFunction,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -425,10 +424,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::BadStaticmethodArgument) {
|
||||
pylint::rules::bad_staticmethod_argument(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
|
||||
@@ -32,9 +32,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -47,7 +48,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py310
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -59,8 +60,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
// Ex) list[...]
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& typing::is_pep585_generic(value, &checker.semantic)
|
||||
{
|
||||
@@ -184,9 +186,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
typing::to_pep585_generic(expr, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -197,7 +200,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -267,9 +270,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
@@ -282,7 +286,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations_or_stub()
|
||||
&& checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
@@ -1172,8 +1176,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}) => {
|
||||
// Ex) `str | None`
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.semantic.future_annotations_or_stub()
|
||||
if !checker.source_type.is_stub()
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
{
|
||||
flake8_future_annotations::rules::future_required_type_annotation(
|
||||
@@ -1358,9 +1363,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IfExprMinMax) {
|
||||
refurb::rules::if_expr_min_max(checker, if_exp);
|
||||
}
|
||||
if checker.enabled(Rule::IfExpInsteadOfOrOperator) {
|
||||
refurb::rules::if_exp_instead_of_or_operator(checker, if_exp);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
|
||||
@@ -406,11 +406,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UselessObjectInheritance) {
|
||||
pyupgrade::rules::useless_object_inheritance(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::ReplaceStrEnum) {
|
||||
if checker.settings.target_version >= PythonVersion::Py311 {
|
||||
pyupgrade::rules::replace_str_enum(checker, class_def);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryClassParentheses) {
|
||||
pyupgrade::rules::unnecessary_class_parentheses(checker, class_def);
|
||||
}
|
||||
@@ -1117,9 +1112,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::TooManyBooleanExpressions) {
|
||||
pylint::rules::too_many_boolean_expressions(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::IfStmtMinMax) {
|
||||
pylint::rules::if_stmt_min_max(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
@@ -1225,9 +1217,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ReadWholeFile) {
|
||||
refurb::rules::read_whole_file(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::WriteWholeFile) {
|
||||
refurb::rules::write_whole_file(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UselessWithLock) {
|
||||
pylint::rules::useless_with_lock(checker, with_stmt);
|
||||
}
|
||||
@@ -1268,7 +1257,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.any_enabled(&[
|
||||
Rule::EnumerateForLoop,
|
||||
Rule::IncorrectDictIterator,
|
||||
Rule::LoopIteratorMutation,
|
||||
Rule::UnnecessaryEnumerate,
|
||||
Rule::UnusedLoopControlVariable,
|
||||
Rule::YieldInForLoop,
|
||||
@@ -1479,9 +1467,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::TypeBivariance) {
|
||||
pylint::rules::type_bivariance(checker, value);
|
||||
}
|
||||
if checker.enabled(Rule::NonAugmentedAssignment) {
|
||||
pylint::rules::non_augmented_assignment(checker, assign);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::UnsortedDunderAll) {
|
||||
ruff::rules::sort_dunder_all_assign(checker, assign);
|
||||
}
|
||||
|
||||
@@ -56,10 +56,9 @@ impl AnnotationContext {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// If `__future__` annotations are enabled or it's a stub file,
|
||||
// then annotations are never evaluated at runtime,
|
||||
// so we can treat them as typing-only.
|
||||
if semantic.future_annotations_or_stub() {
|
||||
// If `__future__` annotations are enabled, then annotations are never evaluated
|
||||
// at runtime, so we can treat them as typing-only.
|
||||
if semantic.future_annotations() {
|
||||
return Self::TypingOnly;
|
||||
}
|
||||
|
||||
@@ -88,7 +87,7 @@ impl AnnotationContext {
|
||||
semantic,
|
||||
) {
|
||||
Self::RuntimeRequired
|
||||
} else if semantic.future_annotations_or_stub() {
|
||||
} else if semantic.future_annotations() {
|
||||
Self::TypingOnly
|
||||
} else {
|
||||
Self::RuntimeEvaluated
|
||||
|
||||
@@ -12,8 +12,6 @@ pub(crate) struct Visit<'a> {
|
||||
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<Snapshot>,
|
||||
/// N.B. This field should always be empty unless it's a stub file
|
||||
pub(crate) class_bases: Vec<(&'a Expr, Snapshot)>,
|
||||
}
|
||||
|
||||
impl Visit<'_> {
|
||||
@@ -24,7 +22,6 @@ impl Visit<'_> {
|
||||
&& self.type_param_definitions.is_empty()
|
||||
&& self.functions.is_empty()
|
||||
&& self.lambdas.is_empty()
|
||||
&& self.class_bases.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,15 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, FStringElement,
|
||||
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
|
||||
Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllDefinition, DunderAllFlags};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
|
||||
};
|
||||
@@ -571,7 +572,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
name,
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
@@ -691,21 +691,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
if let Some(globals) = Globals::from_body(body) {
|
||||
self.semantic.set_globals(globals);
|
||||
}
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::FunctionDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::ClassDef(
|
||||
class_def @ ast::StmtClassDef {
|
||||
name,
|
||||
body,
|
||||
arguments,
|
||||
decorator_list,
|
||||
@@ -724,9 +712,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
|
||||
if let Some(arguments) = arguments {
|
||||
self.semantic.flags |= SemanticModelFlags::CLASS_BASE;
|
||||
self.visit_arguments(arguments);
|
||||
self.semantic.flags -= SemanticModelFlags::CLASS_BASE;
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
@@ -746,18 +732,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
// Set the docstring state before visiting the class body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.visit_body(body);
|
||||
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Class scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::ClassDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
range: _,
|
||||
@@ -914,6 +888,35 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
};
|
||||
|
||||
// Step 3: Clean-up
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::FunctionDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
Stmt::ClassDef(ast::StmtClassDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.analyze.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Class scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
BindingKind::ClassDefinition(scope_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Step 4: Analysis
|
||||
analyze::statement(stmt, self);
|
||||
@@ -932,23 +935,10 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
// Step 0: Pre-processing
|
||||
if self.source_type.is_stub()
|
||||
&& self.semantic.in_class_base()
|
||||
&& !self.semantic.in_deferred_class_base()
|
||||
{
|
||||
self.visit
|
||||
.class_bases
|
||||
.push((expr, self.semantic.snapshot()));
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.semantic.in_typing_literal()
|
||||
// `in_deferred_type_definition()` will only be `true` if we're now visiting the deferred nodes
|
||||
// after having already traversed the source tree once. If we're now visiting the deferred nodes,
|
||||
// we can't defer again, or we'll infinitely recurse!
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations_or_stub()
|
||||
&& self.semantic.future_annotations()
|
||||
&& (self.semantic.in_annotation() || self.source_type.is_stub())
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
@@ -1590,15 +1580,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
.push((bound, self.semantic.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
|
||||
let snapshot = self.semantic.flags;
|
||||
if f_string_element.is_expression() {
|
||||
self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD;
|
||||
}
|
||||
visitor::walk_f_string_element(self, f_string_element);
|
||||
self.semantic.flags = snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -1975,52 +1956,6 @@ impl<'a> Checker<'a> {
|
||||
scope.add(id, binding_id);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all class bases that were deferred.
|
||||
///
|
||||
/// This method should only be relevant in stub files, where forward references are
|
||||
/// legal in class bases. For other kinds of Python files, using a forward reference
|
||||
/// in a class base is never legal, so `self.visit.class_bases` should always be empty.
|
||||
///
|
||||
/// For example, in a stub file:
|
||||
/// ```python
|
||||
/// class Foo(list[Bar]): ... # <-- `Bar` is a forward reference in a class base
|
||||
/// class Bar: ...
|
||||
/// ```
|
||||
fn visit_deferred_class_bases(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
let deferred_bases = std::mem::take(&mut self.visit.class_bases);
|
||||
debug_assert!(
|
||||
self.source_type.is_stub() || deferred_bases.is_empty(),
|
||||
"Class bases should never be deferred outside of stub files"
|
||||
);
|
||||
for (expr, snapshot) in deferred_bases {
|
||||
self.semantic.restore(snapshot);
|
||||
// Set this flag to avoid infinite recursion, or we'll just defer it again:
|
||||
self.semantic.flags |= SemanticModelFlags::DEFERRED_CLASS_BASE;
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all "future type definitions".
|
||||
///
|
||||
/// A "future type definition" is a type definition where [PEP 563] semantics
|
||||
/// apply (i.e., an annotation in a module that has `from __future__ import annotations`
|
||||
/// at the top of the file, or an annotation in a stub file). These type definitions
|
||||
/// support forward references, so they are deferred on initial traversal
|
||||
/// of the source tree.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// def foo() -> Bar: # <-- return annotation is a "future type definition"
|
||||
/// return Bar()
|
||||
///
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 563]: https://peps.python.org/pep-0563/
|
||||
fn visit_deferred_future_type_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.future_type_definitions.is_empty() {
|
||||
@@ -2028,14 +1963,6 @@ impl<'a> Checker<'a> {
|
||||
for (expr, snapshot) in type_definitions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
// Type definitions should only be considered "`__future__` type definitions"
|
||||
// if they are annotations in a module where `from __future__ import
|
||||
// annotations` is active, or they are type definitions in a stub file.
|
||||
debug_assert!(
|
||||
self.semantic.future_annotations_or_stub()
|
||||
&& (self.source_type.is_stub() || self.semantic.in_annotation())
|
||||
);
|
||||
|
||||
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION
|
||||
| SemanticModelFlags::FUTURE_TYPE_DEFINITION;
|
||||
self.visit_expr(expr);
|
||||
@@ -2044,19 +1971,6 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all [type parameter definitions].
|
||||
///
|
||||
/// Type parameters natively support forward references,
|
||||
/// so are always deferred during initial traversal of the source tree.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// class Foo[T: Bar]: pass # <-- Forward reference used in definition of type parameter `T`
|
||||
/// type X[T: Bar] = Foo[T] # <-- Ditto
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
///
|
||||
/// [type parameter definitions]: https://docs.python.org/3/reference/executionmodel.html#annotation-scopes
|
||||
fn visit_deferred_type_param_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.type_param_definitions.is_empty() {
|
||||
@@ -2072,17 +1986,6 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all "string type definitions",
|
||||
/// i.e., type definitions that are enclosed within quotes so as to allow
|
||||
/// the type definition to use forward references.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// def foo() -> "Bar": # <-- return annotation is a "string type definition"
|
||||
/// return Bar()
|
||||
///
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.string_type_definitions.is_empty() {
|
||||
@@ -2095,7 +1998,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_annotation() && self.semantic.future_annotations_or_stub() {
|
||||
if self.semantic.in_annotation() && self.semantic.future_annotations() {
|
||||
if self.enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
}
|
||||
@@ -2131,11 +2034,6 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the AST, visit all function bodies.
|
||||
///
|
||||
/// Function bodies are always deferred on initial traversal of the source tree,
|
||||
/// as the body of a function may validly contain references to global-scope symbols
|
||||
/// that were not yet defined at the point when the function was defined.
|
||||
fn visit_deferred_functions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.functions.is_empty() {
|
||||
@@ -2159,9 +2057,8 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the source tree has been completed,
|
||||
/// visit all lambdas. Lambdas are deferred during the initial traversal
|
||||
/// for the same reason as function bodies.
|
||||
/// Visit all deferred lambdas. Returns a list of snapshots, such that the caller can restore
|
||||
/// the semantic model to the state it was in before visiting the deferred lambdas.
|
||||
fn visit_deferred_lambdas(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.lambdas.is_empty() {
|
||||
@@ -2187,12 +2084,10 @@ impl<'a> Checker<'a> {
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// After initial traversal of the source tree has been completed,
|
||||
/// recursively visit all AST nodes that were deferred on the first pass.
|
||||
/// This includes lambdas, functions, type parameters, and type annotations.
|
||||
/// Recursively visit all deferred AST nodes, including lambdas, functions, and type
|
||||
/// annotations.
|
||||
fn visit_deferred(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
while !self.visit.is_empty() {
|
||||
self.visit_deferred_class_bases();
|
||||
self.visit_deferred_functions();
|
||||
self.visit_deferred_type_param_definitions();
|
||||
self.visit_deferred_lambdas();
|
||||
@@ -2205,54 +2100,45 @@ impl<'a> Checker<'a> {
|
||||
fn visit_exports(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
|
||||
let definitions: Vec<DunderAllDefinition> = self
|
||||
let exports: Vec<DunderAllName> = self
|
||||
.semantic
|
||||
.global_scope()
|
||||
.get_all("__all__")
|
||||
.map(|binding_id| &self.semantic.bindings[binding_id])
|
||||
.filter_map(|binding| match &binding.kind {
|
||||
BindingKind::Export(Export { names }) => {
|
||||
Some(DunderAllDefinition::new(binding.range(), names.to_vec()))
|
||||
}
|
||||
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
for definition in definitions {
|
||||
for export in definition.names() {
|
||||
let (name, range) = (export.name(), export.range());
|
||||
if let Some(binding_id) = self.semantic.global_scope().get(name) {
|
||||
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
for export in exports {
|
||||
let (name, range) = (export.name(), export.range());
|
||||
if let Some(binding_id) = self.semantic.global_scope().get(name) {
|
||||
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
|
||||
} else {
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
)
|
||||
.with_parent(definition.start()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if self.enabled(Rule::UndefinedExport) {
|
||||
if !self.path.ends_with("__init__.py") {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
)
|
||||
.with_parent(definition.start()),
|
||||
);
|
||||
}
|
||||
if self.enabled(Rule::UndefinedExport) {
|
||||
if !self.path.ends_with("__init__.py") {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedExport {
|
||||
name: name.to_string(),
|
||||
},
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,12 +225,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
|
||||
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
|
||||
(Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
|
||||
(Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit),
|
||||
(Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit),
|
||||
@@ -272,7 +272,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0203") => (RuleGroup::Preview, rules::pylint::rules::NoStaticmethodDecorator),
|
||||
(Pylint, "R0206") => (RuleGroup::Stable, rules::pylint::rules::PropertyWithParameters),
|
||||
(Pylint, "R0402") => (RuleGroup::Stable, rules::pylint::rules::ManualFromImport),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "R0911") => (RuleGroup::Stable, rules::pylint::rules::TooManyReturnStatements),
|
||||
(Pylint, "R0912") => (RuleGroup::Stable, rules::pylint::rules::TooManyBranches),
|
||||
(Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments),
|
||||
@@ -283,34 +282,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks),
|
||||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1730") => (RuleGroup::Preview, rules::pylint::rules::IfStmtMinMax),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R2044") => (RuleGroup::Preview, rules::pylint::rules::EmptyComment),
|
||||
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
|
||||
(Pylint, "R6104") => (RuleGroup::Preview, rules::pylint::rules::NonAugmentedAssignment),
|
||||
(Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
|
||||
(Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0117") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
|
||||
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
|
||||
(Pylint, "W0128") => (RuleGroup::Preview, rules::pylint::rules::RedeclaredAssignedName),
|
||||
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
|
||||
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
|
||||
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),
|
||||
(Pylint, "W0211") => (RuleGroup::Preview, rules::pylint::rules::BadStaticmethodArgument),
|
||||
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
|
||||
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
|
||||
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
|
||||
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
|
||||
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
|
||||
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
|
||||
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
@@ -320,6 +316,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
@@ -379,7 +376,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
|
||||
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
|
||||
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
|
||||
|
||||
// flake8-blind-except
|
||||
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
||||
@@ -550,7 +546,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
|
||||
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
|
||||
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
|
||||
(Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum),
|
||||
|
||||
// pydocstyle
|
||||
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
|
||||
@@ -1039,9 +1034,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// refurb
|
||||
(Refurb, "101") => (RuleGroup::Preview, rules::refurb::rules::ReadWholeFile),
|
||||
(Refurb, "103") => (RuleGroup::Preview, rules::refurb::rules::WriteWholeFile),
|
||||
(Refurb, "105") => (RuleGroup::Preview, rules::refurb::rules::PrintEmptyString),
|
||||
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
|
||||
19
crates/ruff_linter/src/colors.rs
Normal file
19
crates/ruff_linter/src/colors.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use anstream::stream::RawStream;
|
||||
use anstream::{AutoStream, ColorChoice};
|
||||
|
||||
pub fn none<S: RawStream>(stream: S) -> AutoStream<S> {
|
||||
AutoStream::new(stream, ColorChoice::Never)
|
||||
}
|
||||
|
||||
pub fn auto<S: RawStream>(stream: S) -> AutoStream<S> {
|
||||
let choice = choice(&stream);
|
||||
AutoStream::new(stream, choice)
|
||||
}
|
||||
|
||||
pub fn choice<S: RawStream>(stream: &S) -> ColorChoice {
|
||||
AutoStream::choice(stream)
|
||||
}
|
||||
|
||||
pub fn enabled<S: RawStream>(stream: &S) -> bool {
|
||||
choice(stream) != ColorChoice::Never
|
||||
}
|
||||
@@ -1,46 +1,36 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::registry::RuleSet;
|
||||
use crate::settings::types::CompiledPerFileIgnoreList;
|
||||
|
||||
/// Create a set with codes matching the pattern/code pairs.
|
||||
pub(crate) fn ignores_from_path(path: &Path, ignore_list: &CompiledPerFileIgnoreList) -> RuleSet {
|
||||
pub(crate) fn ignores_from_path(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, RuleSet)],
|
||||
) -> RuleSet {
|
||||
let file_name = path.file_name().expect("Unable to parse filename");
|
||||
ignore_list
|
||||
pattern_code_pairs
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
if entry.basename_matcher.is_match(file_name) {
|
||||
if entry.negated { None } else {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
|
||||
path,
|
||||
entry.basename_matcher.glob().regex(),
|
||||
entry.rules
|
||||
);
|
||||
Some(&entry.rules)
|
||||
}
|
||||
} else if entry.absolute_matcher.is_match(path) {
|
||||
if entry.negated { None } else {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
|
||||
path,
|
||||
entry.absolute_matcher.glob().regex(),
|
||||
entry.rules
|
||||
);
|
||||
Some(&entry.rules)
|
||||
}
|
||||
} else if entry.negated {
|
||||
.filter_map(|(absolute, basename, rules)| {
|
||||
if basename.is_match(file_name) {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to negated pattern matching neither {:?} nor {:?}: {:?}",
|
||||
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
|
||||
path,
|
||||
entry.basename_matcher.glob().regex(),
|
||||
entry.absolute_matcher.glob().regex(),
|
||||
entry.rules
|
||||
basename.glob().regex(),
|
||||
rules
|
||||
);
|
||||
Some(&entry.rules)
|
||||
Some(rules)
|
||||
} else if absolute.is_match(path) {
|
||||
debug!(
|
||||
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
|
||||
path,
|
||||
absolute.glob().regex(),
|
||||
rules
|
||||
);
|
||||
Some(rules)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
mod checkers;
|
||||
pub mod codes;
|
||||
pub mod colors;
|
||||
mod comments;
|
||||
mod cst;
|
||||
pub mod directives;
|
||||
|
||||
@@ -2,10 +2,11 @@ use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use anstream::eprintln;
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
|
||||
@@ -3,15 +3,16 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use fern;
|
||||
use log::Level;
|
||||
use once_cell::sync::Lazy;
|
||||
use owo_colors::OwoColorize;
|
||||
use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
|
||||
|
||||
use crate::colors;
|
||||
use crate::fs;
|
||||
use crate::source_kind::SourceKind;
|
||||
use ruff_notebook::Notebook;
|
||||
@@ -22,7 +23,7 @@ pub static IDENTIFIERS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::defaul
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once_by_id {
|
||||
($id:expr, $($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
if let Ok(mut states) = $crate::logging::IDENTIFIERS.lock() {
|
||||
@@ -42,7 +43,7 @@ pub static MESSAGES: Lazy<Mutex<FxHashSet<String>>> = Lazy::new(Mutex::default);
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once_by_message {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
if let Ok(mut states) = $crate::logging::MESSAGES.lock() {
|
||||
@@ -59,7 +60,7 @@ macro_rules! warn_user_once_by_message {
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
@@ -73,7 +74,7 @@ macro_rules! warn_user_once {
|
||||
#[macro_export]
|
||||
macro_rules! warn_user {
|
||||
($($arg:tt)*) => {{
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
use log::warn;
|
||||
|
||||
let message = format!("{}", format_args!($($arg)*));
|
||||
@@ -152,7 +153,10 @@ pub fn set_up_logging(level: LogLevel) -> Result<()> {
|
||||
})
|
||||
.level(level.level_filter())
|
||||
.level_for("globset", log::LevelFilter::Warn)
|
||||
.chain(std::io::stderr())
|
||||
.chain(fern::Output::writer(
|
||||
Box::new(colors::auto(std::io::stderr())),
|
||||
"\n",
|
||||
))
|
||||
.apply()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::{Color, ColoredString, Colorize, Styles};
|
||||
use owo_colors::{OwoColorize, Style};
|
||||
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
@@ -81,7 +81,7 @@ impl Display for Diff<'_> {
|
||||
ChangeTag::Equal => " ",
|
||||
};
|
||||
|
||||
let line_style = LineStyle::from(change.tag());
|
||||
let line_style = diff_line_style(change.tag());
|
||||
|
||||
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
|
||||
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
|
||||
@@ -97,14 +97,14 @@ impl Display for Diff<'_> {
|
||||
index: new_index,
|
||||
width: digit_with
|
||||
},
|
||||
line_style.apply_to(sign).bold()
|
||||
sign.style(line_style).bold()
|
||||
)?;
|
||||
|
||||
for (emphasized, value) in change.iter_strings_lossy() {
|
||||
if emphasized {
|
||||
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
|
||||
write!(f, "{}", value.style(line_style).underline().on_black())?;
|
||||
} else {
|
||||
write!(f, "{}", line_style.apply_to(&value))?;
|
||||
write!(f, "{}", value.style(line_style))?;
|
||||
}
|
||||
}
|
||||
if change.missing_newline() {
|
||||
@@ -118,52 +118,11 @@ impl Display for Diff<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
struct LineStyle {
|
||||
fgcolor: Option<Color>,
|
||||
style: Option<Styles>,
|
||||
}
|
||||
|
||||
impl LineStyle {
|
||||
fn apply_to(&self, input: &str) -> ColoredString {
|
||||
let mut colored = ColoredString::from(input);
|
||||
if let Some(color) = self.fgcolor {
|
||||
colored = colored.color(color);
|
||||
}
|
||||
|
||||
if let Some(style) = self.style {
|
||||
match style {
|
||||
Styles::Clear => colored.clear(),
|
||||
Styles::Bold => colored.bold(),
|
||||
Styles::Dimmed => colored.dimmed(),
|
||||
Styles::Underline => colored.underline(),
|
||||
Styles::Reversed => colored.reversed(),
|
||||
Styles::Italic => colored.italic(),
|
||||
Styles::Blink => colored.blink(),
|
||||
Styles::Hidden => colored.hidden(),
|
||||
Styles::Strikethrough => colored.strikethrough(),
|
||||
}
|
||||
} else {
|
||||
colored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangeTag> for LineStyle {
|
||||
fn from(value: ChangeTag) -> Self {
|
||||
match value {
|
||||
ChangeTag::Equal => LineStyle {
|
||||
fgcolor: None,
|
||||
style: Some(Styles::Dimmed),
|
||||
},
|
||||
ChangeTag::Delete => LineStyle {
|
||||
fgcolor: Some(Color::Red),
|
||||
style: None,
|
||||
},
|
||||
ChangeTag::Insert => LineStyle {
|
||||
fgcolor: Some(Color::Green),
|
||||
style: None,
|
||||
},
|
||||
}
|
||||
fn diff_line_style(change_tag: ChangeTag) -> Style {
|
||||
match change_tag {
|
||||
ChangeTag::Equal => Style::new().dimmed(),
|
||||
ChangeTag::Delete => Style::new().red(),
|
||||
ChangeTag::Insert => Style::new().green(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
@@ -156,6 +156,7 @@ mod tests {
|
||||
use ruff_source_file::{OneIndexed, SourceFileBuilder};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::colors;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
|
||||
pub(super) fn create_messages() -> Vec<Message> {
|
||||
@@ -337,10 +338,10 @@ def foo():
|
||||
) -> String {
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_notebook_output(
|
||||
@@ -349,9 +350,9 @@ def foo():
|
||||
notebook_indexes: &FxHashMap<String, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
let mut output = colors::none(Vec::new());
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ use std::io::Write;
|
||||
use annotate_snippets::display_list::{DisplayList, FormatOptions};
|
||||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::colors;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
@@ -284,10 +285,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}],
|
||||
footer,
|
||||
opt: FormatOptions {
|
||||
#[cfg(test)]
|
||||
color: false,
|
||||
#[cfg(not(test))]
|
||||
color: colored::control::SHOULD_COLORIZE.should_colorize(),
|
||||
color: colors::enabled(&std::io::stdout()),
|
||||
..FormatOptions::default()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -147,7 +147,7 @@ impl<'a> Directive<'a> {
|
||||
|
||||
/// Lex an individual rule code (e.g., `F401`).
|
||||
#[inline]
|
||||
pub(crate) fn lex_code(line: &str) -> Option<&str> {
|
||||
fn lex_code(line: &str) -> Option<&str> {
|
||||
// Extract, e.g., the `F` in `F401`.
|
||||
let prefix = line.chars().take_while(char::is_ascii_uppercase).count();
|
||||
// Extract, e.g., the `401` in `F401`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use owo_colors::OwoColorize;
|
||||
use pyproject_toml::PyProjectToml;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
|
||||
@@ -109,7 +109,5 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
// Test redirect by prefix
|
||||
#[cfg(feature = "test-rules")]
|
||||
("RUF96", "RUF95"),
|
||||
// See: https://github.com/astral-sh/ruff/issues/10791
|
||||
("PLW0117", "PLW0177"),
|
||||
])
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Settings {
|
||||
pub mypy_init_return: bool,
|
||||
|
||||
@@ -10,7 +10,7 @@ pub fn default_tmp_dirs() -> Vec<String> {
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub hardcoded_tmp_directory: Vec<String>,
|
||||
pub check_typed_exception: bool,
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::CacheKey;
|
||||
|
||||
use crate::display_settings;
|
||||
|
||||
#[derive(Debug, Clone, CacheKey, Default)]
|
||||
#[derive(Debug, CacheKey, Default)]
|
||||
pub struct Settings {
|
||||
pub extend_allowed_calls: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ mod tests {
|
||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||
#[test_case(Rule::LoopIteratorMutation, Path::new("B909.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::name::UnqualifiedName;
|
||||
use ruff_python_ast::{
|
||||
visitor::{self, Visitor},
|
||||
Arguments, Expr, ExprAttribute, ExprCall, ExprSubscript, Stmt, StmtAssign, StmtAugAssign,
|
||||
StmtBreak, StmtDelete, StmtFor, StmtIf,
|
||||
};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for mutations to an iterable during a loop iteration.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When iterating over an iterable, mutating the iterable can lead to unexpected
|
||||
/// behavior, like skipping elements or infinite loops.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// items = [1, 2, 3]
|
||||
///
|
||||
/// for item in items:
|
||||
/// print(item)
|
||||
///
|
||||
/// # Create an infinite loop by appending to the list.
|
||||
/// items.append(item)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#typesseq-mutable)
|
||||
#[violation]
|
||||
pub struct LoopIteratorMutation {
|
||||
name: Option<SourceCodeSnippet>,
|
||||
}
|
||||
|
||||
impl Violation for LoopIteratorMutation {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let LoopIteratorMutation { name } = self;
|
||||
|
||||
if let Some(name) = name.as_ref().and_then(SourceCodeSnippet::full_display) {
|
||||
format!("Mutation to loop iterable `{name}` during iteration")
|
||||
} else {
|
||||
format!("Mutation to loop iterable during iteration")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// B909
|
||||
pub(crate) fn loop_iterator_mutation(checker: &mut Checker, stmt_for: &StmtFor) {
|
||||
let StmtFor {
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse: _,
|
||||
is_async: _,
|
||||
range: _,
|
||||
} = stmt_for;
|
||||
|
||||
if !matches!(iter.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect mutations to the iterable.
|
||||
let mutations = {
|
||||
let mut visitor = LoopMutationsVisitor::new(iter, target);
|
||||
visitor.visit_body(body);
|
||||
visitor.mutations
|
||||
};
|
||||
|
||||
// Create a diagnostic for each mutation.
|
||||
for mutation in mutations.values().flatten() {
|
||||
let name = UnqualifiedName::from_expr(iter)
|
||||
.map(|name| name.to_string())
|
||||
.map(SourceCodeSnippet::new);
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoopIteratorMutation { name }, *mutation));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the method mutates when called on an iterator.
|
||||
fn is_mutating_function(function_name: &str) -> bool {
|
||||
matches!(
|
||||
function_name,
|
||||
"append"
|
||||
| "sort"
|
||||
| "reverse"
|
||||
| "remove"
|
||||
| "clear"
|
||||
| "extend"
|
||||
| "insert"
|
||||
| "pop"
|
||||
| "popitem"
|
||||
| "setdefault"
|
||||
| "update"
|
||||
| "intersection_update"
|
||||
| "difference_update"
|
||||
| "symmetric_difference_update"
|
||||
| "add"
|
||||
| "discard"
|
||||
)
|
||||
}
|
||||
|
||||
/// A visitor to collect mutations to a variable in a loop.
|
||||
#[derive(Debug, Clone)]
|
||||
struct LoopMutationsVisitor<'a> {
|
||||
iter: &'a Expr,
|
||||
target: &'a Expr,
|
||||
mutations: HashMap<u8, Vec<TextRange>>,
|
||||
branches: Vec<u8>,
|
||||
branch: u8,
|
||||
}
|
||||
|
||||
impl<'a> LoopMutationsVisitor<'a> {
|
||||
/// Initialize the visitor.
|
||||
fn new(iter: &'a Expr, target: &'a Expr) -> Self {
|
||||
Self {
|
||||
iter,
|
||||
target,
|
||||
mutations: HashMap::new(),
|
||||
branches: vec![0],
|
||||
branch: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a mutation.
|
||||
fn add_mutation(&mut self, range: TextRange) {
|
||||
self.mutations.entry(self.branch).or_default().push(range);
|
||||
}
|
||||
|
||||
/// Handle, e.g., `del items[0]`.
|
||||
fn handle_delete(&mut self, range: TextRange, targets: &[Expr]) {
|
||||
for target in targets {
|
||||
if let Expr::Subscript(ExprSubscript {
|
||||
range: _,
|
||||
value,
|
||||
slice,
|
||||
ctx: _,
|
||||
}) = target
|
||||
{
|
||||
// Find, e.g., `del items[0]`.
|
||||
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||
// But allow, e.g., `for item in items: del items[item]`.
|
||||
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
|
||||
self.add_mutation(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle, e.g., `items[0] = 1`.
|
||||
fn handle_assign(&mut self, range: TextRange, targets: &[Expr]) {
|
||||
for target in targets {
|
||||
if let Expr::Subscript(ExprSubscript {
|
||||
range: _,
|
||||
value,
|
||||
slice,
|
||||
ctx: _,
|
||||
}) = target
|
||||
{
|
||||
// Find, e.g., `items[0] = 1`.
|
||||
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||
// But allow, e.g., `for item in items: items[item] = 1`.
|
||||
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
|
||||
self.add_mutation(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle, e.g., `items += [1]`.
|
||||
fn handle_aug_assign(&mut self, range: TextRange, target: &Expr) {
|
||||
if ComparableExpr::from(self.iter) == ComparableExpr::from(target) {
|
||||
self.add_mutation(range);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle, e.g., `items.append(1)`.
|
||||
fn handle_call(&mut self, func: &Expr, arguments: &Arguments) {
|
||||
if let Expr::Attribute(ExprAttribute {
|
||||
range,
|
||||
value,
|
||||
attr,
|
||||
ctx: _,
|
||||
}) = func
|
||||
{
|
||||
if is_mutating_function(attr.as_str()) {
|
||||
// Find, e.g., `items.remove(1)`.
|
||||
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||
// But allow, e.g., `for item in items: items.remove(item)`.
|
||||
if matches!(attr.as_str(), "remove" | "discard" | "pop") {
|
||||
if arguments.len() == 1 {
|
||||
if let [arg] = &*arguments.args {
|
||||
if ComparableExpr::from(self.target) == ComparableExpr::from(arg) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.add_mutation(*range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match stmt {
|
||||
// Ex) `del items[0]`
|
||||
Stmt::Delete(StmtDelete { range, targets }) => {
|
||||
self.handle_delete(*range, targets);
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
// Ex) `items[0] = 1`
|
||||
Stmt::Assign(StmtAssign { range, targets, .. }) => {
|
||||
self.handle_assign(*range, targets);
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
// Ex) `items += [1]`
|
||||
Stmt::AugAssign(StmtAugAssign { range, target, .. }) => {
|
||||
self.handle_aug_assign(*range, target);
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
// Ex) `if True: items.append(1)`
|
||||
Stmt::If(StmtIf {
|
||||
test,
|
||||
body,
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
// Handle the `if` branch.
|
||||
self.branch += 1;
|
||||
self.branches.push(self.branch);
|
||||
self.visit_expr(test);
|
||||
self.visit_body(body);
|
||||
self.branches.pop();
|
||||
|
||||
// Handle the `elif` and `else` branches.
|
||||
for clause in elif_else_clauses {
|
||||
self.branch += 1;
|
||||
self.branches.push(self.branch);
|
||||
if let Some(test) = &clause.test {
|
||||
self.visit_expr(test);
|
||||
}
|
||||
self.visit_body(&clause.body);
|
||||
self.branches.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// On break, clear the mutations for the current branch.
|
||||
Stmt::Break(StmtBreak { range: _ }) => {
|
||||
if let Some(mutations) = self.mutations.get_mut(&self.branch) {
|
||||
mutations.clear();
|
||||
}
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
// Avoid recursion for class and function definitions.
|
||||
Stmt::ClassDef(_) | Stmt::FunctionDef(_) => {}
|
||||
|
||||
// Default case.
|
||||
_ => {
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
// Ex) `items.append(1)`
|
||||
if let Expr::Call(ExprCall {
|
||||
func, arguments, ..
|
||||
}) = expr
|
||||
{
|
||||
self.handle_call(func, arguments);
|
||||
}
|
||||
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ pub(crate) use function_call_in_argument_default::*;
|
||||
pub(crate) use function_uses_loop_variable::*;
|
||||
pub(crate) use getattr_with_constant::*;
|
||||
pub(crate) use jump_statement_in_finally::*;
|
||||
pub(crate) use loop_iterator_mutation::*;
|
||||
pub(crate) use loop_variable_overrides_iterator::*;
|
||||
pub(crate) use mutable_argument_default::*;
|
||||
pub(crate) use no_explicit_stacklevel::*;
|
||||
@@ -48,7 +47,6 @@ mod function_call_in_argument_default;
|
||||
mod function_uses_loop_variable;
|
||||
mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_iterator_mutation;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod mutable_argument_default;
|
||||
mod no_explicit_stacklevel;
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub extend_immutable_calls: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B909.py:12:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
10 | for elem in some_list:
|
||||
11 | # errors
|
||||
12 | some_list.remove(0)
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
13 | del some_list[2]
|
||||
14 | some_list.append(elem)
|
||||
|
|
||||
|
||||
B909.py:13:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
11 | # errors
|
||||
12 | some_list.remove(0)
|
||||
13 | del some_list[2]
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
14 | some_list.append(elem)
|
||||
15 | some_list.sort()
|
||||
|
|
||||
|
||||
B909.py:14:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
12 | some_list.remove(0)
|
||||
13 | del some_list[2]
|
||||
14 | some_list.append(elem)
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
15 | some_list.sort()
|
||||
16 | some_list.reverse()
|
||||
|
|
||||
|
||||
B909.py:15:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
13 | del some_list[2]
|
||||
14 | some_list.append(elem)
|
||||
15 | some_list.sort()
|
||||
| ^^^^^^^^^^^^^^ B909
|
||||
16 | some_list.reverse()
|
||||
17 | some_list.clear()
|
||||
|
|
||||
|
||||
B909.py:16:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
14 | some_list.append(elem)
|
||||
15 | some_list.sort()
|
||||
16 | some_list.reverse()
|
||||
| ^^^^^^^^^^^^^^^^^ B909
|
||||
17 | some_list.clear()
|
||||
18 | some_list.extend([1, 2])
|
||||
|
|
||||
|
||||
B909.py:17:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
15 | some_list.sort()
|
||||
16 | some_list.reverse()
|
||||
17 | some_list.clear()
|
||||
| ^^^^^^^^^^^^^^^ B909
|
||||
18 | some_list.extend([1, 2])
|
||||
19 | some_list.insert(1, 1)
|
||||
|
|
||||
|
||||
B909.py:18:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
16 | some_list.reverse()
|
||||
17 | some_list.clear()
|
||||
18 | some_list.extend([1, 2])
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
19 | some_list.insert(1, 1)
|
||||
20 | some_list.pop(1)
|
||||
|
|
||||
|
||||
B909.py:19:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
17 | some_list.clear()
|
||||
18 | some_list.extend([1, 2])
|
||||
19 | some_list.insert(1, 1)
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
20 | some_list.pop(1)
|
||||
21 | some_list.pop()
|
||||
|
|
||||
|
||||
B909.py:20:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
18 | some_list.extend([1, 2])
|
||||
19 | some_list.insert(1, 1)
|
||||
20 | some_list.pop(1)
|
||||
| ^^^^^^^^^^^^^ B909
|
||||
21 | some_list.pop()
|
||||
|
|
||||
|
||||
B909.py:21:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
19 | some_list.insert(1, 1)
|
||||
20 | some_list.pop(1)
|
||||
21 | some_list.pop()
|
||||
| ^^^^^^^^^^^^^ B909
|
||||
22 |
|
||||
23 | # conditional break should error
|
||||
|
|
||||
|
||||
B909.py:25:9: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
23 | # conditional break should error
|
||||
24 | if elem == 2:
|
||||
25 | some_list.remove(0)
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
26 | if elem == 3:
|
||||
27 | break
|
||||
|
|
||||
|
||||
B909.py:47:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||
|
|
||||
45 | for elem in mydicts:
|
||||
46 | # errors
|
||||
47 | mydicts.popitem()
|
||||
| ^^^^^^^^^^^^^^^ B909
|
||||
48 | mydicts.setdefault("foo", 1)
|
||||
49 | mydicts.update({"foo": "bar"})
|
||||
|
|
||||
|
||||
B909.py:48:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||
|
|
||||
46 | # errors
|
||||
47 | mydicts.popitem()
|
||||
48 | mydicts.setdefault("foo", 1)
|
||||
| ^^^^^^^^^^^^^^^^^^ B909
|
||||
49 | mydicts.update({"foo": "bar"})
|
||||
|
|
||||
|
||||
B909.py:49:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||
|
|
||||
47 | mydicts.popitem()
|
||||
48 | mydicts.setdefault("foo", 1)
|
||||
49 | mydicts.update({"foo": "bar"})
|
||||
| ^^^^^^^^^^^^^^ B909
|
||||
50 |
|
||||
51 | # no errors
|
||||
|
|
||||
|
||||
B909.py:62:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
60 | for _ in myset:
|
||||
61 | # errors
|
||||
62 | myset.update({4, 5})
|
||||
| ^^^^^^^^^^^^ B909
|
||||
63 | myset.intersection_update({4, 5})
|
||||
64 | myset.difference_update({4, 5})
|
||||
|
|
||||
|
||||
B909.py:63:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
61 | # errors
|
||||
62 | myset.update({4, 5})
|
||||
63 | myset.intersection_update({4, 5})
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||
64 | myset.difference_update({4, 5})
|
||||
65 | myset.symmetric_difference_update({4, 5})
|
||||
|
|
||||
|
||||
B909.py:64:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
62 | myset.update({4, 5})
|
||||
63 | myset.intersection_update({4, 5})
|
||||
64 | myset.difference_update({4, 5})
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||
65 | myset.symmetric_difference_update({4, 5})
|
||||
66 | myset.add(4)
|
||||
|
|
||||
|
||||
B909.py:65:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
63 | myset.intersection_update({4, 5})
|
||||
64 | myset.difference_update({4, 5})
|
||||
65 | myset.symmetric_difference_update({4, 5})
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||
66 | myset.add(4)
|
||||
67 | myset.discard(3)
|
||||
|
|
||||
|
||||
B909.py:66:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
64 | myset.difference_update({4, 5})
|
||||
65 | myset.symmetric_difference_update({4, 5})
|
||||
66 | myset.add(4)
|
||||
| ^^^^^^^^^ B909
|
||||
67 | myset.discard(3)
|
||||
|
|
||||
|
||||
B909.py:67:5: B909 Mutation to loop iterable `myset` during iteration
|
||||
|
|
||||
65 | myset.symmetric_difference_update({4, 5})
|
||||
66 | myset.add(4)
|
||||
67 | myset.discard(3)
|
||||
| ^^^^^^^^^^^^^ B909
|
||||
68 |
|
||||
69 | # no errors
|
||||
|
|
||||
|
||||
B909.py:84:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
||||
|
|
||||
82 | # ensure member accesses are handled as errors
|
||||
83 | for elem in a.some_list:
|
||||
84 | a.some_list.remove(0)
|
||||
| ^^^^^^^^^^^^^^^^^^ B909
|
||||
85 | del a.some_list[2]
|
||||
|
|
||||
|
||||
B909.py:85:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
||||
|
|
||||
83 | for elem in a.some_list:
|
||||
84 | a.some_list.remove(0)
|
||||
85 | del a.some_list[2]
|
||||
| ^^^^^^^^^^^^^^^^^^ B909
|
||||
|
|
||||
|
||||
B909.py:93:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
91 | bar = [4, 5, 6]
|
||||
92 | for _ in foo:
|
||||
93 | foo *= 2
|
||||
| ^^^^^^^^ B909
|
||||
94 | foo += bar
|
||||
95 | foo[1] = 9
|
||||
|
|
||||
|
||||
B909.py:94:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
92 | for _ in foo:
|
||||
93 | foo *= 2
|
||||
94 | foo += bar
|
||||
| ^^^^^^^^^^ B909
|
||||
95 | foo[1] = 9
|
||||
96 | foo[1:2] = bar
|
||||
|
|
||||
|
||||
B909.py:95:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
93 | foo *= 2
|
||||
94 | foo += bar
|
||||
95 | foo[1] = 9
|
||||
| ^^^^^^^^^^ B909
|
||||
96 | foo[1:2] = bar
|
||||
97 | foo[1:2:3] = bar
|
||||
|
|
||||
|
||||
B909.py:96:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
94 | foo += bar
|
||||
95 | foo[1] = 9
|
||||
96 | foo[1:2] = bar
|
||||
| ^^^^^^^^^^^^^^ B909
|
||||
97 | foo[1:2:3] = bar
|
||||
|
|
||||
|
||||
B909.py:97:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
95 | foo[1] = 9
|
||||
96 | foo[1:2] = bar
|
||||
97 | foo[1:2:3] = bar
|
||||
| ^^^^^^^^^^^^^^^^ B909
|
||||
98 |
|
||||
99 | foo = {1, 2, 3}
|
||||
|
|
||||
|
||||
B909.py:102:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
100 | bar = {4, 5, 6}
|
||||
101 | for _ in foo: # should error
|
||||
102 | foo |= bar
|
||||
| ^^^^^^^^^^ B909
|
||||
103 | foo &= bar
|
||||
104 | foo -= bar
|
||||
|
|
||||
|
||||
B909.py:103:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
101 | for _ in foo: # should error
|
||||
102 | foo |= bar
|
||||
103 | foo &= bar
|
||||
| ^^^^^^^^^^ B909
|
||||
104 | foo -= bar
|
||||
105 | foo ^= bar
|
||||
|
|
||||
|
||||
B909.py:104:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
102 | foo |= bar
|
||||
103 | foo &= bar
|
||||
104 | foo -= bar
|
||||
| ^^^^^^^^^^ B909
|
||||
105 | foo ^= bar
|
||||
|
|
||||
|
||||
B909.py:105:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
103 | foo &= bar
|
||||
104 | foo -= bar
|
||||
105 | foo ^= bar
|
||||
| ^^^^^^^^^^ B909
|
||||
|
|
||||
|
||||
B909.py:125:5: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
123 | # should error (?)
|
||||
124 | for _ in foo:
|
||||
125 | foo.remove(1)
|
||||
| ^^^^^^^^^^ B909
|
||||
126 | if bar:
|
||||
127 | bar.remove(1)
|
||||
|
|
||||
|
||||
B909.py:136:9: B909 Mutation to loop iterable `foo` during iteration
|
||||
|
|
||||
134 | pass
|
||||
135 | else:
|
||||
136 | foo.remove(1)
|
||||
| ^^^^^^^^^^ B909
|
||||
137 |
|
||||
138 | # should error
|
||||
|
|
||||
|
||||
B909.py:140:8: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
138 | # should error
|
||||
139 | for elem in some_list:
|
||||
140 | if some_list.pop() == 2:
|
||||
| ^^^^^^^^^^^^^ B909
|
||||
141 | pass
|
||||
|
|
||||
|
||||
B909.py:150:8: B909 Mutation to loop iterable `some_list` during iteration
|
||||
|
|
||||
148 | # should error
|
||||
149 | for elem in some_list:
|
||||
150 | if some_list.pop() == 2:
|
||||
| ^^^^^^^^^^^^^ B909
|
||||
151 | pass
|
||||
152 | else:
|
||||
|
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub builtins_ignorelist: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub allow_dict_calls_with_keyword_arguments: bool,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::fmt::{Display, Formatter};
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub notice_rgx: Regex,
|
||||
pub author: Option<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub max_string_length: usize,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub functions_names: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub allow_multiline: bool,
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl FromIterator<String> for BannedAliases {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub aliases: FxHashMap<String, String>,
|
||||
pub banned_aliases: FxHashMap<String, BannedAliases>,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Parameters, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_semantic::analyze;
|
||||
use ruff_python_semantic::analyze::visibility::{is_abstract, is_final, is_overload};
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
|
||||
@@ -120,9 +119,7 @@ pub(crate) fn non_self_return_type(
|
||||
returns: Option<&Expr>,
|
||||
parameters: &Parameters,
|
||||
) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let ScopeKind::Class(class_def) = semantic.current_scope().kind else {
|
||||
let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -135,19 +132,21 @@ pub(crate) fn non_self_return_type(
|
||||
};
|
||||
|
||||
// PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses.
|
||||
if is_metaclass(class_def, semantic) {
|
||||
if is_metaclass(class_def, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip any abstract or overloaded methods.
|
||||
if is_abstract(decorator_list, semantic) || is_overload(decorator_list, semantic) {
|
||||
if is_abstract(decorator_list, checker.semantic())
|
||||
|| is_overload(decorator_list, checker.semantic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if is_async {
|
||||
if name == "__aenter__"
|
||||
&& is_name(returns, &class_def.name)
|
||||
&& !is_final(&class_def.decorator_list, semantic)
|
||||
&& !is_final(&class_def.decorator_list, checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
@@ -162,7 +161,7 @@ pub(crate) fn non_self_return_type(
|
||||
|
||||
// In-place methods that are expected to return `Self`.
|
||||
if is_inplace_bin_op(name) {
|
||||
if !is_self(returns, semantic) {
|
||||
if !is_self(returns, checker.semantic()) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
@@ -175,7 +174,8 @@ pub(crate) fn non_self_return_type(
|
||||
}
|
||||
|
||||
if is_name(returns, &class_def.name) {
|
||||
if matches!(name, "__enter__" | "__new__") && !is_final(&class_def.decorator_list, semantic)
|
||||
if matches!(name, "__enter__" | "__new__")
|
||||
&& !is_final(&class_def.decorator_list, checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
@@ -190,8 +190,8 @@ pub(crate) fn non_self_return_type(
|
||||
|
||||
match name {
|
||||
"__iter__" => {
|
||||
if is_iterable_or_iterator(returns, semantic)
|
||||
&& subclasses_iterator(class_def, semantic)
|
||||
if is_iterable(returns, checker.semantic())
|
||||
&& is_iterator(class_def.arguments.as_deref(), checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
@@ -203,8 +203,8 @@ pub(crate) fn non_self_return_type(
|
||||
}
|
||||
}
|
||||
"__aiter__" => {
|
||||
if is_async_iterable_or_iterator(returns, semantic)
|
||||
&& subclasses_async_iterator(class_def, semantic)
|
||||
if is_async_iterable(returns, checker.semantic())
|
||||
&& is_async_iterator(class_def.arguments.as_deref(), checker.semantic())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
@@ -221,14 +221,26 @@ pub(crate) fn non_self_return_type(
|
||||
|
||||
/// Returns `true` if the given class is a metaclass.
|
||||
fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
|
||||
)
|
||||
class_def.arguments.as_ref().is_some_and(|arguments| {
|
||||
arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(|expr| is_metaclass_base(expr, semantic))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression resolves to a metaclass.
|
||||
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(base)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the method is an in-place binary operator.
|
||||
fn is_inplace_bin_op(name: &str) -> bool {
|
||||
matches!(
|
||||
@@ -263,17 +275,24 @@ fn is_self(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
/// Return `true` if the given class extends `collections.abc.Iterator`.
|
||||
fn subclasses_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
|
||||
)
|
||||
fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
|
||||
let Some(Arguments { args: bases, .. }) = arguments else {
|
||||
return false;
|
||||
};
|
||||
bases.iter().any(|expr| {
|
||||
semantic
|
||||
.resolve_qualified_name(map_subscript(expr))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the given expression resolves to `collections.abc.Iterable` or `collections.abc.Iterator`.
|
||||
fn is_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
/// Return `true` if the given expression resolves to `collections.abc.Iterable`.
|
||||
fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(map_subscript(expr))
|
||||
.is_some_and(|qualified_name| {
|
||||
@@ -286,17 +305,24 @@ fn is_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
/// Return `true` if the given class extends `collections.abc.AsyncIterator`.
|
||||
fn subclasses_async_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
|
||||
)
|
||||
fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
|
||||
let Some(Arguments { args: bases, .. }) = arguments else {
|
||||
return false;
|
||||
};
|
||||
bases.iter().any(|expr| {
|
||||
semantic
|
||||
.resolve_qualified_name(map_subscript(expr))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable` or `collections.abc.AsyncIterator`.
|
||||
fn is_async_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable`.
|
||||
fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(map_subscript(expr))
|
||||
.is_some_and(|qualified_name| {
|
||||
|
||||
@@ -89,20 +89,4 @@ PYI034.py:195:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` u
|
||||
|
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
PYI034.py:199:9: PYI034 `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
|
||||
|
|
||||
198 | class SubclassOfBadIterator3(BadIterator3):
|
||||
199 | def __iter__(self) -> Iterator[int]: # Y034
|
||||
| ^^^^^^^^ PYI034
|
||||
200 | ...
|
||||
|
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
PYI034.py:203:9: PYI034 `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
|
||||
|
|
||||
202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
203 | def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
|
||||
| ^^^^^^^^^ PYI034
|
||||
204 | ...
|
||||
|
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
@@ -567,7 +567,7 @@ fn check_values(checker: &mut Checker, names: &Expr, values: &Expr) {
|
||||
// Replace `]` with `)` or `,)`.
|
||||
let values_end = Edit::replacement(
|
||||
if needs_trailing_comma {
|
||||
",)".into()
|
||||
"),".into()
|
||||
} else {
|
||||
")".into()
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn default_broad_exceptions() -> Vec<IdentifierPattern> {
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub fixture_parentheses: bool,
|
||||
pub parametrize_names_type: types::ParametrizeNameType,
|
||||
|
||||
@@ -168,7 +168,7 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^^^^^^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `list` of `list` for parameter values
|
||||
|
||||
@@ -179,8 +179,8 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), [(3, 4), (5, 6)])
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `list` of `list`
|
||||
|
|
||||
@@ -188,7 +188,7 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `list` of `list` for parameter values
|
||||
|
||||
@@ -199,8 +199,8 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), ([3, 4], (5, 6)))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `list` of `list`
|
||||
|
|
||||
@@ -208,7 +208,7 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `list` of `list` for parameter values
|
||||
|
||||
@@ -219,5 +219,5 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), ((3, 4), [5, 6]))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
@@ -210,7 +210,7 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^^^^^^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `list` of `tuple` for parameter values
|
||||
|
||||
@@ -221,5 +221,5 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), [(3, 4), (5, 6)])
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
@@ -237,7 +237,7 @@ PT007.py:80:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
80 |+@pytest.mark.parametrize("a", (1, 2))
|
||||
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
|
||||
PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
||||
|
|
||||
@@ -245,7 +245,7 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `tuple` of `list` for parameter values
|
||||
|
||||
@@ -256,8 +256,8 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), ([3, 4], (5, 6)))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
||||
|
|
||||
@@ -265,7 +265,7 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
| ^^^^^^ PT007
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 | @pytest.mark.parametrize(
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
|
|
||||
= help: Use `tuple` of `list` for parameter values
|
||||
|
||||
@@ -276,8 +276,8 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
81 |+@pytest.mark.parametrize(("b", "c"), ((3, 4), [5, 6]))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
||||
|
|
||||
@@ -285,8 +285,8 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
| ^^^^ PT007
|
||||
83 | @pytest.mark.parametrize(
|
||||
84 | "d",
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
84 | pass
|
||||
|
|
||||
= help: Use `tuple` of `list` for parameter values
|
||||
|
||||
@@ -296,48 +296,5 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 |-@pytest.mark.parametrize("d", [3,])
|
||||
82 |+@pytest.mark.parametrize("d", (3,))
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
85 85 | [("3", "4")],
|
||||
|
||||
PT007.py:85:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
||||
|
|
||||
83 | @pytest.mark.parametrize(
|
||||
84 | "d",
|
||||
85 | [("3", "4")],
|
||||
| ^^^^^^^^^^^^ PT007
|
||||
86 | )
|
||||
87 | @pytest.mark.parametrize(
|
||||
|
|
||||
= help: Use `tuple` of `list` for parameter values
|
||||
|
||||
ℹ Unsafe fix
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
85 |- [("3", "4")],
|
||||
85 |+ (("3", "4"),),
|
||||
86 86 | )
|
||||
87 87 | @pytest.mark.parametrize(
|
||||
88 88 | "e",
|
||||
|
||||
PT007.py:89:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
||||
|
|
||||
87 | @pytest.mark.parametrize(
|
||||
88 | "e",
|
||||
89 | [("3", "4"),],
|
||||
| ^^^^^^^^^^^^^ PT007
|
||||
90 | )
|
||||
91 | def test_multiple_decorators(a, b, c, d, e):
|
||||
|
|
||||
= help: Use `tuple` of `list` for parameter values
|
||||
|
||||
ℹ Unsafe fix
|
||||
86 86 | )
|
||||
87 87 | @pytest.mark.parametrize(
|
||||
88 88 | "e",
|
||||
89 |- [("3", "4"),],
|
||||
89 |+ (("3", "4"),),
|
||||
90 90 | )
|
||||
91 91 | def test_multiple_decorators(a, b, c, d, e):
|
||||
92 92 | pass
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
@@ -279,7 +279,7 @@ PT007.py:80:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
80 |+@pytest.mark.parametrize("a", (1, 2))
|
||||
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
|
||||
PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
||||
|
|
||||
@@ -287,8 +287,8 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 | @pytest.mark.parametrize("d", [3,])
|
||||
| ^^^^ PT007
|
||||
83 | @pytest.mark.parametrize(
|
||||
84 | "d",
|
||||
83 | def test_multiple_decorators(a, b, c):
|
||||
84 | pass
|
||||
|
|
||||
= help: Use `tuple` of `tuple` for parameter values
|
||||
|
||||
@@ -298,48 +298,5 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
|
||||
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
|
||||
82 |-@pytest.mark.parametrize("d", [3,])
|
||||
82 |+@pytest.mark.parametrize("d", (3,))
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
85 85 | [("3", "4")],
|
||||
|
||||
PT007.py:85:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
||||
|
|
||||
83 | @pytest.mark.parametrize(
|
||||
84 | "d",
|
||||
85 | [("3", "4")],
|
||||
| ^^^^^^^^^^^^ PT007
|
||||
86 | )
|
||||
87 | @pytest.mark.parametrize(
|
||||
|
|
||||
= help: Use `tuple` of `tuple` for parameter values
|
||||
|
||||
ℹ Unsafe fix
|
||||
82 82 | @pytest.mark.parametrize("d", [3,])
|
||||
83 83 | @pytest.mark.parametrize(
|
||||
84 84 | "d",
|
||||
85 |- [("3", "4")],
|
||||
85 |+ (("3", "4"),),
|
||||
86 86 | )
|
||||
87 87 | @pytest.mark.parametrize(
|
||||
88 88 | "e",
|
||||
|
||||
PT007.py:89:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
||||
|
|
||||
87 | @pytest.mark.parametrize(
|
||||
88 | "e",
|
||||
89 | [("3", "4"),],
|
||||
| ^^^^^^^^^^^^^ PT007
|
||||
90 | )
|
||||
91 | def test_multiple_decorators(a, b, c, d, e):
|
||||
|
|
||||
= help: Use `tuple` of `tuple` for parameter values
|
||||
|
||||
ℹ Unsafe fix
|
||||
86 86 | )
|
||||
87 87 | @pytest.mark.parametrize(
|
||||
88 88 | "e",
|
||||
89 |- [("3", "4"),],
|
||||
89 |+ (("3", "4"),),
|
||||
90 90 | )
|
||||
91 91 | def test_multiple_decorators(a, b, c, d, e):
|
||||
92 92 | pass
|
||||
83 83 | def test_multiple_decorators(a, b, c):
|
||||
84 84 | pass
|
||||
|
||||
@@ -449,8 +449,13 @@ pub(crate) fn check_string_quotes(checker: &mut Checker, string_like: StringLike
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(dhruvmanila): Support checking for escaped quotes in f-strings.
|
||||
if checker.semantic().in_f_string_replacement_field() {
|
||||
// If the string is part of a f-string, ignore it.
|
||||
if checker
|
||||
.indexer()
|
||||
.fstring_ranges()
|
||||
.outermost(string_like.start())
|
||||
.is_some_and(|outer| outer.start() < string_like.start() && string_like.end() < outer.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ impl From<ruff_python_ast::str::Quote> for Quote {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub inline_quotes: Quote,
|
||||
pub multiline_quotes: Quote,
|
||||
|
||||
@@ -17,7 +17,7 @@ pub const IGNORE_NAMES: [&str; 7] = [
|
||||
"_value_",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub ignore_names: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ use crate::fix::snippet::SourceCodeSnippet;
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[violation]
|
||||
pub struct NeedlessBool {
|
||||
condition: Option<SourceCodeSnippet>,
|
||||
negate: bool,
|
||||
condition: SourceCodeSnippet,
|
||||
replacement: Option<SourceCodeSnippet>,
|
||||
}
|
||||
|
||||
impl Violation for NeedlessBool {
|
||||
@@ -50,22 +50,21 @@ impl Violation for NeedlessBool {
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let NeedlessBool { condition, negate } = self;
|
||||
|
||||
if let Some(condition) = condition.as_ref().and_then(SourceCodeSnippet::full_display) {
|
||||
let NeedlessBool { condition, .. } = self;
|
||||
if let Some(condition) = condition.full_display() {
|
||||
format!("Return the condition `{condition}` directly")
|
||||
} else if *negate {
|
||||
format!("Return the negated condition directly")
|
||||
} else {
|
||||
format!("Return the condition directly")
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let NeedlessBool { condition, .. } = self;
|
||||
|
||||
if let Some(condition) = condition.as_ref().and_then(SourceCodeSnippet::full_display) {
|
||||
Some(format!("Replace with `return {condition}`"))
|
||||
let NeedlessBool { replacement, .. } = self;
|
||||
if let Some(replacement) = replacement
|
||||
.as_ref()
|
||||
.and_then(SourceCodeSnippet::full_display)
|
||||
{
|
||||
Some(format!("Replace with `{replacement}`"))
|
||||
} else {
|
||||
Some(format!("Inline condition"))
|
||||
}
|
||||
@@ -192,21 +191,29 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the replacement condition.
|
||||
let condition = if checker.indexer().has_comments(&range, checker.locator()) {
|
||||
let condition = checker.locator().slice(if_test);
|
||||
let replacement = if checker.indexer().has_comments(&range, checker.locator()) {
|
||||
None
|
||||
} else {
|
||||
// If the return values are inverted, wrap the condition in a `not`.
|
||||
if inverted {
|
||||
Some(Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
op: ast::UnaryOp::Not,
|
||||
operand: Box::new(if_test.clone()),
|
||||
let node = ast::StmtReturn {
|
||||
value: Some(Box::new(Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
op: ast::UnaryOp::Not,
|
||||
operand: Box::new(if_test.clone()),
|
||||
range: TextRange::default(),
|
||||
}))),
|
||||
range: TextRange::default(),
|
||||
}))
|
||||
};
|
||||
Some(checker.generator().stmt(&node.into()))
|
||||
} else if if_test.is_compare_expr() {
|
||||
// If the condition is a comparison, we can replace it with the condition, since we
|
||||
// know it's a boolean.
|
||||
Some(if_test.clone())
|
||||
let node = ast::StmtReturn {
|
||||
value: Some(Box::new(if_test.clone())),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
Some(checker.generator().stmt(&node.into()))
|
||||
} else if checker.semantic().is_builtin("bool") {
|
||||
// Otherwise, we need to wrap the condition in a call to `bool`.
|
||||
let func_node = ast::ExprName {
|
||||
@@ -214,7 +221,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let call_node = ast::ExprCall {
|
||||
let value_node = ast::ExprCall {
|
||||
func: Box::new(func_node.into()),
|
||||
arguments: Arguments {
|
||||
args: Box::from([if_test.clone()]),
|
||||
@@ -223,32 +230,20 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
|
||||
},
|
||||
range: TextRange::default(),
|
||||
};
|
||||
Some(Expr::Call(call_node))
|
||||
let return_node = ast::StmtReturn {
|
||||
value: Some(Box::new(value_node.into())),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
Some(checker.generator().stmt(&return_node.into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the replacement `return` statement.
|
||||
let replacement = condition.as_ref().map(|expr| {
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
value: Some(Box::new(expr.clone())),
|
||||
range: TextRange::default(),
|
||||
})
|
||||
});
|
||||
|
||||
// Generate source code.
|
||||
let replacement = replacement
|
||||
.as_ref()
|
||||
.map(|stmt| checker.generator().stmt(stmt));
|
||||
let condition = condition
|
||||
.as_ref()
|
||||
.map(|expr| checker.generator().expr(expr));
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NeedlessBool {
|
||||
condition: condition.map(SourceCodeSnippet::new),
|
||||
negate: inverted,
|
||||
condition: SourceCodeSnippet::from_str(condition),
|
||||
replacement: replacement.clone().map(SourceCodeSnippet::new),
|
||||
},
|
||||
range,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM103.py:3:5: SIM103 [*] Return the condition `bool(a)` directly
|
||||
SIM103.py:3:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
1 | def f():
|
||||
2 | # SIM103
|
||||
@@ -52,7 +52,7 @@ SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly
|
||||
16 13 |
|
||||
17 14 | def f():
|
||||
|
||||
SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
|
||||
SIM103.py:21:5: SIM103 [*] Return the condition `b` directly
|
||||
|
|
||||
19 | if a:
|
||||
20 | return 1
|
||||
@@ -78,7 +78,7 @@ SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
|
||||
26 23 |
|
||||
27 24 | def f():
|
||||
|
||||
SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
|
||||
SIM103.py:32:9: SIM103 [*] Return the condition `b` directly
|
||||
|
|
||||
30 | return 1
|
||||
31 | else:
|
||||
@@ -104,10 +104,10 @@ SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
|
||||
37 34 |
|
||||
38 35 | def f():
|
||||
|
||||
SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
SIM103.py:57:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
55 | def f():
|
||||
56 | # SIM103
|
||||
56 | # SIM103 (but not fixable)
|
||||
57 | if a:
|
||||
| _____^
|
||||
58 | | return False
|
||||
@@ -120,7 +120,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
ℹ Unsafe fix
|
||||
54 54 |
|
||||
55 55 | def f():
|
||||
56 56 | # SIM103
|
||||
56 56 | # SIM103 (but not fixable)
|
||||
57 |- if a:
|
||||
58 |- return False
|
||||
59 |- else:
|
||||
@@ -130,7 +130,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
62 59 |
|
||||
63 60 | def f():
|
||||
|
||||
SIM103.py:83:5: SIM103 Return the condition directly
|
||||
SIM103.py:83:5: SIM103 Return the condition `a` directly
|
||||
|
|
||||
81 | def bool():
|
||||
82 | return False
|
||||
@@ -142,29 +142,3 @@ SIM103.py:83:5: SIM103 Return the condition directly
|
||||
| |____________________^ SIM103
|
||||
|
|
||||
= help: Inline condition
|
||||
|
||||
SIM103.py:91:5: SIM103 [*] Return the condition `not (keys is not None and notice.key not in keys)` directly
|
||||
|
|
||||
89 | def f():
|
||||
90 | # SIM103
|
||||
91 | if keys is not None and notice.key not in keys:
|
||||
| _____^
|
||||
92 | | return False
|
||||
93 | | else:
|
||||
94 | | return True
|
||||
| |___________________^ SIM103
|
||||
|
|
||||
= help: Replace with `return not (keys is not None and notice.key not in keys)`
|
||||
|
||||
ℹ Unsafe fix
|
||||
88 88 |
|
||||
89 89 | def f():
|
||||
90 90 | # SIM103
|
||||
91 |- if keys is not None and notice.key not in keys:
|
||||
92 |- return False
|
||||
93 |- else:
|
||||
94 |- return True
|
||||
91 |+ return not (keys is not None and notice.key not in keys)
|
||||
95 92 |
|
||||
96 93 |
|
||||
97 94 | ###
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM103.py:3:5: SIM103 [*] Return the condition `bool(a)` directly
|
||||
SIM103.py:3:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
1 | def f():
|
||||
2 | # SIM103
|
||||
@@ -52,7 +52,7 @@ SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly
|
||||
16 13 |
|
||||
17 14 | def f():
|
||||
|
||||
SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
|
||||
SIM103.py:21:5: SIM103 [*] Return the condition `b` directly
|
||||
|
|
||||
19 | if a:
|
||||
20 | return 1
|
||||
@@ -78,7 +78,7 @@ SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
|
||||
26 23 |
|
||||
27 24 | def f():
|
||||
|
||||
SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
|
||||
SIM103.py:32:9: SIM103 [*] Return the condition `b` directly
|
||||
|
|
||||
30 | return 1
|
||||
31 | else:
|
||||
@@ -104,10 +104,10 @@ SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
|
||||
37 34 |
|
||||
38 35 | def f():
|
||||
|
||||
SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
SIM103.py:57:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
55 | def f():
|
||||
56 | # SIM103
|
||||
56 | # SIM103 (but not fixable)
|
||||
57 | if a:
|
||||
| _____^
|
||||
58 | | return False
|
||||
@@ -120,7 +120,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
ℹ Unsafe fix
|
||||
54 54 |
|
||||
55 55 | def f():
|
||||
56 56 | # SIM103
|
||||
56 56 | # SIM103 (but not fixable)
|
||||
57 |- if a:
|
||||
58 |- return False
|
||||
59 |- else:
|
||||
@@ -130,7 +130,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
|
||||
62 59 |
|
||||
63 60 | def f():
|
||||
|
||||
SIM103.py:83:5: SIM103 Return the condition directly
|
||||
SIM103.py:83:5: SIM103 Return the condition `a` directly
|
||||
|
|
||||
81 | def bool():
|
||||
82 | return False
|
||||
@@ -143,73 +143,47 @@ SIM103.py:83:5: SIM103 Return the condition directly
|
||||
|
|
||||
= help: Inline condition
|
||||
|
||||
SIM103.py:91:5: SIM103 [*] Return the condition `not (keys is not None and notice.key not in keys)` directly
|
||||
SIM103.py:96:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
89 | def f():
|
||||
90 | # SIM103
|
||||
91 | if keys is not None and notice.key not in keys:
|
||||
94 | def f():
|
||||
95 | # SIM103
|
||||
96 | if a:
|
||||
| _____^
|
||||
92 | | return False
|
||||
93 | | else:
|
||||
94 | | return True
|
||||
| |___________________^ SIM103
|
||||
97 | | return True
|
||||
98 | | return False
|
||||
| |________________^ SIM103
|
||||
|
|
||||
= help: Replace with `return not (keys is not None and notice.key not in keys)`
|
||||
= help: Replace with `return bool(a)`
|
||||
|
||||
ℹ Unsafe fix
|
||||
88 88 |
|
||||
89 89 | def f():
|
||||
90 90 | # SIM103
|
||||
91 |- if keys is not None and notice.key not in keys:
|
||||
92 |- return False
|
||||
93 |- else:
|
||||
94 |- return True
|
||||
91 |+ return not (keys is not None and notice.key not in keys)
|
||||
95 92 |
|
||||
96 93 |
|
||||
97 94 | ###
|
||||
93 93 |
|
||||
94 94 | def f():
|
||||
95 95 | # SIM103
|
||||
96 |- if a:
|
||||
97 |- return True
|
||||
98 |- return False
|
||||
96 |+ return bool(a)
|
||||
99 97 |
|
||||
100 98 |
|
||||
101 99 | def f():
|
||||
|
||||
SIM103.py:104:5: SIM103 [*] Return the condition `bool(a)` directly
|
||||
SIM103.py:103:5: SIM103 [*] Return the condition `a` directly
|
||||
|
|
||||
102 | def f():
|
||||
103 | # SIM103
|
||||
104 | if a:
|
||||
101 | def f():
|
||||
102 | # SIM103
|
||||
103 | if a:
|
||||
| _____^
|
||||
105 | | return True
|
||||
106 | | return False
|
||||
| |________________^ SIM103
|
||||
|
|
||||
= help: Replace with `return bool(a)`
|
||||
|
||||
ℹ Unsafe fix
|
||||
101 101 |
|
||||
102 102 | def f():
|
||||
103 103 | # SIM103
|
||||
104 |- if a:
|
||||
105 |- return True
|
||||
106 |- return False
|
||||
104 |+ return bool(a)
|
||||
107 105 |
|
||||
108 106 |
|
||||
109 107 | def f():
|
||||
|
||||
SIM103.py:111:5: SIM103 [*] Return the condition `not a` directly
|
||||
|
|
||||
109 | def f():
|
||||
110 | # SIM103
|
||||
111 | if a:
|
||||
| _____^
|
||||
112 | | return False
|
||||
113 | | return True
|
||||
104 | | return False
|
||||
105 | | return True
|
||||
| |_______________^ SIM103
|
||||
|
|
||||
= help: Replace with `return not a`
|
||||
|
||||
ℹ Unsafe fix
|
||||
108 108 |
|
||||
109 109 | def f():
|
||||
110 110 | # SIM103
|
||||
111 |- if a:
|
||||
112 |- return False
|
||||
113 |- return True
|
||||
111 |+ return not a
|
||||
100 100 |
|
||||
101 101 | def f():
|
||||
102 102 | # SIM103
|
||||
103 |- if a:
|
||||
104 |- return False
|
||||
105 |- return True
|
||||
103 |+ return not a
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use std::fmt;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr, StmtClassDef};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, identifier::Identifier, Arguments, Expr, Stmt, StmtClassDef};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_slots::rules::helpers::has_slots;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for subclasses of `collections.namedtuple` or `typing.NamedTuple`
|
||||
/// that lack a `__slots__` definition.
|
||||
/// Checks for subclasses of `collections.namedtuple` that lack a `__slots__`
|
||||
/// definition.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, the `__slots__` attribute allows you to explicitly define the
|
||||
@@ -47,28 +48,12 @@ use crate::rules::flake8_slots::rules::helpers::has_slots;
|
||||
/// ## References
|
||||
/// - [Python documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)
|
||||
#[violation]
|
||||
pub struct NoSlotsInNamedtupleSubclass(NamedTupleKind);
|
||||
pub struct NoSlotsInNamedtupleSubclass;
|
||||
|
||||
impl Violation for NoSlotsInNamedtupleSubclass {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let NoSlotsInNamedtupleSubclass(namedtuple_kind) = self;
|
||||
format!("Subclasses of {namedtuple_kind} should define `__slots__`")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
enum NamedTupleKind {
|
||||
Collections,
|
||||
Typing,
|
||||
}
|
||||
|
||||
impl fmt::Display for NamedTupleKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Collections => "`collections.namedtuple()`",
|
||||
Self::Typing => "call-based `typing.NamedTuple()`",
|
||||
})
|
||||
format!("Subclasses of `collections.namedtuple()` should define `__slots__`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,33 +67,22 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(namedtuple_kind) = namedtuple_base(bases, checker.semantic()) {
|
||||
if bases.iter().any(|base| {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = base else {
|
||||
return false;
|
||||
};
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["collections", "namedtuple"])
|
||||
})
|
||||
}) {
|
||||
if !has_slots(&class.body) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoSlotsInNamedtupleSubclass(namedtuple_kind),
|
||||
NoSlotsInNamedtupleSubclass,
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the class has a call-based namedtuple in its bases,
|
||||
/// return the kind of namedtuple it is
|
||||
/// (either `collections.namedtuple()`, or `typing.NamedTuple()`).
|
||||
/// Else, return `None`.
|
||||
fn namedtuple_base(bases: &[Expr], semantic: &SemanticModel) -> Option<NamedTupleKind> {
|
||||
for base in bases {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = base else {
|
||||
continue;
|
||||
};
|
||||
let Some(qualified_name) = semantic.resolve_qualified_name(func) else {
|
||||
continue;
|
||||
};
|
||||
match qualified_name.segments() {
|
||||
["collections", "namedtuple"] => return Some(NamedTupleKind::Collections),
|
||||
["typing", "NamedTuple"] => return Some(NamedTupleKind::Typing),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -8,9 +8,4 @@ SLOT002.py:5:7: SLOT002 Subclasses of `collections.namedtuple()` should define `
|
||||
6 | pass
|
||||
|
|
||||
|
||||
SLOT002.py:9:7: SLOT002 Subclasses of call-based `typing.NamedTuple()` should define `__slots__`
|
||||
|
|
||||
9 | class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
|
||||
| ^^^^^^^^^^^^^^^^^^ SLOT002
|
||||
10 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Display for Strictness {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey, Default)]
|
||||
#[derive(Debug, CacheKey, Default)]
|
||||
pub struct Settings {
|
||||
pub ban_relative_imports: Strictness,
|
||||
pub banned_api: FxHashMap<String, ApiBan>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub strict: bool,
|
||||
pub exempt_modules: Vec<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub ignore_variadic_names: bool,
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ pub(crate) fn categorize_imports<'a>(
|
||||
block_by_type
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct KnownModules {
|
||||
/// A map of known modules to their section.
|
||||
known: Vec<(glob::Pattern, ImportSection)>,
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Display for RelativeImportsOrder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Settings {
|
||||
pub required_imports: BTreeSet<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub max_complexity: usize,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use ruff_macros::CacheKey;
|
||||
|
||||
use crate::display_settings;
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub ignore_names: IgnoreNames,
|
||||
pub classmethod_decorators: Vec<String>,
|
||||
@@ -85,7 +85,7 @@ static DEFAULTS: &[&str] = &[
|
||||
"maxDiff",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum IgnoreNames {
|
||||
Default,
|
||||
UserProvided {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::fmt;
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
|
||||
#[derive(Debug, Clone, Default, CacheKey)]
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub max_line_length: LineLength,
|
||||
pub max_doc_length: Option<LineLength>,
|
||||
|
||||
@@ -59,36 +59,26 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||
}
|
||||
|
||||
let body = docstring.body();
|
||||
let first_word = body.split_once(' ').map_or_else(
|
||||
|| {
|
||||
// If the docstring is a single word, trim the punctuation marks because
|
||||
// it makes the ASCII test below fail.
|
||||
body.trim_end_matches(['.', '!', '?'])
|
||||
},
|
||||
|(first_word, _)| first_word,
|
||||
);
|
||||
let Some(first_word) = body.split(' ').next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Like pydocstyle, we only support ASCII for now.
|
||||
for char in first_word.chars() {
|
||||
if !char.is_ascii_alphabetic() && char != '\'' {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut first_word_chars = first_word.chars();
|
||||
let Some(first_char) = first_word_chars.next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !first_char.is_ascii() {
|
||||
return;
|
||||
}
|
||||
|
||||
let uppercase_first_char = first_char.to_ascii_uppercase();
|
||||
if first_char == uppercase_first_char {
|
||||
return;
|
||||
}
|
||||
|
||||
// Like pydocstyle, we only support ASCII for now.
|
||||
for char in first_word.chars().skip(1) {
|
||||
if !char.is_ascii_alphabetic() && char != '\'' {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str();
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user