Compare commits
56 Commits
pythonplus
...
v0.3.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5062572aca | ||
|
|
dc6f6398e7 | ||
|
|
01fe268612 | ||
|
|
c62184d057 | ||
|
|
9b3c732538 | ||
|
|
caa1450895 | ||
|
|
60fd98eb2f | ||
|
|
ac150b9314 | ||
|
|
d9ac170eb4 | ||
|
|
c5ea4209bb | ||
|
|
4ad3166a3f | ||
|
|
9aded0284e | ||
|
|
a5f41e8d63 | ||
|
|
685de912ff | ||
|
|
4045df4ad4 | ||
|
|
9d705a4414 | ||
|
|
fd3d272026 | ||
|
|
954a48b129 | ||
|
|
7caf0d064a | ||
|
|
fc792d1d2e | ||
|
|
f7740a8a20 | ||
|
|
42d4216fd7 | ||
|
|
bc9b4571eb | ||
|
|
ffd6e79677 | ||
|
|
17d56ccab3 | ||
|
|
363ff2a87e | ||
|
|
938118b65c | ||
|
|
d9f1cdbea1 | ||
|
|
1a2f9f082d | ||
|
|
ae0ff9b029 | ||
|
|
162d2eb723 | ||
|
|
31db1b6e16 | ||
|
|
93566f9321 | ||
|
|
b98d8ae42e | ||
|
|
92e6026446 | ||
|
|
bb540718c2 | ||
|
|
916301070c | ||
|
|
d6204f91cb | ||
|
|
ac6a9667d4 | ||
|
|
c8912cf1e7 | ||
|
|
88adb9601c | ||
|
|
12486315fb | ||
|
|
91e81413db | ||
|
|
2edd61709f | ||
|
|
dc021dd4d2 | ||
|
|
fd26b29986 | ||
|
|
6123a5b8bc | ||
|
|
526abebbae | ||
|
|
229a50a2c8 | ||
|
|
8619986123 | ||
|
|
608df9a1bc | ||
|
|
740c08b033 | ||
|
|
7e652e8fcb | ||
|
|
9675e1867a | ||
|
|
10ace88e9a | ||
|
|
a8e50a7f40 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v42
|
||||
- uses: tj-actions/changed-files@v43
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -92,6 +92,7 @@ coverage.xml
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
repos/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -1,5 +1,85 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-simplify`\] Detect implicit `else` cases in `needless-bool` (`SIM103`) ([#10414](https://github.com/astral-sh/ruff/pull/10414))
|
||||
- \[`pylint`\] Implement `nan-comparison` (`PLW0117`) ([#10401](https://github.com/astral-sh/ruff/pull/10401))
|
||||
- \[`pylint`\] Implement `nonlocal-and-global` (`E115`) ([#10407](https://github.com/astral-sh/ruff/pull/10407))
|
||||
- \[`pylint`\] Implement `singledispatchmethod-function` (`PLE5120`) ([#10428](https://github.com/astral-sh/ruff/pull/10428))
|
||||
- \[`refurb`\] Implement `list-reverse-copy` (`FURB187`) ([#10212](https://github.com/astral-sh/ruff/pull/10212))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-pytest-style`\] Add automatic fix for `pytest-parametrize-values-wrong-type` (`PT007`) ([#10461](https://github.com/astral-sh/ruff/pull/10461))
|
||||
- \[`pycodestyle`\] Allow SPDX license headers to exceed the line length (`E501`) ([#10481](https://github.com/astral-sh/ruff/pull/10481))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Fix unstable formatting for trailing subscript end-of-line comment ([#10492](https://github.com/astral-sh/ruff/pull/10492))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid code comment detection in PEP 723 script tags ([#10464](https://github.com/astral-sh/ruff/pull/10464))
|
||||
- Avoid incorrect tuple transformation in single-element case (`C409`) ([#10491](https://github.com/astral-sh/ruff/pull/10491))
|
||||
- Bug fix: Prevent fully defined links [`name`](link) from being reformatted ([#10442](https://github.com/astral-sh/ruff/pull/10442))
|
||||
- Consider raw source code for `W605` ([#10480](https://github.com/astral-sh/ruff/pull/10480))
|
||||
- Docs: Link inline settings when not part of options section ([#10499](https://github.com/astral-sh/ruff/pull/10499))
|
||||
- Don't treat annotations as redefinitions in `.pyi` files ([#10512](https://github.com/astral-sh/ruff/pull/10512))
|
||||
- Fix `E231` bug: Inconsistent catch compared to pycodestyle, such as when dict nested in list ([#10469](https://github.com/astral-sh/ruff/pull/10469))
|
||||
- Fix pylint upstream categories not showing in docs ([#10441](https://github.com/astral-sh/ruff/pull/10441))
|
||||
- Add missing `Options` references to blank line docs ([#10498](https://github.com/astral-sh/ruff/pull/10498))
|
||||
- 'Revert "F821: Fix false negatives in .py files when `from __future__ import annotations` is active (#10362)"' ([#10513](https://github.com/astral-sh/ruff/pull/10513))
|
||||
- Apply NFKC normalization to unicode identifiers in the lexer ([#10412](https://github.com/astral-sh/ruff/pull/10412))
|
||||
- Avoid failures due to non-deterministic binding ordering ([#10478](https://github.com/astral-sh/ruff/pull/10478))
|
||||
- \[`flake8-bugbear`\] Allow tuples of exceptions (`B030`) ([#10437](https://github.com/astral-sh/ruff/pull/10437))
|
||||
- \[`flake8-quotes`\] Avoid syntax errors due to invalid quotes (`Q000, Q002`) ([#10199](https://github.com/astral-sh/ruff/pull/10199))
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bandit`\]: Implement `S610` rule ([#10316](https://github.com/astral-sh/ruff/pull/10316))
|
||||
- \[`pycodestyle`\] Implement `blank-line-at-end-of-file` (`W391`) ([#10243](https://github.com/astral-sh/ruff/pull/10243))
|
||||
- \[`pycodestyle`\] Implement `redundant-backslash` (`E502`) ([#10292](https://github.com/astral-sh/ruff/pull/10292))
|
||||
- \[`pylint`\] - implement `redeclared-assigned-name` (`W0128`) ([#9268](https://github.com/astral-sh/ruff/pull/9268))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8_comprehensions`\] Handled special case for `C400` which also matches `C416` ([#10419](https://github.com/astral-sh/ruff/pull/10419))
|
||||
- \[`flake8-bandit`\] Implement upstream updates for `S311`, `S324` and `S605` ([#10313](https://github.com/astral-sh/ruff/pull/10313))
|
||||
- \[`pyflakes`\] Remove `F401` fix for `__init__` imports by default and allow opt-in to unsafe fix ([#10365](https://github.com/astral-sh/ruff/pull/10365))
|
||||
- \[`pylint`\] Implement `invalid-bool-return-type` (`E304`) ([#10377](https://github.com/astral-sh/ruff/pull/10377))
|
||||
- \[`pylint`\] Include builtin warnings in useless-exception-statement (`PLW0133`) ([#10394](https://github.com/astral-sh/ruff/pull/10394))
|
||||
|
||||
### CLI
|
||||
|
||||
- Add message on success to `ruff check` ([#8631](https://github.com/astral-sh/ruff/pull/8631))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`PIE970`\] Allow trailing ellipsis in `typing.TYPE_CHECKING` ([#10413](https://github.com/astral-sh/ruff/pull/10413))
|
||||
- Avoid `TRIO115` if the argument is a variable ([#10376](https://github.com/astral-sh/ruff/pull/10376))
|
||||
- \[`F811`\] Avoid removing shadowed imports that point to different symbols ([#10387](https://github.com/astral-sh/ruff/pull/10387))
|
||||
- Fix `F821` and `F822` false positives in `.pyi` files ([#10341](https://github.com/astral-sh/ruff/pull/10341))
|
||||
- Fix `F821` false negatives in `.py` files when `from __future__ import annotations` is active ([#10362](https://github.com/astral-sh/ruff/pull/10362))
|
||||
- Fix case where `Indexer` fails to identify continuation preceded by newline #10351 ([#10354](https://github.com/astral-sh/ruff/pull/10354))
|
||||
- Sort hash maps in `Settings` display ([#10370](https://github.com/astral-sh/ruff/pull/10370))
|
||||
- Track conditional deletions in the semantic model ([#10415](https://github.com/astral-sh/ruff/pull/10415))
|
||||
- \[`C413`\] Wrap expressions in parentheses when negating ([#10346](https://github.com/astral-sh/ruff/pull/10346))
|
||||
- \[`pycodestyle`\] Do not ignore lines before the first logical line in blank lines rules. ([#10382](https://github.com/astral-sh/ruff/pull/10382))
|
||||
- \[`pycodestyle`\] Do not trigger `E225` and `E275` when the next token is a ')' ([#10315](https://github.com/astral-sh/ruff/pull/10315))
|
||||
- \[`pylint`\] Avoid false-positive slot non-assignment for `__dict__` (`PLE0237`) ([#10348](https://github.com/astral-sh/ruff/pull/10348))
|
||||
- Gate f-string struct size test for Rustc \< 1.76 ([#10371](https://github.com/astral-sh/ruff/pull/10371))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Use `ruff.toml` format in README ([#10393](https://github.com/astral-sh/ruff/pull/10393))
|
||||
- \[`RUF008`\] Make it clearer that a mutable default in a dataclass is only valid if it is typed as a ClassVar ([#10395](https://github.com/astral-sh/ruff/pull/10395))
|
||||
- \[`pylint`\] Extend docs and test in `invalid-str-return-type` (`E307`) ([#10400](https://github.com/astral-sh/ruff/pull/10400))
|
||||
- Remove `.` from `check` and `format` commands ([#10217](https://github.com/astral-sh/ruff/pull/10217))
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Preview features
|
||||
@@ -1199,7 +1279,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
|
||||
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
|
||||
|
||||
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
|
||||
*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).*
|
||||
|
||||
### Configuration
|
||||
|
||||
|
||||
151
Cargo.lock
generated
151
Cargo.lock
generated
@@ -152,21 +152,6 @@ dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -309,9 +294,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.2"
|
||||
version = "4.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
|
||||
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -373,11 +358,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.0"
|
||||
version = "4.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
|
||||
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
@@ -631,12 +616,6 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
@@ -699,12 +678,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "drop_bomb"
|
||||
version = "0.1.5"
|
||||
@@ -912,6 +885,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@@ -1514,6 +1493,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
@@ -1759,33 +1748,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
@@ -1798,9 +1760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2003,11 +1965,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.2"
|
||||
version = "0.3.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"bitflags 2.4.2",
|
||||
"cachedir",
|
||||
@@ -2025,6 +1986,7 @@ dependencies = [
|
||||
"log",
|
||||
"mimalloc",
|
||||
"notify",
|
||||
"num_cpus",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
@@ -2101,7 +2063,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools 0.12.1",
|
||||
"libcst",
|
||||
"once_cell",
|
||||
"pretty_assertions",
|
||||
"rayon",
|
||||
"regex",
|
||||
@@ -2145,7 +2106,6 @@ name = "ruff_formatter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"drop_bomb",
|
||||
"insta",
|
||||
"ruff_cache",
|
||||
"ruff_macros",
|
||||
"ruff_text_size",
|
||||
@@ -2167,7 +2127,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.2"
|
||||
version = "0.3.4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2192,14 +2152,12 @@ dependencies = [
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pretty_assertions",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
"result-like",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
"ruff_index",
|
||||
"ruff_macros",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
@@ -2220,7 +2178,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"toml",
|
||||
@@ -2246,7 +2203,6 @@ name = "ruff_notebook"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
"itertools 0.12.1",
|
||||
"once_cell",
|
||||
"rand",
|
||||
@@ -2295,7 +2251,6 @@ name = "ruff_python_formatter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.2",
|
||||
"clap",
|
||||
"countme",
|
||||
"insta",
|
||||
@@ -2341,10 +2296,8 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"lexical-parse-float",
|
||||
"rand",
|
||||
"ruff_python_ast",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
@@ -2368,6 +2321,7 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"tiny-keccak",
|
||||
"unicode-ident",
|
||||
"unicode-normalization",
|
||||
"unicode_names2",
|
||||
]
|
||||
|
||||
@@ -2409,7 +2363,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"itertools 0.12.1",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_index",
|
||||
"ruff_python_parser",
|
||||
"ruff_source_file",
|
||||
@@ -2442,13 +2395,12 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.3.2"
|
||||
version = "0.3.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2466,7 +2418,6 @@ dependencies = [
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"ruff_text_size",
|
||||
@@ -2521,7 +2472,6 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
@@ -2872,7 +2822,7 @@ version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
@@ -2962,12 +2912,6 @@ dependencies = [
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "3.3.1"
|
||||
@@ -3003,18 +2947,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3087,9 +3031,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
|
||||
checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3108,9 +3052,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.6"
|
||||
version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
||||
checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -3428,15 +3372,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@@ -3480,9 +3415,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -3521,9 +3456,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.41"
|
||||
version = "0.3.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143ddeb4f833e2ed0d252e618986e18bfc7b0e52f2d28d77d05b2f045dd8eb61"
|
||||
checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
@@ -3535,9 +3470,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.41"
|
||||
version = "0.3.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89"
|
||||
checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -16,18 +16,16 @@ aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
configparser = { version = "3.0.3" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
@@ -65,12 +63,13 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
num_cpus = { version = "1.16.0" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.78" }
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
quote = { version = "1.0.23" }
|
||||
@@ -96,9 +95,9 @@ strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.51" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.57" }
|
||||
thiserror = { version = "1.0.58" }
|
||||
tikv-jemallocator = { version = "0.5.0" }
|
||||
toml = { version = "0.8.9" }
|
||||
toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
@@ -108,12 +107,13 @@ unic-ucd-category = { version = "0.9" }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
ureq = { version = "2.9.6" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
wasm-bindgen = { version = "0.2.92" }
|
||||
wasm-bindgen-test = { version = "0.3.40" }
|
||||
wasm-bindgen-test = { version = "0.3.42" }
|
||||
wild = { version = "2" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -32,7 +32,7 @@ An extremely fast Python linter and code formatter, written in Rust.
|
||||
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
|
||||
- 📏 Over [800 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
|
||||
of popular Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
@@ -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.2
|
||||
rev: v0.3.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -272,7 +272,7 @@ for more on the linting and formatting commands, respectively.
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
**Ruff supports over 700 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
**Ruff supports over 800 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
@@ -429,6 +429,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Mypy](https://github.com/python/mypy)
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [Nokia](https://nokia.com/)
|
||||
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||
- [NumPyro](https://github.com/pyro-ppl/numpyro)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.3.2"
|
||||
version = "0.3.4"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -41,6 +41,7 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
num_cpus = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -53,7 +54,7 @@ tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
tracing-subscriber = { workspace = true, features = ["registry"]}
|
||||
tracing-subscriber = { workspace = true, features = ["registry"] }
|
||||
tracing-tree = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
@@ -61,9 +62,8 @@ wild = { workspace = true }
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
||||
assert_cmd = { workspace = true }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"]}
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -496,7 +496,7 @@ pub struct FormatCommand {
|
||||
pub range: Option<FormatRange>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[derive(Copy, Clone, Debug, clap::Parser)]
|
||||
pub struct ServerCommand {
|
||||
/// Enable preview mode; required for regular operation
|
||||
#[arg(long)]
|
||||
|
||||
@@ -252,6 +252,7 @@ mod test {
|
||||
for file in [&pyproject_toml, &python_file, ¬ebook] {
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.mode(0o000)
|
||||
.open(file)?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::ExitStatus;
|
||||
use anyhow::Result;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
@@ -9,7 +11,11 @@ use tracing_subscriber::{
|
||||
};
|
||||
use tracing_tree::time::Uptime;
|
||||
|
||||
pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
pub(crate) fn run_server(
|
||||
preview: bool,
|
||||
worker_threads: NonZeroUsize,
|
||||
log_level: LogLevel,
|
||||
) -> Result<ExitStatus> {
|
||||
if !preview {
|
||||
tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`");
|
||||
return Ok(ExitStatus::Error);
|
||||
@@ -33,7 +39,7 @@ pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatu
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
|
||||
let server = Server::new()?;
|
||||
let server = Server::new(worker_threads)?;
|
||||
|
||||
server.run().map(|()| ExitStatus::Success)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdout, BufWriter, Write};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
@@ -204,10 +205,15 @@ fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitS
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here
|
||||
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let ServerCommand { preview } = args;
|
||||
commands::server::run_server(preview, log_level)
|
||||
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
|
||||
let worker_threads = num_cpus::get().max(4);
|
||||
commands::server::run_server(
|
||||
preview,
|
||||
NonZeroUsize::try_from(worker_threads).expect("a non-zero worker thread count"),
|
||||
log_level,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||
|
||||
@@ -16,7 +16,7 @@ impl std::fmt::Display for PanicError {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static LAST_PANIC: std::cell::Cell<Option<PanicError>> = std::cell::Cell::new(None);
|
||||
static LAST_PANIC: std::cell::Cell<Option<PanicError>> = const { std::cell::Cell::new(None) };
|
||||
}
|
||||
|
||||
/// [`catch_unwind`](std::panic::catch_unwind) wrapper that sets a custom [`set_hook`](std::panic::set_hook)
|
||||
|
||||
@@ -1353,6 +1353,7 @@ fn unreadable_pyproject_toml() -> Result<()> {
|
||||
// Create an empty file with 000 permissions
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.mode(0o000)
|
||||
.open(pyproject_toml)?;
|
||||
|
||||
@@ -22,7 +22,7 @@ ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
|
||||
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"] }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help"] }
|
||||
@@ -31,7 +31,6 @@ imara-diff = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
|
||||
@@ -134,7 +134,7 @@ impl Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
/// We currently prefer the the similarity index, but i'd like to keep this around
|
||||
/// We currently prefer the similarity index, but i'd like to keep this around
|
||||
#[allow(clippy::cast_precision_loss, unused)]
|
||||
pub(crate) fn jaccard_index(&self) -> f32 {
|
||||
self.intersection as f32 / (self.black_input + self.ruff_output + self.intersection) as f32
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Generate Markdown documentation for applicable rules.
|
||||
#![allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -97,12 +98,13 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) {
|
||||
let mut in_options = false;
|
||||
let mut after = String::new();
|
||||
let mut referenced_options = HashSet::new();
|
||||
|
||||
// HACK: This is an ugly regex hack that's necessary because mkdocs uses
|
||||
// a non-CommonMark-compliant Markdown parser, which doesn't support code
|
||||
// tags in link definitions
|
||||
// (see https://github.com/Python-Markdown/markdown/issues/280).
|
||||
let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[])").unwrap().replace_all(
|
||||
let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[(])").unwrap().replace_all(
|
||||
documentation,
|
||||
|caps: &Captures| {
|
||||
format!(
|
||||
@@ -135,6 +137,7 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
|
||||
let anchor = option.replace('.', "_");
|
||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
|
||||
referenced_options.insert(option);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -142,6 +145,20 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
|
||||
|
||||
out.push_str(line);
|
||||
}
|
||||
|
||||
let re = Regex::new(r"\[`([^`]*?)`]\[(.*?)]").unwrap();
|
||||
for (_, [option, _]) in re.captures_iter(&documentation).map(|c| c.extract()) {
|
||||
if let Some(OptionEntry::Field(field)) = Options::metadata().find(option) {
|
||||
if referenced_options.insert(option) {
|
||||
let anchor = option.replace('.', "_");
|
||||
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
|
||||
}
|
||||
if field.deprecated.is_some() {
|
||||
eprintln!("Rule {rule_name} references deprecated option {option}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !after.is_empty() {
|
||||
out.push('\n');
|
||||
out.push('\n');
|
||||
@@ -159,7 +176,7 @@ mod tests {
|
||||
process_documentation(
|
||||
"
|
||||
See also [`lint.mccabe.max-complexity`] and [`lint.task-tags`].
|
||||
Something [`else`][other].
|
||||
Something [`else`][other]. Some [link](https://example.com).
|
||||
|
||||
## Options
|
||||
|
||||
@@ -174,7 +191,7 @@ Something [`else`][other].
|
||||
output,
|
||||
"
|
||||
See also [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity] and [`lint.task-tags`][lint.task-tags].
|
||||
Something [`else`][other].
|
||||
Something [`else`][other]. Some [link](https://example.com).
|
||||
|
||||
## Options
|
||||
|
||||
|
||||
@@ -180,8 +180,22 @@ pub(crate) fn generate() -> String {
|
||||
.map(|rule| (rule.upstream_category(&linter), rule))
|
||||
.into_group_map();
|
||||
|
||||
let mut rules_by_upstream_category: Vec<_> = rules_by_upstream_category.iter().collect();
|
||||
|
||||
// Sort the upstream categories alphabetically by prefix.
|
||||
rules_by_upstream_category.sort_by(|(a, _), (b, _)| {
|
||||
a.as_ref()
|
||||
.map(|category| category.prefix)
|
||||
.unwrap_or_default()
|
||||
.cmp(
|
||||
b.as_ref()
|
||||
.map(|category| category.prefix)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
|
||||
if rules_by_upstream_category.len() > 1 {
|
||||
for (opt, rules) in &rules_by_upstream_category {
|
||||
for (opt, rules) in rules_by_upstream_category {
|
||||
if opt.is_some() {
|
||||
let UpstreamCategoryAndPrefix { category, prefix } = opt.unwrap();
|
||||
table_out.push_str(&format!("#### {category} ({prefix})"));
|
||||
|
||||
@@ -24,7 +24,6 @@ tracing = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "ruff_text_size/serde"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.3.2"
|
||||
version = "0.3.4"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -15,7 +15,6 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
@@ -75,11 +74,9 @@ url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -36,3 +36,32 @@ dictionary = {
|
||||
# except:
|
||||
# except Foo:
|
||||
# except Exception as e: print(e)
|
||||
|
||||
|
||||
# Script tag without an opening tag (Error)
|
||||
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
# Script tag (OK)
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
# Script tag without a closing tag (OK)
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
|
||||
@@ -9,62 +9,69 @@ B030:
|
||||
|
||||
try:
|
||||
pass
|
||||
except 1: # error
|
||||
except 1: # Error
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (1, ValueError): # error
|
||||
except (1, ValueError): # Error
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
|
||||
except (ValueError, (RuntimeError, (KeyError, TypeError))): # Error
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
except (ValueError, *(RuntimeError, (KeyError, TypeError))): # Error
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *(RuntimeError, TypeError)): # ok
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *[RuntimeError, *(TypeError,)]): # ok
|
||||
except (*a, *(RuntimeError, (KeyError, TypeError))): # Error
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *b): # ok
|
||||
except* a + (RuntimeError, (KeyError, TypeError)): # Error
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(RuntimeError, TypeError)): # ok
|
||||
except (ValueError, *(RuntimeError, TypeError)): # OK
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *[RuntimeError, *(TypeError,)]): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(b, c)): # ok
|
||||
except (*a, *b): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(*b, *c)): # ok
|
||||
except (*a, *(RuntimeError, TypeError)): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(b, c)): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (*a, *(*b, *c)): # OK
|
||||
pass
|
||||
|
||||
|
||||
@@ -74,5 +81,52 @@ def what_to_catch():
|
||||
|
||||
try:
|
||||
pass
|
||||
except what_to_catch(): # ok
|
||||
except what_to_catch(): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (a, b) + (c, d): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except* (a, b) + (c, d): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except* (a, (b) + (c)): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (a, b) + (c, d) + (e, f): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except a + (b, c): # OK
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, *(RuntimeError, TypeError), *((ArithmeticError,) + (EOFError,))):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except ((a, b) + (c, d)) + ((e, f) + (g)): # OK
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (a, b) * (c, d): # B030
|
||||
pass
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# Cannot combine with C416. Should use list comprehension here.
|
||||
even_nums = list(2 * x for x in range(3))
|
||||
odd_nums = list(
|
||||
2 * x + 1 for x in range(3)
|
||||
)
|
||||
|
||||
|
||||
# Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
x = list(x for x in range(3))
|
||||
x = list(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
|
||||
# Not built-in list.
|
||||
def list(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
list(2 * x for x in range(3))
|
||||
list(x for x in range(3))
|
||||
|
||||
@@ -16,3 +16,11 @@ tuple( # comment
|
||||
tuple([ # comment
|
||||
1, 2
|
||||
])
|
||||
|
||||
tuple((
|
||||
1,
|
||||
))
|
||||
|
||||
t6 = tuple([1])
|
||||
t7 = tuple((1,))
|
||||
t8 = tuple([1,])
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
def func():
|
||||
import logging
|
||||
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
def func():
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
|
||||
@@ -227,3 +227,11 @@ class Repro[int](Protocol):
|
||||
def impl(self) -> str:
|
||||
"""Docstring"""
|
||||
return self.func()
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
def contains_meaningful_ellipsis() -> list[int]:
|
||||
"""Allow this in a TYPE_CHECKING block."""
|
||||
...
|
||||
|
||||
@@ -79,5 +79,6 @@ 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,])
|
||||
def test_multiple_decorators(a, b, c):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
""'Start with empty string' ' and lint docstring safely'
|
||||
""" Not a docstring """
|
||||
|
||||
def foo(self, bar="""not a docstring"""):
|
||||
""'Start with empty string' ' and lint docstring safely'
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): ""'Start with empty string' ' and lint docstring safely'; pass
|
||||
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
"Do not"' start with empty string' ' and lint docstring safely'
|
||||
""" Not a docstring """
|
||||
|
||||
def foo(self, bar="""not a docstring"""):
|
||||
"Do not"' start with empty string' ' and lint docstring safely'
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): "Do not"' start with empty string' ' and lint docstring safely'; pass
|
||||
@@ -0,0 +1,5 @@
|
||||
""'Start with empty string' ' and lint docstring safely'
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
@@ -0,0 +1,5 @@
|
||||
"Do not"' start with empty string' ' and lint docstring safely'
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
''"Start with empty string" ' and lint docstring safely'
|
||||
''' Not a docstring '''
|
||||
|
||||
def foo(self, bar='''not a docstring'''):
|
||||
''"Start with empty string" ' and lint docstring safely'
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): ''"Start with empty string" ' and lint docstring safely'; pass
|
||||
@@ -0,0 +1,9 @@
|
||||
class SingleLineDocstrings():
|
||||
'Do not'" start with empty string" ' and lint docstring safely'
|
||||
''' Not a docstring '''
|
||||
|
||||
def foo(self, bar='''not a docstring'''):
|
||||
'Do not'" start with empty string" ' and lint docstring safely'
|
||||
pass
|
||||
|
||||
class Nested(foo()[:]): 'Do not'" start with empty string" ' and lint docstring safely'; pass
|
||||
@@ -0,0 +1,5 @@
|
||||
''"Start with empty string" ' and lint docstring safely'
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
@@ -0,0 +1,5 @@
|
||||
'Do not'" start with empty string" ' and lint docstring safely'
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""" this is not a docstring """
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_would_be_triple_quotes.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_would_be_triple_quotes.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = ""'Start with empty string' ' and lint docstring safely'
|
||||
s = "Do not"' start with empty string' ' and lint docstring safely'
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_would_be_triple_quotes.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_would_be_triple_quotes.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s = ''"Start with empty string" ' and lint docstring safely'
|
||||
s = 'Do not'" start with empty string" ' and lint docstring safely'
|
||||
@@ -84,3 +84,22 @@ def f():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
###
|
||||
# Positive cases (preview)
|
||||
###
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if a:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM103
|
||||
if a:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -47,4 +47,60 @@ snapshot.file_uri[len(f's3://{self.s3_bucket_name}/'):]
|
||||
{len(f's3://{self.s3_bucket_name}/'):1}
|
||||
|
||||
#: Okay
|
||||
a = (1,
|
||||
a = (1,)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/10113
|
||||
"""Minimal repo."""
|
||||
|
||||
def main() -> None:
|
||||
"""Primary function."""
|
||||
results = {
|
||||
"k1": [1],
|
||||
"k2":[2],
|
||||
}
|
||||
results_in_tuple = (
|
||||
{
|
||||
"k1": [1],
|
||||
"k2":[2],
|
||||
},
|
||||
)
|
||||
results_in_list = [
|
||||
{
|
||||
"k1": [1],
|
||||
"k2":[2],
|
||||
}
|
||||
]
|
||||
results_in_list_first = [
|
||||
{
|
||||
"k2":[2],
|
||||
}
|
||||
]
|
||||
|
||||
x = [
|
||||
{
|
||||
"k1":[2], # E231
|
||||
"k2": [2:4],
|
||||
"k3":[2], # E231
|
||||
"k4": [2],
|
||||
"k5": [2],
|
||||
"k6": [1, 2, 3, 4,5,6,7] # E231
|
||||
},
|
||||
{
|
||||
"k1": [
|
||||
{
|
||||
"ka":[2,3], # E231
|
||||
},
|
||||
{
|
||||
"kb": [2,3], # E231
|
||||
},
|
||||
{
|
||||
"ka":[2, 3], # E231
|
||||
"kb": [2, 3], # Ok
|
||||
"kc": [2, 3], # Ok
|
||||
"kd": [2,3], # E231
|
||||
"ke":[2,3], # E231
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -82,3 +82,8 @@ class Bar:
|
||||
"""
|
||||
This is a long sentence that ends with a shortened URL and, therefore, could easily be broken across multiple lines ([source](https://ruff.rs))
|
||||
"""
|
||||
|
||||
|
||||
# OK
|
||||
# SPDX-FileCopyrightText: Copyright 2012-2015 Charlie Marsh <very-long-email-address@fake.com>
|
||||
# SPDX-License-Identifier: a very long license identifier that exceeds the line length limit
|
||||
|
||||
@@ -52,3 +52,8 @@ value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/10434
|
||||
f"{{}}+-\d"
|
||||
f"\n{{}}+-\d+"
|
||||
f"\n{{}}<EFBFBD>+-\d+"
|
||||
|
||||
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_29.pyi
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F811_29.pyi
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Regression test for: https://github.com/astral-sh/ruff/issues/10509"""
|
||||
|
||||
from foo import Bar as Bar
|
||||
|
||||
class Eggs:
|
||||
Bar: int # OK
|
||||
|
||||
Bar = 1 # F811
|
||||
@@ -33,16 +33,3 @@ class MyClass:
|
||||
baz: MyClass
|
||||
eggs = baz # Still invalid even when `__future__.annotations` are enabled
|
||||
eggs = "baz" # always okay
|
||||
|
||||
# Forward references:
|
||||
MaybeDStr: TypeAlias = Optional[DStr] # Still invalid even when `__future__.annotations` are enabled
|
||||
MaybeDStr2: TypeAlias = Optional["DStr"] # always okay
|
||||
DStr: TypeAlias = Union[D, str] # Still invalid even when `__future__.annotations` are enabled
|
||||
DStr2: TypeAlias = Union["D", str] # always okay
|
||||
|
||||
class D: ...
|
||||
|
||||
# More circular references
|
||||
class Leaf: ...
|
||||
class Tree(list[Tree | Leaf]): ... # Still invalid even when `__future__.annotations` are enabled
|
||||
class Tree2(list["Tree | Leaf"]): ... # always okay
|
||||
|
||||
9
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_28.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_28.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Test that unicode identifiers are NFKC-normalised"""
|
||||
|
||||
𝒞 = 500
|
||||
print(𝒞)
|
||||
print(C + 𝒞) # 2 references to the same variable due to NFKC normalization
|
||||
print(C / 𝒞)
|
||||
print(C == 𝑪 == 𝒞 == 𝓒 == 𝕮)
|
||||
|
||||
print(𝒟) # F821
|
||||
23
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_29.py
vendored
Normal file
23
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_29.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Regression test for #10451.
|
||||
|
||||
Annotations in a class are allowed to be forward references
|
||||
if `from __future__ import annotations` is active,
|
||||
even if they're in a class included in
|
||||
`lint.flake8-type-checking.runtime-evaluated-base-classes`.
|
||||
|
||||
They're not allowed to refer to symbols that cannot be *resolved*
|
||||
at runtime, however.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
some_mapping: Mapped[list[Bar]] | None = None # Should not trigger F821 (resolveable forward reference)
|
||||
simplified: list[Bar] | None = None # Should not trigger F821 (resolveable forward reference)
|
||||
|
||||
|
||||
class Bar:
|
||||
pass
|
||||
@@ -11,6 +11,13 @@ def f():
|
||||
print(X)
|
||||
|
||||
|
||||
def f():
|
||||
global X
|
||||
|
||||
if X > 0:
|
||||
del X
|
||||
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
|
||||
76
crates/ruff_linter/resources/test/fixtures/pylint/nan_comparison.py
vendored
Normal file
76
crates/ruff_linter/resources/test/fixtures/pylint/nan_comparison.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import math
|
||||
from math import nan as bad_val
|
||||
import numpy as np
|
||||
from numpy import nan as npy_nan
|
||||
|
||||
|
||||
x = float("nan")
|
||||
y = np.NaN
|
||||
|
||||
# PLW0117
|
||||
if x == float("nan"):
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if x == float("NaN"):
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if x == float("NAN"):
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if x == float("Nan"):
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if x == math.nan:
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if x == bad_val:
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if y == np.NaN:
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if y == np.NAN:
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if y == np.nan:
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
if y == npy_nan:
|
||||
pass
|
||||
|
||||
# OK
|
||||
if math.isnan(x):
|
||||
pass
|
||||
|
||||
# OK
|
||||
if np.isnan(y):
|
||||
pass
|
||||
|
||||
# OK
|
||||
if x == 0:
|
||||
pass
|
||||
|
||||
# OK
|
||||
if x == float("32"):
|
||||
pass
|
||||
|
||||
# OK
|
||||
if x == float(42):
|
||||
pass
|
||||
|
||||
# OK
|
||||
if y == np.inf:
|
||||
pass
|
||||
|
||||
# OK
|
||||
if x == "nan":
|
||||
pass
|
||||
67
crates/ruff_linter/resources/test/fixtures/pylint/nonlocal_and_global.py
vendored
Normal file
67
crates/ruff_linter/resources/test/fixtures/pylint/nonlocal_and_global.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# Positive cases
|
||||
|
||||
counter = 0
|
||||
|
||||
|
||||
def count():
|
||||
global counter
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
|
||||
|
||||
def count():
|
||||
counter = 0
|
||||
|
||||
def count(counter_type):
|
||||
if counter_type == "nonlocal":
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
else:
|
||||
global counter
|
||||
counter += 1
|
||||
|
||||
|
||||
def count():
|
||||
counter = 0
|
||||
|
||||
def count_twice():
|
||||
for i in range(2):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
global counter
|
||||
|
||||
|
||||
def count():
|
||||
nonlocal counter
|
||||
global counter
|
||||
counter += 1
|
||||
|
||||
|
||||
# Negative cases
|
||||
|
||||
counter = 0
|
||||
|
||||
|
||||
def count():
|
||||
global counter
|
||||
counter += 1
|
||||
|
||||
|
||||
def count():
|
||||
counter = 0
|
||||
|
||||
def count_local():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
|
||||
|
||||
def count():
|
||||
counter = 0
|
||||
|
||||
def count_local():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
|
||||
def count_global():
|
||||
global counter
|
||||
counter += 1
|
||||
6
crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pylint/redeclared_assigned_name.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
FIRST, FIRST = (1, 2) # PLW0128
|
||||
FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
|
||||
FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
|
||||
FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
|
||||
|
||||
FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK
|
||||
23
crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py
vendored
Normal file
23
crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
from functools import singledispatchmethod
|
||||
|
||||
|
||||
@singledispatchmethod # [singledispatchmethod-function]
|
||||
def convert_position(position):
|
||||
pass
|
||||
|
||||
|
||||
class Board:
|
||||
|
||||
@singledispatchmethod # Ok
|
||||
@classmethod
|
||||
def convert_position(cls, position):
|
||||
pass
|
||||
|
||||
@singledispatchmethod # Ok
|
||||
def move(self, position):
|
||||
pass
|
||||
|
||||
@singledispatchmethod # [singledispatchmethod-function]
|
||||
@staticmethod
|
||||
def do(position):
|
||||
pass
|
||||
@@ -1,11 +1,28 @@
|
||||
import datetime
|
||||
import datetime as dt
|
||||
from datetime import timezone
|
||||
from datetime import timezone as tz
|
||||
def func():
|
||||
import datetime
|
||||
|
||||
print(datetime.timezone(-1))
|
||||
print(timezone.utc)
|
||||
print(tz.utc)
|
||||
print(datetime.timezone(-1))
|
||||
|
||||
print(datetime.timezone.utc)
|
||||
print(dt.timezone.utc)
|
||||
|
||||
def func():
|
||||
from datetime import timezone
|
||||
|
||||
print(timezone.utc)
|
||||
|
||||
|
||||
def func():
|
||||
from datetime import timezone as tz
|
||||
|
||||
print(tz.utc)
|
||||
|
||||
|
||||
def func():
|
||||
import datetime
|
||||
|
||||
print(datetime.timezone.utc)
|
||||
|
||||
|
||||
def func():
|
||||
import datetime as dt
|
||||
|
||||
print(dt.timezone.utc)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import math
|
||||
|
||||
from math import e as special_e
|
||||
from math import log as special_log
|
||||
|
||||
# Errors.
|
||||
# Errors
|
||||
math.log(1, 2)
|
||||
math.log(1, 10)
|
||||
math.log(1, math.e)
|
||||
@@ -11,15 +8,10 @@ foo = ...
|
||||
math.log(foo, 2)
|
||||
math.log(foo, 10)
|
||||
math.log(foo, math.e)
|
||||
math.log(1, special_e)
|
||||
special_log(1, 2)
|
||||
special_log(1, 10)
|
||||
special_log(1, math.e)
|
||||
special_log(1, special_e)
|
||||
math.log(1, 2.0)
|
||||
math.log(1, 10.0)
|
||||
|
||||
# Ok.
|
||||
# OK
|
||||
math.log2(1)
|
||||
math.log10(1)
|
||||
math.log(1)
|
||||
@@ -40,6 +32,7 @@ math.log10(1, 2) # math.log10 takes only one argument.
|
||||
|
||||
math.log(1, base=2) # math.log does not accept keyword arguments.
|
||||
|
||||
|
||||
def log(*args):
|
||||
print(f"Logging: {args}")
|
||||
|
||||
|
||||
62
crates/ruff_linter/resources/test/fixtures/refurb/FURB187.py
vendored
Normal file
62
crates/ruff_linter/resources/test/fixtures/refurb/FURB187.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Errors
|
||||
|
||||
|
||||
def a():
|
||||
l = []
|
||||
l = reversed(l)
|
||||
|
||||
|
||||
def b():
|
||||
l = []
|
||||
l = list(reversed(l))
|
||||
|
||||
|
||||
def c():
|
||||
l = []
|
||||
l = l[::-1]
|
||||
|
||||
|
||||
# False negative
|
||||
def c2():
|
||||
class Wrapper:
|
||||
l: list[int]
|
||||
|
||||
w = Wrapper()
|
||||
w.l = list(reversed(w.l))
|
||||
w.l = w.l[::-1]
|
||||
w.l = reversed(w.l)
|
||||
|
||||
|
||||
# OK
|
||||
|
||||
|
||||
def d():
|
||||
l = []
|
||||
_ = reversed(l)
|
||||
|
||||
|
||||
def e():
|
||||
l = []
|
||||
l = l[::-2]
|
||||
l = l[1:]
|
||||
l = l[1::-1]
|
||||
l = l[:1:-1]
|
||||
|
||||
|
||||
def f():
|
||||
d = {}
|
||||
|
||||
# Don't warn: `d` is a dictionary, which doesn't have a `reverse` method.
|
||||
d = reversed(d)
|
||||
|
||||
|
||||
def g():
|
||||
l = "abc"[::-1]
|
||||
|
||||
|
||||
def h():
|
||||
l = reversed([1, 2, 3])
|
||||
|
||||
|
||||
def i():
|
||||
l = list(reversed([1, 2, 3]))
|
||||
@@ -1,4 +1,3 @@
|
||||
import typing
|
||||
from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
|
||||
|
||||
@@ -26,10 +25,6 @@ def f(arg: str = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.List[str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Tuple[str] = None): # RUF013
|
||||
pass
|
||||
|
||||
@@ -41,10 +36,6 @@ def f(arg: Optional[int] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Optional[int] = None):
|
||||
pass
|
||||
|
||||
|
||||
# Union
|
||||
|
||||
|
||||
@@ -60,10 +51,6 @@ def f(arg: Union[str, None] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Union[int, str, None] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[int, str, Any] = None):
|
||||
pass
|
||||
|
||||
@@ -80,10 +67,6 @@ def f(arg: Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
# PEP 604 Union
|
||||
|
||||
|
||||
@@ -130,10 +113,6 @@ def f(arg: Literal[1, "foo"] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
# Annotated
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# No `typing.Optional` import
|
||||
|
||||
|
||||
def f(arg: int = None): # RUF011
|
||||
def f(arg: int = None): # RUF013
|
||||
pass
|
||||
|
||||
30
crates/ruff_linter/resources/test/fixtures/ruff/RUF013_3.py
vendored
Normal file
30
crates/ruff_linter/resources/test/fixtures/ruff/RUF013_3.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import typing
|
||||
|
||||
|
||||
def f(arg: typing.List[str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
# Optional
|
||||
|
||||
|
||||
def f(arg: typing.Optional[int] = None):
|
||||
pass
|
||||
|
||||
|
||||
# Union
|
||||
|
||||
|
||||
def f(arg: typing.Union[int, str, None] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
# Literal
|
||||
|
||||
|
||||
def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
|
||||
pass
|
||||
@@ -3,13 +3,10 @@ Violation:
|
||||
Use '.exception' over '.error' inside except blocks
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def bad():
|
||||
import logging
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -20,6 +17,10 @@ def bad():
|
||||
|
||||
|
||||
def bad():
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -50,6 +51,10 @@ def bad():
|
||||
|
||||
|
||||
def good():
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -64,6 +69,10 @@ def good():
|
||||
|
||||
|
||||
def fine():
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -71,16 +80,20 @@ def fine():
|
||||
|
||||
|
||||
def fine():
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
logger.error("Context message here", exc_info=sys.exc_info())
|
||||
|
||||
|
||||
from logging import error, exception
|
||||
|
||||
|
||||
def bad():
|
||||
from logging import error, exception
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -91,6 +104,8 @@ def bad():
|
||||
|
||||
|
||||
def good():
|
||||
from logging import error, exception
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -98,6 +113,8 @@ def good():
|
||||
|
||||
|
||||
def fine():
|
||||
from logging import error, exception
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -105,6 +122,9 @@ def fine():
|
||||
|
||||
|
||||
def fine():
|
||||
from logging import error, exception
|
||||
import sys
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
@@ -112,6 +132,8 @@ def fine():
|
||||
|
||||
|
||||
def nested():
|
||||
from logging import error, exception
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except Exception:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Imported, ScopeKind};
|
||||
use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -43,6 +43,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
Rule::SingledispatchMethod,
|
||||
Rule::SingledispatchmethodFunction,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -91,13 +92,29 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::GlobalVariableNotAssigned) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
let binding = checker.semantic.binding(binding_id);
|
||||
// If the binding is a `global`, then it's a top-level `global` that was never
|
||||
// assigned in the current scope. If it were assigned, the `global` would be
|
||||
// shadowed by the assignment.
|
||||
if binding.kind.is_global() {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pylint::rules::GlobalVariableNotAssigned {
|
||||
name: (*name).to_string(),
|
||||
},
|
||||
binding.range(),
|
||||
));
|
||||
// If the binding was conditionally deleted, it will include a reference within
|
||||
// a `Del` context, but won't be shadowed by a `BindingKind::Deletion`, as in:
|
||||
// ```python
|
||||
// if condition:
|
||||
// del var
|
||||
// ```
|
||||
if binding
|
||||
.references
|
||||
.iter()
|
||||
.map(|id| checker.semantic.reference(*id))
|
||||
.all(ResolvedReference::is_load)
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
pylint::rules::GlobalVariableNotAssigned {
|
||||
name: (*name).to_string(),
|
||||
},
|
||||
binding.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,6 +420,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pylint::rules::singledispatch_method(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::SingledispatchmethodFunction) {
|
||||
pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if checker.any_enabled(&[
|
||||
Rule::InvalidFirstArgumentNameForClassMethod,
|
||||
Rule::InvalidFirstArgumentNameForMethod,
|
||||
|
||||
@@ -1283,6 +1283,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MagicValueComparison) {
|
||||
pylint::rules::magic_value_comparison(checker, left, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::NanComparison) {
|
||||
pylint::rules::nan_comparison(checker, left, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_compare(checker, compare);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}));
|
||||
}
|
||||
}
|
||||
Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => {
|
||||
Stmt::Nonlocal(nonlocal @ ast::StmtNonlocal { names, range: _ }) => {
|
||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||
checker.diagnostics.extend(names.iter().filter_map(|name| {
|
||||
pycodestyle::rules::ambiguous_variable_name(name, name.range())
|
||||
@@ -50,6 +50,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonlocalAndGlobal) {
|
||||
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
||||
}
|
||||
}
|
||||
Stmt::Break(_) => {
|
||||
if checker.enabled(Rule::BreakOutsideLoop) {
|
||||
@@ -1079,7 +1082,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_simplify::rules::if_with_same_arms(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::NeedlessBool) {
|
||||
flake8_simplify::rules::needless_bool(checker, if_);
|
||||
flake8_simplify::rules::needless_bool(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::IfElseBlockInsteadOfDictLookup) {
|
||||
flake8_simplify::rules::if_else_block_instead_of_dict_lookup(checker, if_);
|
||||
@@ -1389,6 +1392,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
|
||||
if checker.enabled(Rule::RedeclaredAssignedName) {
|
||||
pylint::rules::redeclared_assigned_name(checker, targets);
|
||||
}
|
||||
if checker.enabled(Rule::LambdaAssignment) {
|
||||
if let [target] = &targets[..] {
|
||||
pycodestyle::rules::lambda_assignment(checker, target, value, None, stmt);
|
||||
@@ -1497,6 +1503,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::ListReverseCopy) {
|
||||
refurb::rules::list_assign_reversed(checker, assign);
|
||||
}
|
||||
}
|
||||
Stmt::AnnAssign(
|
||||
assign_stmt @ ast::StmtAnnAssign {
|
||||
|
||||
@@ -540,7 +540,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
for name in names {
|
||||
if let Some((scope_id, binding_id)) = self.semantic.nonlocal(name) {
|
||||
// Mark the binding as "used".
|
||||
self.semantic.add_local_reference(binding_id, name.range());
|
||||
self.semantic.add_local_reference(
|
||||
binding_id,
|
||||
ExprContext::Load,
|
||||
name.range(),
|
||||
);
|
||||
|
||||
// Mark the binding in the enclosing scope as "rebound" in the current
|
||||
// scope.
|
||||
@@ -933,7 +937,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
&& (self.semantic.in_typing_only_annotation() || self.source_type.is_stub())
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
self.visit.string_type_definitions.push((
|
||||
@@ -1832,7 +1835,7 @@ impl<'a> Checker<'a> {
|
||||
if matches!(
|
||||
parent,
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
||||
) && !(self.semantic.in_annotation() || self.source_type.is_stub())
|
||||
) && !self.semantic.in_annotation()
|
||||
{
|
||||
self.add_binding(id, expr.range(), BindingKind::Annotation, flags);
|
||||
return;
|
||||
@@ -2113,7 +2116,8 @@ impl<'a> Checker<'a> {
|
||||
// Mark anything referenced in `__all__` as used.
|
||||
// TODO(charlie): `range` here should be the range of the name in `__all__`, not
|
||||
// the range of `__all__` itself.
|
||||
self.semantic.add_global_reference(binding_id, range);
|
||||
self.semantic
|
||||
.add_global_reference(binding_id, ExprContext::Load, range);
|
||||
} else {
|
||||
if self.semantic.global_scope().uses_star_imports() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
|
||||
@@ -20,12 +20,7 @@ use crate::rules::isort::block::{Block, BlockBuilder};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) -> Option<ImportMap> {
|
||||
let Some(package) = package else {
|
||||
return None;
|
||||
};
|
||||
let Some(module_path) = to_module_path(package, path) else {
|
||||
return None;
|
||||
};
|
||||
let module_path = to_module_path(package?, path)?;
|
||||
|
||||
let num_imports = blocks.iter().map(|block| block.imports.len()).sum();
|
||||
let mut module_imports = Vec::with_capacity(num_imports);
|
||||
|
||||
@@ -234,6 +234,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit),
|
||||
(Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit),
|
||||
(Pylint, "E0115") => (RuleGroup::Preview, rules::pylint::rules::NonlocalAndGlobal),
|
||||
(Pylint, "E0116") => (RuleGroup::Stable, rules::pylint::rules::ContinueInFinally),
|
||||
(Pylint, "E0117") => (RuleGroup::Stable, rules::pylint::rules::NonlocalWithoutBinding),
|
||||
(Pylint, "E0118") => (RuleGroup::Stable, rules::pylint::rules::LoadBeforeGlobalDeclaration),
|
||||
@@ -256,6 +257,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E1310") => (RuleGroup::Stable, rules::pylint::rules::BadStrStripCall),
|
||||
(Pylint, "E1507") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarValue),
|
||||
(Pylint, "E1519") => (RuleGroup::Preview, rules::pylint::rules::SingledispatchMethod),
|
||||
(Pylint, "E1520") => (RuleGroup::Preview, rules::pylint::rules::SingledispatchmethodFunction),
|
||||
(Pylint, "E1700") => (RuleGroup::Stable, rules::pylint::rules::YieldFromInAsyncFunction),
|
||||
(Pylint, "E2502") => (RuleGroup::Stable, rules::pylint::rules::BidirectionalUnicode),
|
||||
(Pylint, "E2510") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterBackspace),
|
||||
@@ -292,8 +294,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
|
||||
(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),
|
||||
@@ -1052,6 +1056,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),
|
||||
(Refurb, "180") => (RuleGroup::Preview, rules::refurb::rules::MetaClassABCMeta),
|
||||
(Refurb, "181") => (RuleGroup::Preview, rules::refurb::rules::HashlibDigestHex),
|
||||
(Refurb, "187") => (RuleGroup::Preview, rules::refurb::rules::ListReverseCopy),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Stable, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
|
||||
@@ -40,10 +40,7 @@ pub(crate) fn delete_stmt(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) -> Edit {
|
||||
if parent
|
||||
.map(|parent| is_lone_child(stmt, parent))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if parent.is_some_and(|parent| is_lone_child(stmt, parent)) {
|
||||
// If removing this node would lead to an invalid syntax tree, replace
|
||||
// it with a `pass`.
|
||||
Edit::range_replacement("pass".to_string(), stmt.range())
|
||||
|
||||
@@ -278,9 +278,7 @@ impl<'a> Insertion<'a> {
|
||||
/// Find the end of the last docstring.
|
||||
fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
|
||||
let mut iter = body.iter();
|
||||
let Some(mut stmt) = iter.next() else {
|
||||
return None;
|
||||
};
|
||||
let mut stmt = iter.next()?;
|
||||
if !is_docstring_stmt(stmt) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -255,6 +255,7 @@ impl Renamer {
|
||||
| BindingKind::ClassDefinition(_)
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::ConditionalDeletion(_)
|
||||
| BindingKind::UnboundException(_) => {
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range()))
|
||||
}
|
||||
|
||||
@@ -43,7 +43,49 @@ impl Violation for CommentedOutCode {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_standalone_comment(line: &str) -> bool {
|
||||
/// ERA001
|
||||
pub(crate) fn commented_out_code(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
// Skip comments within `/// script` tags.
|
||||
let mut in_script_tag = false;
|
||||
|
||||
// Iterate over all comments in the document.
|
||||
for range in indexer.comment_ranges() {
|
||||
let line = locator.lines(*range);
|
||||
|
||||
// Detect `/// script` tags.
|
||||
if in_script_tag {
|
||||
if is_script_tag_end(line) {
|
||||
in_script_tag = false;
|
||||
}
|
||||
} else {
|
||||
if is_script_tag_start(line) {
|
||||
in_script_tag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip comments within `/// script` tags.
|
||||
if in_script_tag {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
|
||||
diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion(
|
||||
locator.full_lines_range(*range),
|
||||
)));
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if line contains an own-line comment.
|
||||
fn is_own_line_comment(line: &str) -> bool {
|
||||
for char in line.chars() {
|
||||
if char == '#' {
|
||||
return true;
|
||||
@@ -55,23 +97,16 @@ fn is_standalone_comment(line: &str) -> bool {
|
||||
unreachable!("Comment should contain '#' character")
|
||||
}
|
||||
|
||||
/// ERA001
|
||||
pub(crate) fn commented_out_code(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
for range in indexer.comment_ranges() {
|
||||
let line = locator.full_lines(*range);
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
|
||||
diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion(
|
||||
locator.full_lines_range(*range),
|
||||
)));
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
/// Returns `true` if the line appears to start a script tag.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
fn is_script_tag_start(line: &str) -> bool {
|
||||
line == "# /// script"
|
||||
}
|
||||
|
||||
/// Returns `true` if the line appears to start a script tag.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
fn is_script_tag_end(line: &str) -> bool {
|
||||
line == "# ///"
|
||||
}
|
||||
|
||||
@@ -245,6 +245,7 @@ ERA001.py:36:1: ERA001 Found commented-out code
|
||||
36 |-# except:
|
||||
37 36 | # except Foo:
|
||||
38 37 | # except Exception as e: print(e)
|
||||
39 38 |
|
||||
|
||||
ERA001.py:37:1: ERA001 Found commented-out code
|
||||
|
|
||||
@@ -262,6 +263,8 @@ ERA001.py:37:1: ERA001 Found commented-out code
|
||||
36 36 | # except:
|
||||
37 |-# except Foo:
|
||||
38 37 | # except Exception as e: print(e)
|
||||
39 38 |
|
||||
40 39 |
|
||||
|
||||
ERA001.py:38:1: ERA001 Found commented-out code
|
||||
|
|
||||
@@ -277,3 +280,44 @@ ERA001.py:38:1: ERA001 Found commented-out code
|
||||
36 36 | # except:
|
||||
37 37 | # except Foo:
|
||||
38 |-# except Exception as e: print(e)
|
||||
39 38 |
|
||||
40 39 |
|
||||
41 40 | # Script tag without an opening tag (Error)
|
||||
|
||||
ERA001.py:44:1: ERA001 Found commented-out code
|
||||
|
|
||||
43 | # requires-python = ">=3.11"
|
||||
44 | # dependencies = [
|
||||
| ^^^^^^^^^^^^^^^^^^ ERA001
|
||||
45 | # "requests<3",
|
||||
46 | # "rich",
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
41 41 | # Script tag without an opening tag (Error)
|
||||
42 42 |
|
||||
43 43 | # requires-python = ">=3.11"
|
||||
44 |-# dependencies = [
|
||||
45 44 | # "requests<3",
|
||||
46 45 | # "rich",
|
||||
47 46 | # ]
|
||||
|
||||
ERA001.py:47:1: ERA001 Found commented-out code
|
||||
|
|
||||
45 | # "requests<3",
|
||||
46 | # "rich",
|
||||
47 | # ]
|
||||
| ^^^ ERA001
|
||||
48 | # ///
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
44 44 | # dependencies = [
|
||||
45 45 | # "requests<3",
|
||||
46 46 | # "rich",
|
||||
47 |-# ]
|
||||
48 47 | # ///
|
||||
49 48 |
|
||||
50 49 | # Script tag (OK)
|
||||
|
||||
@@ -80,9 +80,7 @@ pub(crate) fn compare_to_hardcoded_password_string(
|
||||
.diagnostics
|
||||
.extend(comparators.iter().filter_map(|comp| {
|
||||
string_literal(comp).filter(|string| !string.is_empty())?;
|
||||
let Some(name) = password_target(left) else {
|
||||
return None;
|
||||
};
|
||||
let name = password_target(left)?;
|
||||
Some(Diagnostic::new(
|
||||
HardcodedPasswordString {
|
||||
name: name.to_string(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr};
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr, Operator};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -44,30 +44,6 @@ impl Violation for ExceptWithNonExceptionClasses {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an [`Expr`], flatten any [`Expr::Starred`] expressions.
|
||||
/// This should leave any unstarred iterables alone (subsequently raising a
|
||||
/// warning for B029).
|
||||
fn flatten_starred_iterables(expr: &Expr) -> Vec<&Expr> {
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = expr else {
|
||||
return vec![expr];
|
||||
};
|
||||
let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(elts.len());
|
||||
let mut exprs_to_process: VecDeque<&Expr> = elts.iter().collect();
|
||||
while let Some(expr) = exprs_to_process.pop_front() {
|
||||
match expr {
|
||||
Expr::Starred(ast::ExprStarred { value, .. }) => match value.as_ref() {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
| Expr::List(ast::ExprList { elts, .. }) => {
|
||||
exprs_to_process.append(&mut elts.iter().collect());
|
||||
}
|
||||
_ => flattened_exprs.push(value),
|
||||
},
|
||||
_ => flattened_exprs.push(expr),
|
||||
}
|
||||
}
|
||||
flattened_exprs
|
||||
}
|
||||
|
||||
/// B030
|
||||
pub(crate) fn except_with_non_exception_classes(
|
||||
checker: &mut Checker,
|
||||
@@ -78,7 +54,7 @@ pub(crate) fn except_with_non_exception_classes(
|
||||
let Some(type_) = type_ else {
|
||||
return;
|
||||
};
|
||||
for expr in flatten_starred_iterables(type_) {
|
||||
for expr in flatten_iterables(type_) {
|
||||
if !matches!(
|
||||
expr,
|
||||
Expr::Subscript(_) | Expr::Attribute(_) | Expr::Name(_) | Expr::Call(_),
|
||||
@@ -89,3 +65,61 @@ pub(crate) fn except_with_non_exception_classes(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an [`Expr`], flatten any [`Expr::Starred`] expressions and any
|
||||
/// [`Expr::BinOp`] expressions into a flat list of expressions.
|
||||
///
|
||||
/// This should leave any unstarred iterables alone (subsequently raising a
|
||||
/// warning for B029).
|
||||
fn flatten_iterables(expr: &Expr) -> Vec<&Expr> {
|
||||
// Unpack the top-level Tuple into queue, otherwise add as-is.
|
||||
let mut exprs_to_process: VecDeque<&Expr> = match expr {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.iter().collect(),
|
||||
_ => vec![expr].into(),
|
||||
};
|
||||
let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(exprs_to_process.len());
|
||||
|
||||
while let Some(expr) = exprs_to_process.pop_front() {
|
||||
match expr {
|
||||
Expr::Starred(ast::ExprStarred { value, .. }) => match value.as_ref() {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||
| Expr::List(ast::ExprList { elts, .. }) => {
|
||||
exprs_to_process.append(&mut elts.iter().collect());
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::Add, ..
|
||||
}) => {
|
||||
exprs_to_process.push_back(value);
|
||||
}
|
||||
_ => flattened_exprs.push(value),
|
||||
},
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
right,
|
||||
op: Operator::Add,
|
||||
..
|
||||
}) => {
|
||||
for expr in [left, right] {
|
||||
// If left or right are tuples, starred, or binary operators, flatten them.
|
||||
match expr.as_ref() {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
exprs_to_process.append(&mut elts.iter().collect());
|
||||
}
|
||||
Expr::Starred(ast::ExprStarred { value, .. }) => {
|
||||
exprs_to_process.push_back(value);
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::Add, ..
|
||||
}) => {
|
||||
exprs_to_process.push_back(expr);
|
||||
}
|
||||
_ => flattened_exprs.push(expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => flattened_exprs.push(expr),
|
||||
}
|
||||
}
|
||||
|
||||
flattened_exprs
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ B030.py:12:8: B030 `except` handlers should only be exception classes or tuples
|
||||
|
|
||||
10 | try:
|
||||
11 | pass
|
||||
12 | except 1: # error
|
||||
12 | except 1: # Error
|
||||
| ^ B030
|
||||
13 | pass
|
||||
|
|
||||
@@ -14,7 +14,7 @@ B030.py:17:9: B030 `except` handlers should only be exception classes or tuples
|
||||
|
|
||||
15 | try:
|
||||
16 | pass
|
||||
17 | except (1, ValueError): # error
|
||||
17 | except (1, ValueError): # Error
|
||||
| ^ B030
|
||||
18 | pass
|
||||
|
|
||||
@@ -23,7 +23,7 @@ B030.py:22:21: B030 `except` handlers should only be exception classes or tuples
|
||||
|
|
||||
20 | try:
|
||||
21 | pass
|
||||
22 | except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
|
||||
22 | except (ValueError, (RuntimeError, (KeyError, TypeError))): # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B030
|
||||
23 | pass
|
||||
|
|
||||
@@ -32,7 +32,7 @@ B030.py:27:37: B030 `except` handlers should only be exception classes or tuples
|
||||
|
|
||||
25 | try:
|
||||
26 | pass
|
||||
27 | except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
27 | except (ValueError, *(RuntimeError, (KeyError, TypeError))): # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ B030
|
||||
28 | pass
|
||||
|
|
||||
@@ -41,9 +41,25 @@ B030.py:33:29: B030 `except` handlers should only be exception classes or tuples
|
||||
|
|
||||
31 | try:
|
||||
32 | pass
|
||||
33 | except (*a, *(RuntimeError, (KeyError, TypeError))): # error
|
||||
33 | except (*a, *(RuntimeError, (KeyError, TypeError))): # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ B030
|
||||
34 | pass
|
||||
|
|
||||
|
||||
B030.py:39:28: B030 `except` handlers should only be exception classes or tuples of exception classes
|
||||
|
|
||||
37 | try:
|
||||
38 | pass
|
||||
39 | except* a + (RuntimeError, (KeyError, TypeError)): # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ B030
|
||||
40 | pass
|
||||
|
|
||||
|
||||
B030.py:131:8: B030 `except` handlers should only be exception classes or tuples of exception classes
|
||||
|
|
||||
129 | try:
|
||||
130 | pass
|
||||
131 | except (a, b) * (c, d): # B030
|
||||
| ^^^^^^^^^^^^^^^ B030
|
||||
132 | pass
|
||||
|
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::ExprGenerator;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -9,37 +11,53 @@ use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary generators that can be rewritten as `list`
|
||||
/// comprehensions.
|
||||
/// comprehensions (or with `list` directly).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `list` around a generator expression, since
|
||||
/// there are equivalent comprehensions for these types. Using a
|
||||
/// comprehension is clearer and more idiomatic.
|
||||
///
|
||||
/// Further, if the comprehension can be removed entirely, as in the case of
|
||||
/// `list(x for x in foo)`, it's better to use `list(foo)` directly, since it's
|
||||
/// even more direct.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// list(f(x) for x in foo)
|
||||
/// list(x for x in foo)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// [f(x) for x in foo]
|
||||
/// list(foo)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
|
||||
/// when rewriting the call. In most cases, though, comments will be preserved.
|
||||
#[violation]
|
||||
pub struct UnnecessaryGeneratorList;
|
||||
pub struct UnnecessaryGeneratorList {
|
||||
short_circuit: bool,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for UnnecessaryGeneratorList {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary generator (rewrite as a `list` comprehension)")
|
||||
if self.short_circuit {
|
||||
format!("Unnecessary generator (rewrite using `list()`)")
|
||||
} else {
|
||||
format!("Unnecessary generator (rewrite as a `list` comprehension)")
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Rewrite as a `list` comprehension".to_string()
|
||||
if self.short_circuit {
|
||||
"Rewrite using `list()`".to_string()
|
||||
} else {
|
||||
"Rewrite as a `list` comprehension".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,28 +74,59 @@ pub(crate) fn unnecessary_generator_list(checker: &mut Checker, call: &ast::Expr
|
||||
if !checker.semantic().is_builtin("list") {
|
||||
return;
|
||||
}
|
||||
if argument.is_generator_expr() {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, call.range());
|
||||
|
||||
// Convert `list(x for x in y)` to `[x for x in y]`.
|
||||
diagnostic.set_fix({
|
||||
// Replace `list(` with `[`.
|
||||
let call_start = Edit::replacement(
|
||||
"[".to_string(),
|
||||
call.start(),
|
||||
call.arguments.start() + TextSize::from(1),
|
||||
);
|
||||
let Some(ExprGenerator {
|
||||
elt, generators, ..
|
||||
}) = argument.as_generator_expr()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Replace `)` with `]`.
|
||||
let call_end = Edit::replacement(
|
||||
"]".to_string(),
|
||||
call.arguments.end() - TextSize::from(1),
|
||||
call.end(),
|
||||
);
|
||||
|
||||
Fix::unsafe_edits(call_start, [call_end])
|
||||
});
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
// Short-circuit: given `list(x for x in y)`, generate `list(y)` (in lieu of `[x for x in y]`).
|
||||
if let [generator] = generators.as_slice() {
|
||||
if generator.ifs.is_empty() && !generator.is_async {
|
||||
if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryGeneratorList {
|
||||
short_circuit: true,
|
||||
},
|
||||
call.range(),
|
||||
);
|
||||
let iterator = format!("list({})", checker.locator().slice(generator.iter.range()));
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
iterator,
|
||||
call.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert `list(f(x) for x in y)` to `[f(x) for x in y]`.
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryGeneratorList {
|
||||
short_circuit: false,
|
||||
},
|
||||
call.range(),
|
||||
);
|
||||
diagnostic.set_fix({
|
||||
// Replace `list(` with `[`.
|
||||
let call_start = Edit::replacement(
|
||||
"[".to_string(),
|
||||
call.start(),
|
||||
call.arguments.start() + TextSize::from(1),
|
||||
);
|
||||
|
||||
// Replace `)` with `]`.
|
||||
let call_end = Edit::replacement(
|
||||
"]".to_string(),
|
||||
call.arguments.end() - TextSize::from(1),
|
||||
call.end(),
|
||||
);
|
||||
|
||||
Fix::unsafe_edits(call_start, [call_end])
|
||||
});
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -71,9 +72,12 @@ pub(crate) fn unnecessary_literal_within_tuple_call(checker: &mut Checker, call:
|
||||
if !call.arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(argument) =
|
||||
helpers::first_argument_with_matching_function("tuple", &call.func, &call.arguments.args)
|
||||
else {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function(
|
||||
"tuple",
|
||||
&call.func,
|
||||
&call.arguments.args,
|
||||
&call.arguments.keywords,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("tuple") {
|
||||
@@ -92,23 +96,41 @@ pub(crate) fn unnecessary_literal_within_tuple_call(checker: &mut Checker, call:
|
||||
call.range(),
|
||||
);
|
||||
|
||||
// Convert `tuple([1, 2])` to `tuple(1, 2)`
|
||||
// Convert `tuple([1, 2])` to `(1, 2)`
|
||||
diagnostic.set_fix({
|
||||
// Replace from the start of the call to the start of the inner list or tuple with `(`.
|
||||
let call_start = Edit::replacement(
|
||||
"(".to_string(),
|
||||
let elts = match argument {
|
||||
Expr::List(ast::ExprList { elts, .. }) => elts.as_slice(),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts.as_slice(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let needs_trailing_comma = if let [item] = elts {
|
||||
SimpleTokenizer::new(
|
||||
checker.locator().contents(),
|
||||
TextRange::new(item.end(), call.end()),
|
||||
)
|
||||
.all(|token| token.kind != SimpleTokenKind::Comma)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Replace `[` with `(`.
|
||||
let elt_start = Edit::replacement(
|
||||
"(".into(),
|
||||
call.start(),
|
||||
argument.start() + TextSize::from(1),
|
||||
);
|
||||
|
||||
// Replace from the end of the inner list or tuple to the end of the call with `)`.
|
||||
let call_end = Edit::replacement(
|
||||
")".to_string(),
|
||||
// Replace `]` with `)` or `,)`.
|
||||
let elt_end = Edit::replacement(
|
||||
if needs_trailing_comma {
|
||||
",)".into()
|
||||
} else {
|
||||
")".into()
|
||||
},
|
||||
argument.end() - TextSize::from(1),
|
||||
call.end(),
|
||||
);
|
||||
|
||||
Fix::unsafe_edits(call_start, [call_end])
|
||||
Fix::unsafe_edits(elt_start, [elt_end])
|
||||
});
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -1,42 +1,90 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
---
|
||||
C400.py:1:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
||||
C400.py:2:13: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
||||
|
|
||||
1 | x = list(x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
|
||||
2 | x = list(
|
||||
3 | x for x in range(3)
|
||||
1 | # Cannot combine with C416. Should use list comprehension here.
|
||||
2 | even_nums = list(2 * x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C400
|
||||
3 | odd_nums = list(
|
||||
4 | 2 * x + 1 for x in range(3)
|
||||
|
|
||||
= help: Rewrite as a `list` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-x = list(x for x in range(3))
|
||||
1 |+x = [x for x in range(3)]
|
||||
2 2 | x = list(
|
||||
3 3 | x for x in range(3)
|
||||
4 4 | )
|
||||
1 1 | # Cannot combine with C416. Should use list comprehension here.
|
||||
2 |-even_nums = list(2 * x for x in range(3))
|
||||
2 |+even_nums = [2 * x for x in range(3)]
|
||||
3 3 | odd_nums = list(
|
||||
4 4 | 2 * x + 1 for x in range(3)
|
||||
5 5 | )
|
||||
|
||||
C400.py:2:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
||||
C400.py:3:12: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
||||
|
|
||||
1 | x = list(x for x in range(3))
|
||||
2 | x = list(
|
||||
| _____^
|
||||
3 | | x for x in range(3)
|
||||
4 | | )
|
||||
1 | # Cannot combine with C416. Should use list comprehension here.
|
||||
2 | even_nums = list(2 * x for x in range(3))
|
||||
3 | odd_nums = list(
|
||||
| ____________^
|
||||
4 | | 2 * x + 1 for x in range(3)
|
||||
5 | | )
|
||||
| |_^ C400
|
||||
|
|
||||
= help: Rewrite as a `list` comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | x = list(x for x in range(3))
|
||||
2 |-x = list(
|
||||
2 |+x = [
|
||||
3 3 | x for x in range(3)
|
||||
4 |-)
|
||||
4 |+]
|
||||
5 5 |
|
||||
1 1 | # Cannot combine with C416. Should use list comprehension here.
|
||||
2 2 | even_nums = list(2 * x for x in range(3))
|
||||
3 |-odd_nums = list(
|
||||
3 |+odd_nums = [
|
||||
4 4 | 2 * x + 1 for x in range(3)
|
||||
5 |-)
|
||||
5 |+]
|
||||
6 6 |
|
||||
7 7 | def list(*args, **kwargs):
|
||||
7 7 |
|
||||
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
|
||||
C400.py:9:5: C400 [*] Unnecessary generator (rewrite using `list()`)
|
||||
|
|
||||
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
9 | x = list(x for x in range(3))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
|
||||
10 | x = list(
|
||||
11 | x for x in range(3)
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
9 |-x = list(x for x in range(3))
|
||||
9 |+x = list(range(3))
|
||||
10 10 | x = list(
|
||||
11 11 | x for x in range(3)
|
||||
12 12 | )
|
||||
|
||||
C400.py:10:5: C400 [*] Unnecessary generator (rewrite using `list()`)
|
||||
|
|
||||
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
9 | x = list(x for x in range(3))
|
||||
10 | x = list(
|
||||
| _____^
|
||||
11 | | x for x in range(3)
|
||||
12 | | )
|
||||
| |_^ C400
|
||||
13 |
|
||||
14 | # Not built-in list.
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 |
|
||||
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
|
||||
9 9 | x = list(x for x in range(3))
|
||||
10 |-x = list(
|
||||
11 |- x for x in range(3)
|
||||
12 |-)
|
||||
10 |+x = list(range(3))
|
||||
13 11 |
|
||||
14 12 | # Not built-in list.
|
||||
15 13 | def list(*args, **kwargs):
|
||||
|
||||
@@ -143,6 +143,8 @@ C409.py:16:1: C409 [*] Unnecessary `list` literal passed to `tuple()` (rewrite a
|
||||
17 | | 1, 2
|
||||
18 | | ])
|
||||
| |__^ C409
|
||||
19 |
|
||||
20 | tuple((
|
||||
|
|
||||
= help: Rewrite as a `tuple` literal
|
||||
|
||||
@@ -155,5 +157,85 @@ C409.py:16:1: C409 [*] Unnecessary `list` literal passed to `tuple()` (rewrite a
|
||||
17 17 | 1, 2
|
||||
18 |-])
|
||||
18 |+)
|
||||
19 19 |
|
||||
20 20 | tuple((
|
||||
21 21 | 1,
|
||||
|
||||
C409.py:20:1: C409 [*] Unnecessary `tuple` literal passed to `tuple()` (remove the outer call to `tuple()`)
|
||||
|
|
||||
18 | ])
|
||||
19 |
|
||||
20 | / tuple((
|
||||
21 | | 1,
|
||||
22 | | ))
|
||||
| |__^ C409
|
||||
23 |
|
||||
24 | t6 = tuple([1])
|
||||
|
|
||||
= help: Remove outer `tuple` call
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 | 1, 2
|
||||
18 18 | ])
|
||||
19 19 |
|
||||
20 |-tuple((
|
||||
20 |+(
|
||||
21 21 | 1,
|
||||
22 |-))
|
||||
22 |+)
|
||||
23 23 |
|
||||
24 24 | t6 = tuple([1])
|
||||
25 25 | t7 = tuple((1,))
|
||||
|
||||
C409.py:24:6: C409 [*] Unnecessary `list` literal passed to `tuple()` (rewrite as a `tuple` literal)
|
||||
|
|
||||
22 | ))
|
||||
23 |
|
||||
24 | t6 = tuple([1])
|
||||
| ^^^^^^^^^^ C409
|
||||
25 | t7 = tuple((1,))
|
||||
26 | t8 = tuple([1,])
|
||||
|
|
||||
= help: Rewrite as a `tuple` literal
|
||||
|
||||
ℹ Unsafe fix
|
||||
21 21 | 1,
|
||||
22 22 | ))
|
||||
23 23 |
|
||||
24 |-t6 = tuple([1])
|
||||
24 |+t6 = (1,)
|
||||
25 25 | t7 = tuple((1,))
|
||||
26 26 | t8 = tuple([1,])
|
||||
|
||||
C409.py:25:6: C409 [*] Unnecessary `tuple` literal passed to `tuple()` (remove the outer call to `tuple()`)
|
||||
|
|
||||
24 | t6 = tuple([1])
|
||||
25 | t7 = tuple((1,))
|
||||
| ^^^^^^^^^^^ C409
|
||||
26 | t8 = tuple([1,])
|
||||
|
|
||||
= help: Remove outer `tuple` call
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 | ))
|
||||
23 23 |
|
||||
24 24 | t6 = tuple([1])
|
||||
25 |-t7 = tuple((1,))
|
||||
25 |+t7 = (1,)
|
||||
26 26 | t8 = tuple([1,])
|
||||
|
||||
C409.py:26:6: C409 [*] Unnecessary `list` literal passed to `tuple()` (rewrite as a `tuple` literal)
|
||||
|
|
||||
24 | t6 = tuple([1])
|
||||
25 | t7 = tuple((1,))
|
||||
26 | t8 = tuple([1,])
|
||||
| ^^^^^^^^^^^ C409
|
||||
|
|
||||
= help: Rewrite as a `tuple` literal
|
||||
|
||||
ℹ Unsafe fix
|
||||
23 23 |
|
||||
24 24 | t6 = tuple([1])
|
||||
25 25 | t7 = tuple((1,))
|
||||
26 |-t8 = tuple([1,])
|
||||
26 |+t8 = (1,)
|
||||
|
||||
@@ -159,9 +159,7 @@ fn get_element_type(element: &Stmt, semantic: &SemanticModel) -> Option<ContentT
|
||||
return Some(ContentType::FieldDeclaration);
|
||||
}
|
||||
}
|
||||
let Some(expr) = targets.first() else {
|
||||
return None;
|
||||
};
|
||||
let expr = targets.first()?;
|
||||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -61,15 +61,9 @@ pub(crate) fn unconventional_import_alias(
|
||||
binding: &Binding,
|
||||
conventions: &FxHashMap<String, String>,
|
||||
) -> Option<Diagnostic> {
|
||||
let Some(import) = binding.as_any_import() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let import = binding.as_any_import()?;
|
||||
let qualified_name = import.qualified_name().to_string();
|
||||
|
||||
let Some(expected_alias) = conventions.get(qualified_name.as_str()) else {
|
||||
return None;
|
||||
};
|
||||
let expected_alias = conventions.get(qualified_name.as_str())?;
|
||||
|
||||
let name = binding.name(checker.locator());
|
||||
if binding.is_alias() && name == expected_alias {
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging/mod.rs
|
||||
---
|
||||
LOG009.py:3:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
LOG009.py:4:5: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
|
|
||||
1 | import logging
|
||||
2 |
|
||||
3 | logging.WARN # LOG009
|
||||
| ^^^^^^^^^^^^ LOG009
|
||||
4 | logging.WARNING # OK
|
||||
2 | import logging
|
||||
3 |
|
||||
4 | logging.WARN # LOG009
|
||||
| ^^^^^^^^^^^^ LOG009
|
||||
5 | logging.WARNING # OK
|
||||
|
|
||||
= help: Replace `logging.WARN` with `logging.WARNING`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 |-logging.WARN # LOG009
|
||||
3 |+logging.WARNING # LOG009
|
||||
4 4 | logging.WARNING # OK
|
||||
5 5 |
|
||||
6 6 | from logging import WARN, WARNING
|
||||
|
||||
LOG009.py:8:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
|
|
||||
6 | from logging import WARN, WARNING
|
||||
7 |
|
||||
8 | WARN # LOG009
|
||||
| ^^^^ LOG009
|
||||
9 | WARNING # OK
|
||||
|
|
||||
= help: Replace `logging.WARN` with `logging.WARNING`
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | from logging import WARN, WARNING
|
||||
1 1 | def func():
|
||||
2 2 | import logging
|
||||
3 3 |
|
||||
4 |- logging.WARN # LOG009
|
||||
4 |+ logging.WARNING # LOG009
|
||||
5 5 | logging.WARNING # OK
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 |-WARN # LOG009
|
||||
8 |+logging.WARNING # LOG009
|
||||
9 9 | WARNING # OK
|
||||
|
||||
LOG009.py:11:5: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
|
|
||||
9 | from logging import WARN, WARNING
|
||||
10 |
|
||||
11 | WARN # LOG009
|
||||
| ^^^^ LOG009
|
||||
12 | WARNING # OK
|
||||
|
|
||||
= help: Replace `logging.WARN` with `logging.WARNING`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | def func():
|
||||
9 9 | from logging import WARN, WARNING
|
||||
10 10 |
|
||||
11 |- WARN # LOG009
|
||||
11 |+ WARNING # LOG009
|
||||
12 12 | WARNING # OK
|
||||
|
||||
@@ -87,6 +87,12 @@ pub(crate) fn unnecessary_placeholder(checker: &mut Checker, body: &[Stmt]) {
|
||||
let kind = match stmt {
|
||||
Stmt::Pass(_) => Placeholder::Pass,
|
||||
Stmt::Expr(expr) if expr.value.is_ellipsis_literal_expr() => {
|
||||
// In a type-checking block, a trailing ellipsis might be meaningful. A
|
||||
// user might be using the type-checking context to declare a stub.
|
||||
if checker.semantic().in_type_checking_block() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ellipses are significant in protocol methods and abstract methods. Specifically,
|
||||
// Pyright uses the presence of an ellipsis to indicate that a method is a stub,
|
||||
// rather than a default implementation.
|
||||
|
||||
@@ -8,28 +8,34 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as
|
||||
/// the type annotation for the `obj` parameter.
|
||||
/// the type annotation for their second parameter.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The Python documentation recommends the use of `object` to "indicate that a
|
||||
/// value could be any type in a typesafe manner", while `Any` should be used to
|
||||
/// "indicate that a value is dynamically typed."
|
||||
/// value could be any type in a typesafe manner". `Any`, on the other hand,
|
||||
/// should be seen as an "escape hatch when you need to mix dynamically and
|
||||
/// statically typed code". Since using `Any` allows you to write highly unsafe
|
||||
/// code, you should generally only use `Any` when the semantics of your code
|
||||
/// would otherwise be inexpressible to the type checker.
|
||||
///
|
||||
/// The semantics of `__eq__` and `__ne__` are such that the `obj` parameter
|
||||
/// should be any type, as opposed to a dynamically typed value. Therefore, the
|
||||
/// `object` type annotation is more appropriate.
|
||||
/// The expectation in Python is that a comparison of two arbitrary objects
|
||||
/// using `==` or `!=` should never raise an exception. This contract can be
|
||||
/// fully expressed in the type system and does not involve requesting unsound
|
||||
/// behaviour from a type checker. As such, `object` is a more appropriate
|
||||
/// annotation than `Any` for the second parameter of the methods implementing
|
||||
/// these comparison operators -- `__eq__` and `__ne__`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __eq__(self, obj: typing.Any):
|
||||
/// def __eq__(self, obj: typing.Any) -> bool:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __eq__(self, obj: object):
|
||||
/// def __eq__(self, obj: object) -> bool:
|
||||
/// ...
|
||||
/// ```
|
||||
/// ## References
|
||||
|
||||
@@ -13,16 +13,17 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Why is this bad?
|
||||
/// `typing.NamedTuple` is the "typed version" of `collections.namedtuple`.
|
||||
///
|
||||
/// The class generated by subclassing `typing.NamedTuple` is equivalent to
|
||||
/// `collections.namedtuple`, with the exception that `typing.NamedTuple`
|
||||
/// includes an `__annotations__` attribute, which allows type checkers to
|
||||
/// infer the types of the fields.
|
||||
/// Inheriting from `typing.NamedTuple` creates a custom `tuple` subclass in
|
||||
/// the same way as using the `collections.namedtuple` factory function.
|
||||
/// However, using `typing.NamedTuple` allows you to provide a type annotation
|
||||
/// for each field in the class. This means that type checkers will have more
|
||||
/// information to work with, and will be able to analyze your code more
|
||||
/// precisely.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from collections import namedtuple
|
||||
///
|
||||
///
|
||||
/// person = namedtuple("Person", ["name", "age"])
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -20,18 +20,28 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// a = b = int
|
||||
/// a.b = int
|
||||
///
|
||||
///
|
||||
/// class Klass:
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// Klass.X: TypeAlias = int
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// a: TypeAlias = int
|
||||
/// b: TypeAlias = int
|
||||
///
|
||||
///
|
||||
/// class a:
|
||||
/// b: int
|
||||
/// class Klass:
|
||||
/// X: TypeAlias = int
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct ComplexAssignmentInStub;
|
||||
|
||||
@@ -10,16 +10,16 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for `if` statements with complex conditionals in stubs.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Stub files support simple conditionals to test for differences in Python
|
||||
/// versions and platforms. However, type checkers only understand a limited
|
||||
/// subset of these conditionals; complex conditionals may result in false
|
||||
/// positives or false negatives.
|
||||
/// Type checkers understand simple conditionals to express variations between
|
||||
/// different Python versions and platforms. However, complex tests may not be
|
||||
/// understood by a type checker, leading to incorrect inferences when they
|
||||
/// analyze your code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if (2, 7) < sys.version_info < (3, 5):
|
||||
/// if (3, 10) <= sys.version_info < (3, 12):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
@@ -27,9 +27,12 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info < (3, 5):
|
||||
/// if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// The [typing documentation on stub files](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct ComplexIfStatementInStub;
|
||||
|
||||
|
||||
@@ -19,25 +19,32 @@ use crate::checkers::ast::Checker;
|
||||
/// methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Improperly-annotated `__exit__` and `__aexit__` methods can cause
|
||||
/// Improperly annotated `__exit__` and `__aexit__` methods can cause
|
||||
/// unexpected behavior when interacting with type checkers.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from types import TracebackType
|
||||
///
|
||||
///
|
||||
/// class Foo:
|
||||
/// def __exit__(self, typ, exc, tb, extra_arg) -> None:
|
||||
/// def __exit__(
|
||||
/// self, typ: BaseException, exc: BaseException, tb: TracebackType
|
||||
/// ) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from types import TracebackType
|
||||
///
|
||||
///
|
||||
/// class Foo:
|
||||
/// def __exit__(
|
||||
/// self,
|
||||
/// typ: type[BaseException] | None,
|
||||
/// exc: BaseException | None,
|
||||
/// tb: TracebackType | None,
|
||||
/// extra_arg: int = 0,
|
||||
/// ) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
|
||||
@@ -10,9 +10,10 @@ use crate::checkers::ast::Checker;
|
||||
/// statement in stub files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Stub files are already evaluated under `annotations` semantics. As such,
|
||||
/// the `from __future__ import annotations` import statement has no effect
|
||||
/// and should be omitted.
|
||||
/// Stub files natively support forward references in all contexts, as stubs are
|
||||
/// never executed at runtime. (They should be thought of as "data files" for
|
||||
/// type checkers.) As such, the `from __future__ import annotations` import
|
||||
/// statement has no effect and should be omitted.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)
|
||||
|
||||
@@ -15,24 +15,46 @@ use crate::checkers::ast::Checker;
|
||||
/// `__iter__` methods should always should return an `Iterator` of some kind,
|
||||
/// not an `Iterable`.
|
||||
///
|
||||
/// In Python, an `Iterator` is an object that has a `__next__` method, which
|
||||
/// provides a consistent interface for sequentially processing elements from
|
||||
/// a sequence or other iterable object. Meanwhile, an `Iterable` is an object
|
||||
/// with an `__iter__` method, which itself returns an `Iterator`.
|
||||
/// In Python, an `Iterable` is an object that has an `__iter__` method; an
|
||||
/// `Iterator` is an object that has `__iter__` and `__next__` methods. All
|
||||
/// `__iter__` methods are expected to return `Iterator`s. Type checkers may
|
||||
/// not always recognize an object as being iterable if its `__iter__` method
|
||||
/// does not return an `Iterator`.
|
||||
///
|
||||
/// Every `Iterator` is an `Iterable`, but not every `Iterable` is an `Iterator`.
|
||||
/// By returning an `Iterable` from `__iter__`, you may end up returning an
|
||||
/// object that doesn't implement `__next__`, which will cause a `TypeError`
|
||||
/// at runtime. For example, returning a `list` from `__iter__` will cause
|
||||
/// a `TypeError` when you call `__next__` on it, as a `list` is an `Iterable`,
|
||||
/// but not an `Iterator`.
|
||||
/// For example, `list` is an `Iterable`, but not an `Iterator`; you can obtain
|
||||
/// an iterator over a list's elements by passing the list to `iter()`:
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> import collections.abc
|
||||
/// >>> x = [42]
|
||||
/// >>> isinstance(x, collections.abc.Iterable)
|
||||
/// True
|
||||
/// >>> isinstance(x, collections.abc.Iterator)
|
||||
/// False
|
||||
/// >>> next(x)
|
||||
/// Traceback (most recent call last):
|
||||
/// File "<stdin>", line 1, in <module>
|
||||
/// TypeError: 'list' object is not an iterator
|
||||
/// >>> y = iter(x)
|
||||
/// >>> isinstance(y, collections.abc.Iterable)
|
||||
/// True
|
||||
/// >>> isinstance(y, collections.abc.Iterator)
|
||||
/// True
|
||||
/// >>> next(y)
|
||||
/// 42
|
||||
/// ```
|
||||
///
|
||||
/// Using `Iterable` rather than `Iterator` as a return type for an `__iter__`
|
||||
/// methods would imply that you would not necessarily be able to call `next()`
|
||||
/// on the returned object, violating the expectations of the interface.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import collections.abc
|
||||
///
|
||||
///
|
||||
/// class Class:
|
||||
/// class Klass:
|
||||
/// def __iter__(self) -> collections.abc.Iterable[str]:
|
||||
/// ...
|
||||
/// ```
|
||||
@@ -42,7 +64,7 @@ use crate::checkers::ast::Checker;
|
||||
/// import collections.abc
|
||||
///
|
||||
///
|
||||
/// class Class:
|
||||
/// class Klass:
|
||||
/// def __iter__(self) -> collections.abc.Iterator[str]:
|
||||
/// ...
|
||||
/// ```
|
||||
|
||||
@@ -9,19 +9,22 @@ use crate::checkers::ast::Checker;
|
||||
use crate::settings::types::PythonVersion::Py311;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) in
|
||||
/// stubs.
|
||||
/// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) for
|
||||
/// parameter annotations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Prefer `typing.Never` (or `typing_extensions.Never`) over `typing.NoReturn`,
|
||||
/// as the former is more explicit about the intent of the annotation. This is
|
||||
/// a purely stylistic choice, as the two are semantically equivalent.
|
||||
/// Prefer `Never` over `NoReturn` for parameter annotations. `Never` has a
|
||||
/// clearer name in these contexts, since it makes little sense to talk about a
|
||||
/// parameter annotation "not returning".
|
||||
///
|
||||
/// This is a purely stylistic lint: the two types have identical semantics for
|
||||
/// type checkers. Both represent Python's "[bottom type]" (a type that has no
|
||||
/// members).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import NoReturn
|
||||
///
|
||||
///
|
||||
/// def foo(x: NoReturn): ...
|
||||
/// ```
|
||||
///
|
||||
@@ -29,13 +32,14 @@ use crate::settings::types::PythonVersion::Py311;
|
||||
/// ```python
|
||||
/// from typing import Never
|
||||
///
|
||||
///
|
||||
/// def foo(x: Never): ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Never`](https://docs.python.org/3/library/typing.html#typing.Never)
|
||||
/// - [Python documentation: `typing.NoReturn`](https://docs.python.org/3/library/typing.html#typing.NoReturn)
|
||||
///
|
||||
/// [bottom type]: https://en.wikipedia.org/wiki/Bottom_type
|
||||
#[violation]
|
||||
pub struct NoReturnArgumentAnnotationInStub {
|
||||
module: TypingModule,
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for non-empty function stub bodies.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Stub files are meant to be used as a reference for the interface of a
|
||||
/// module, and should not contain any implementation details. Thus, the
|
||||
/// body of a stub function should be empty.
|
||||
/// Stub files are never executed at runtime; they should be thought of as
|
||||
/// "data files" for type checkers or IDEs. Function bodies are redundant
|
||||
/// for this purpose.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -26,7 +26,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 484 – Type Hints: Stub Files](https://www.python.org/dev/peps/pep-0484/#stub-files)
|
||||
/// - [The recommended style for stub functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#id6)
|
||||
/// in the typing docs.
|
||||
#[violation]
|
||||
pub struct NonEmptyStubBody;
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for methods that are annotated with a fixed return type, which
|
||||
/// should instead be returning `self`.
|
||||
/// Checks for methods that are annotated with a fixed return type which
|
||||
/// should instead be returning `Self`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If methods like `__new__` or `__enter__` are annotated with a fixed return
|
||||
/// type, and the class is subclassed, type checkers will not be able to infer
|
||||
/// the correct return type.
|
||||
/// If methods that generally return `self` at runtime are annotated with a
|
||||
/// fixed return type, and the class is subclassed, type checkers will not be
|
||||
/// able to infer the correct return type.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
@@ -30,7 +30,7 @@ use crate::checkers::ast::Checker;
|
||||
/// self.radius = radius
|
||||
/// return self
|
||||
///
|
||||
/// # This returns `Shape`, not `Circle`.
|
||||
/// # Type checker infers return type as `Shape`, not `Circle`.
|
||||
/// Circle().set_scale(0.5)
|
||||
///
|
||||
/// # Thus, this expression is invalid, as `Shape` has no attribute `set_radius`.
|
||||
@@ -40,7 +40,7 @@ use crate::checkers::ast::Checker;
|
||||
/// Specifically, this check enforces that the return type of the following
|
||||
/// methods is `Self`:
|
||||
///
|
||||
/// 1. In-place binary operations, like `__iadd__`, `__imul__`, etc.
|
||||
/// 1. In-place binary-operation dunder methods, like `__iadd__`, `__imul__`, etc.
|
||||
/// 1. `__new__`, `__enter__`, and `__aenter__`, if those methods return the
|
||||
/// class name.
|
||||
/// 1. `__iter__` methods that return `Iterator`, despite the class inheriting
|
||||
@@ -51,16 +51,16 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
|
||||
/// def __new__(cls, *args: Any, **kwargs: Any) -> Foo:
|
||||
/// ...
|
||||
///
|
||||
/// def __enter__(self) -> Bad:
|
||||
/// def __enter__(self) -> Foo:
|
||||
/// ...
|
||||
///
|
||||
/// async def __aenter__(self) -> Bad:
|
||||
/// async def __aenter__(self) -> Foo:
|
||||
/// ...
|
||||
///
|
||||
/// def __iadd__(self, other: Bad) -> Bad:
|
||||
/// def __iadd__(self, other: Foo) -> Foo:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
@@ -79,11 +79,11 @@ use crate::checkers::ast::Checker;
|
||||
/// async def __aenter__(self) -> Self:
|
||||
/// ...
|
||||
///
|
||||
/// def __iadd__(self, other: Bad) -> Self:
|
||||
/// def __iadd__(self, other: Foo) -> Self:
|
||||
/// ...
|
||||
/// ```
|
||||
/// ## References
|
||||
/// - [PEP 673](https://peps.python.org/pep-0673/)
|
||||
/// - [`typing.Self` documentation](https://docs.python.org/3/library/typing.html#typing.Self)
|
||||
#[violation]
|
||||
pub struct NonSelfReturnType {
|
||||
class_name: String,
|
||||
|
||||
@@ -12,14 +12,15 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a function has a default value where the literal representation is
|
||||
/// greater than 50 characters, it is likely to be an implementation detail or
|
||||
/// a constant that varies depending on the system you're running on.
|
||||
/// greater than 50 characters, the value is likely to be an implementation
|
||||
/// detail or a constant that varies depending on the system you're running on.
|
||||
///
|
||||
/// Consider replacing such constants with ellipses (`...`).
|
||||
/// Default values like these should generally be omitted from stubs. Use
|
||||
/// ellipses (`...`) instead.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(arg: int = 12345678901) -> None:
|
||||
/// def foo(arg: int = 693568516352839939918568862861217771399698285293568) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -7,31 +7,25 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of the `pass` statement within a class body
|
||||
/// in a stub (`.pyi`) file.
|
||||
/// Checks for the presence of the `pass` statement in non-empty class bodies
|
||||
/// in `.pyi` files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In stub files, class definitions are intended to provide type hints, but
|
||||
/// are never actually evaluated. As such, it's unnecessary to include a `pass`
|
||||
/// statement in a class body, since it has no effect.
|
||||
///
|
||||
/// Instead of `pass`, prefer `...` to indicate that the class body is empty
|
||||
/// and adhere to common stub file conventions.
|
||||
/// The `pass` statement is always unnecessary in non-empty class bodies in
|
||||
/// stubs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class MyClass:
|
||||
/// x: int
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class MyClass:
|
||||
/// ...
|
||||
/// x: int
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Mypy documentation: Stub files](https://mypy.readthedocs.io/en/stable/stubs.html)
|
||||
#[violation]
|
||||
pub struct PassInClassBody;
|
||||
|
||||
|
||||
@@ -9,22 +9,22 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for `pass` statements in empty stub bodies.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// For consistency, empty stub bodies should contain `...` instead of `pass`.
|
||||
///
|
||||
/// Additionally, an ellipsis better conveys the intent of the stub body (that
|
||||
/// the body has been implemented, but has been intentionally left blank to
|
||||
/// document the interface).
|
||||
/// For stylistic consistency, `...` should always be used rather than `pass`
|
||||
/// in stub files.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(bar: int) -> list[int]:
|
||||
/// pass
|
||||
/// def foo(bar: int) -> list[int]: pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(bar: int) -> list[int]: ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// The [recommended style for functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#functions-and-methods)
|
||||
/// in the typing docs.
|
||||
#[violation]
|
||||
pub struct PassStatementStubBody;
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ impl fmt::Display for VarKind {
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that type `TypeVar`, `ParamSpec`, and `TypeVarTuple` definitions in
|
||||
/// stubs are prefixed with `_`.
|
||||
/// Checks that type `TypeVar`s, `ParamSpec`s, and `TypeVarTuple`s in stubs
|
||||
/// have names prefixed with `_`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By prefixing type parameters with `_`, we can avoid accidentally exposing
|
||||
/// names internal to the stub.
|
||||
/// Prefixing type parameters with `_` avoids accidentally exposing names
|
||||
/// internal to the stub.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for quoted type annotations in stub (`.pyi`) files, which should be avoided.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Stub files are evaluated using `annotations` semantics, as if
|
||||
/// `from __future__ import annotations` were included in the file. As such,
|
||||
/// quotes are never required for type annotations in stub files, and should be
|
||||
/// omitted.
|
||||
/// Stub files natively support forward references in all contexts, as stubs
|
||||
/// are never executed at runtime. (They should be thought of as "data files"
|
||||
/// for type checkers and IDEs.) As such, quotes are never required for type
|
||||
/// annotations in stub files, and should be omitted.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -25,6 +25,9 @@ use crate::checkers::ast::Checker;
|
||||
/// def function() -> int:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)
|
||||
#[violation]
|
||||
pub struct QuotedAnnotationInStub;
|
||||
|
||||
|
||||
@@ -13,30 +13,28 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of redundant `Literal` types and builtin super
|
||||
/// types in an union.
|
||||
/// Checks for redundant unions between a `Literal` and a builtin supertype of
|
||||
/// that `Literal`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The use of `Literal` types in a union with the builtin super type of one of
|
||||
/// its literal members is redundant, as the super type is strictly more
|
||||
/// general than the `Literal` type.
|
||||
///
|
||||
/// Using a `Literal` type in a union with its builtin supertype is redundant,
|
||||
/// as the supertype will be strictly more general than the `Literal` type.
|
||||
/// For example, `Literal["A"] | str` is equivalent to `str`, and
|
||||
/// `Literal[1] | int` is equivalent to `int`, as `str` and `int` are the super
|
||||
/// types of `"A"` and `1` respectively.
|
||||
/// `Literal[1] | int` is equivalent to `int`, as `str` and `int` are the
|
||||
/// supertypes of `"A"` and `1` respectively.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// A: Literal["A"] | str
|
||||
/// x: Literal["A", b"B"] | str
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// A: Literal["A"]
|
||||
/// x: Literal[b"B"] | str
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct RedundantLiteralUnion {
|
||||
@@ -150,10 +148,7 @@ fn match_builtin_type(expr: &Expr, semantic: &SemanticModel) -> Option<ExprType>
|
||||
/// Return the [`ExprType`] of an [`Expr`] if it is a literal (e.g., an `int`, like `1`, or a
|
||||
/// `bool`, like `True`).
|
||||
fn match_literal_type(expr: &Expr) -> Option<ExprType> {
|
||||
let Some(literal_expr) = expr.as_literal_expr() else {
|
||||
return None;
|
||||
};
|
||||
let result = match literal_expr {
|
||||
Some(match expr.as_literal_expr()? {
|
||||
LiteralExpressionRef::BooleanLiteral(_) => ExprType::Bool,
|
||||
LiteralExpressionRef::StringLiteral(_) => ExprType::Str,
|
||||
LiteralExpressionRef::BytesLiteral(_) => ExprType::Bytes,
|
||||
@@ -165,6 +160,5 @@ fn match_literal_type(expr: &Expr) -> Option<ExprType> {
|
||||
LiteralExpressionRef::NoneLiteral(_) | LiteralExpressionRef::EllipsisLiteral(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(result)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,34 +7,41 @@ use ruff_text_size::Ranged;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for union annotations that contain redundant numeric types (e.g.,
|
||||
/// `int | float`).
|
||||
/// Checks for parameter annotations that contain redundant unions between
|
||||
/// builtin numeric types (e.g., `int | float`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, `int` is a subtype of `float`, and `float` is a subtype of
|
||||
/// `complex`. As such, a union that includes both `int` and `float` is
|
||||
/// redundant, as it is equivalent to a union that only includes `float`.
|
||||
/// The [typing specification] states:
|
||||
///
|
||||
/// For more, see [PEP 3141], which defines Python's "numeric tower".
|
||||
/// > Python’s numeric types `complex`, `float` and `int` are not subtypes of
|
||||
/// > each other, but to support common use cases, the type system contains a
|
||||
/// > straightforward shortcut: when an argument is annotated as having type
|
||||
/// > `float`, an argument of type `int` is acceptable; similar, for an
|
||||
/// > argument annotated as having type `complex`, arguments of type `float` or
|
||||
/// > `int` are acceptable.
|
||||
///
|
||||
/// Unions with redundant elements are less readable than unions without them.
|
||||
/// As such, a union that includes both `int` and `float` is redundant in the
|
||||
/// specific context of a parameter annotation, as it is equivalent to a union
|
||||
/// that only includes `float`. For readability and clarity, unions should omit
|
||||
/// redundant elements.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(x: float | int) -> None:
|
||||
/// def foo(x: float | int | str) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(x: float) -> None:
|
||||
/// def foo(x: float | str) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
|
||||
/// - [The typing specification](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
|
||||
/// - [PEP 484: The numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower)
|
||||
///
|
||||
/// [PEP 3141]: https://peps.python.org/pep-3141/
|
||||
/// [typing specification]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
#[violation]
|
||||
pub struct RedundantNumericUnion {
|
||||
redundancy: Redundancy,
|
||||
|
||||
@@ -17,32 +17,30 @@ use crate::rules::flake8_pyi::rules::TypingModule;
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for typed function arguments in stubs with default values that
|
||||
/// are not "simple" /// (i.e., `int`, `float`, `complex`, `bytes`, `str`,
|
||||
/// `bool`, `None`, `...`, or simple container literals).
|
||||
/// Checks for typed function arguments in stubs with complex default values.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Stub (`.pyi`) files exist to define type hints, and are not evaluated at
|
||||
/// runtime. As such, function arguments in stub files should not have default
|
||||
/// values, as they are ignored by type checkers.
|
||||
///
|
||||
/// However, the use of default values may be useful for IDEs and other
|
||||
/// consumers of stub files, and so "simple" values may be worth including and
|
||||
/// are permitted by this rule.
|
||||
/// Stub (`.pyi`) files exist as "data files" for static analysis tools, and
|
||||
/// are not evaluated at runtime. While simple default values may be useful for
|
||||
/// some tools that consume stubs, such as IDEs, they are ignored by type
|
||||
/// checkers.
|
||||
///
|
||||
/// Instead of including and reproducing a complex value, use `...` to indicate
|
||||
/// that the assignment has a default value, but that the value is non-simple
|
||||
/// or varies according to the current platform or Python version.
|
||||
/// that the assignment has a default value, but that the value is "complex" or
|
||||
/// varies according to the current platform or Python version. For the
|
||||
/// purposes of this rule, any default value counts as "complex" unless it is
|
||||
/// a literal `int`, `float`, `complex`, `bytes`, `str`, `bool`, `None`, `...`,
|
||||
/// or a simple container literal.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(arg: List[int] = []) -> None:
|
||||
/// def foo(arg: list[int] = list(range(10_000))) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(arg: List[int] = ...) -> None:
|
||||
/// def foo(arg: list[int] = ...) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -12,11 +12,14 @@ use crate::checkers::ast::Checker;
|
||||
/// in stub (`.pyi`) files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a function has a default value where the string or bytes representation
|
||||
/// is greater than 50 characters, it is likely to be an implementation detail
|
||||
/// or a constant that varies depending on the system you're running on.
|
||||
/// If a function or variable has a default value where the string or bytes
|
||||
/// representation is greater than 50 characters long, it is likely to be an
|
||||
/// implementation detail or a constant that varies depending on the system
|
||||
/// you're running on.
|
||||
///
|
||||
/// Consider replacing such constants with ellipses (`...`).
|
||||
/// Although IDEs may find them useful, default values are ignored by type
|
||||
/// checkers, the primary consumers of stub files. Replace very long constants
|
||||
/// with ellipses (`...`) to simplify the stub.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -57,6 +57,9 @@ impl Violation for SnakeCaseTypeAlias {
|
||||
///
|
||||
/// _MyType: TypeAlias = int
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 484: Type Aliases](https://peps.python.org/pep-0484/#type-aliases)
|
||||
#[violation]
|
||||
pub struct TSuffixedTypeAlias {
|
||||
name: String,
|
||||
|
||||
@@ -11,22 +11,25 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for the presence of multiple literal types in a union.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Literal types accept multiple arguments, and it is clearer to specify them
|
||||
/// as a single literal.
|
||||
/// `Literal["foo", 42]` has identical semantics to
|
||||
/// `Literal["foo"] | Literal[42]`, but is clearer and more concise.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// field: Literal[1] | Literal[2]
|
||||
/// field: Literal[1] | Literal[2] | str
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// field: Literal[1, 2]
|
||||
/// field: Literal[1, 2] | str
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)
|
||||
#[violation]
|
||||
pub struct UnnecessaryLiteralUnion {
|
||||
members: Vec<String>,
|
||||
|
||||
@@ -12,17 +12,17 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for the presence of multiple `type`s in a union.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `type` built-in function accepts unions, and it is clearer to
|
||||
/// explicitly specify them as a single `type`.
|
||||
/// `type[T | S]` has identical semantics to `type[T] | type[S]` in a type
|
||||
/// annotation, but is cleaner and more concise.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// field: type[int] | type[float]
|
||||
/// field: type[int] | type[float] | str
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// field: type[int | float]
|
||||
/// field: type[int | float] | str
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UnnecessaryTypeUnion {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user