Compare commits

..

2 Commits

Author SHA1 Message Date
Carl Meyer
ab9b38c0f1 re-add para on CLICOLOR and CLICOLOR_FORCE 2024-04-05 16:07:54 -06:00
Carl Meyer
af12ee1763 Switch from colored to owo_colors/anstream 2024-04-04 17:55:43 -06:00
202 changed files with 1358 additions and 7014 deletions

View File

@@ -17,4 +17,4 @@ MD013: false
# MD024/no-duplicate-heading
MD024:
# Allow when nested under different parents e.g. CHANGELOG.md
siblings_only: true
allow_different_nesting: true

View File

@@ -13,7 +13,7 @@ exclude: |
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.16
rev: v0.15
hooks:
- id: validate-pyproject
@@ -31,7 +31,7 @@ repos:
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.39.0
rev: v0.37.0
hooks:
- id: markdownlint-fix
exclude: |
@@ -41,7 +41,7 @@ repos:
)$
- repo: https://github.com/crate-ci/typos
rev: v1.20.4
rev: v1.16.22
hooks:
- id: typos
@@ -55,7 +55,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.5
rev: v0.1.4
hooks:
- id: ruff-format
- id: ruff
@@ -70,7 +70,7 @@ repos:
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
rev: v3.0.3
hooks:
- id: prettier
types: [yaml]

View File

@@ -1,63 +1,5 @@
# Changelog
## 0.3.7
### Preview features
- \[`flake8-bugbear`\] Implement `loop-iterator-mutation` (`B909`) ([#9578](https://github.com/astral-sh/ruff/pull/9578))
- \[`pylint`\] Implement rule to prefer augmented assignment (`PLR6104`) ([#9932](https://github.com/astral-sh/ruff/pull/9932))
### Bug fixes
- Avoid TOCTOU errors in cache initialization ([#10884](https://github.com/astral-sh/ruff/pull/10884))
- \[`pylint`\] Recode `nan-comparison` rule to `W0177` ([#10894](https://github.com/astral-sh/ruff/pull/10894))
- \[`pylint`\] Reverse min-max logic in `if-stmt-min-max` ([#10890](https://github.com/astral-sh/ruff/pull/10890))
## 0.3.6
### Preview features
- \[`pylint`\] Implement `bad-staticmethod-argument` (`PLW0211`) ([#10781](https://github.com/astral-sh/ruff/pull/10781))
- \[`pylint`\] Implement `if-stmt-min-max` (`PLR1730`, `PLR1731`) ([#10002](https://github.com/astral-sh/ruff/pull/10002))
- \[`pyupgrade`\] Replace `str,Enum` multiple inheritance with `StrEnum` `UP042` ([#10713](https://github.com/astral-sh/ruff/pull/10713))
- \[`refurb`\] Implement `if-expr-instead-of-or-operator` (`FURB110`) ([#10687](https://github.com/astral-sh/ruff/pull/10687))
- \[`refurb`\] Implement `int-on-sliced-str` (`FURB166`) ([#10650](https://github.com/astral-sh/ruff/pull/10650))
- \[`refurb`\] Implement `write-whole-file` (`FURB103`) ([#10802](https://github.com/astral-sh/ruff/pull/10802))
- \[`refurb`\] Support `itemgetter` in `reimplemented-operator` (`FURB118`) ([#10526](https://github.com/astral-sh/ruff/pull/10526))
- \[`flake8_comprehensions`\] Add `sum`/`min`/`max` to unnecessary comprehension check (`C419`) ([#10759](https://github.com/astral-sh/ruff/pull/10759))
### Rule changes
- \[`pydocstyle`\] Require capitalizing docstrings where the first sentence is a single word (`D403`) ([#10776](https://github.com/astral-sh/ruff/pull/10776))
- \[`pycodestyle`\] Ignore annotated lambdas in class scopes (`E731`) ([#10720](https://github.com/astral-sh/ruff/pull/10720))
- \[`flake8-pyi`\] Various improvements to PYI034 ([#10807](https://github.com/astral-sh/ruff/pull/10807))
- \[`flake8-slots`\] Flag subclasses of call-based `typing.NamedTuple`s as well as subclasses of `collections.namedtuple()` (`SLOT002`) ([#10808](https://github.com/astral-sh/ruff/pull/10808))
- \[`pyflakes`\] Allow forward references in class bases in stub files (`F821`) ([#10779](https://github.com/astral-sh/ruff/pull/10779))
- \[`pygrep-hooks`\] Improve `blanket-noqa` error message (`PGH004`) ([#10851](https://github.com/astral-sh/ruff/pull/10851))
### CLI
- Support `FORCE_COLOR` env var ([#10839](https://github.com/astral-sh/ruff/pull/10839))
### Configuration
- Support negated patterns in `[extend-]per-file-ignores` ([#10852](https://github.com/astral-sh/ruff/pull/10852))
### Bug fixes
- \[`flake8-import-conventions`\] Accept non-aliased (but correct) import in `unconventional-import-alias` (`ICN001`) ([#10729](https://github.com/astral-sh/ruff/pull/10729))
- \[`flake8-quotes`\] Add semantic model flag when inside f-string replacement field ([#10766](https://github.com/astral-sh/ruff/pull/10766))
- \[`pep8-naming`\] Recursively resolve `TypeDicts` for N815 violations ([#10719](https://github.com/astral-sh/ruff/pull/10719))
- \[`flake8-quotes`\] Respect `Q00*` ignores in `flake8-quotes` rules ([#10728](https://github.com/astral-sh/ruff/pull/10728))
- \[`flake8-simplify`\] Show negated condition in `needless-bool` diagnostics (`SIM103`) ([#10854](https://github.com/astral-sh/ruff/pull/10854))
- \[`ruff`\] Use within-scope shadowed bindings in `asyncio-dangling-task` (`RUF006`) ([#10793](https://github.com/astral-sh/ruff/pull/10793))
- \[`flake8-pytest-style`\] Fix single-tuple conversion in `pytest-parametrize-values-wrong-type` (`PT007`) ([#10862](https://github.com/astral-sh/ruff/pull/10862))
- \[`flake8-return`\] Ignore assignments to annotated variables in `unnecessary-assign` (`RET504`) ([#10741](https://github.com/astral-sh/ruff/pull/10741))
- \[`refurb`\] Do not allow any keyword arguments for `read-whole-file` in `rb` mode (`FURB101`) ([#10803](https://github.com/astral-sh/ruff/pull/10803))
- \[`pylint`\] Don't recommend decorating staticmethods with `@singledispatch` (`PLE1519`, `PLE1520`) ([#10637](https://github.com/astral-sh/ruff/pull/10637))
- \[`pydocstyle`\] Use section name range for all section-related docstring diagnostics ([#10740](https://github.com/astral-sh/ruff/pull/10740))
- Respect `# noqa` directives on `__all__` openers ([#10798](https://github.com/astral-sh/ruff/pull/10798))
## 0.3.5
### Preview features

116
Cargo.lock generated
View File

@@ -244,12 +244,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chic"
version = "1.2.2"
@@ -371,7 +365,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -382,9 +376,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "clearscreen"
version = "3.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c"
checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994"
dependencies = [
"nix",
"terminfo",
@@ -602,7 +596,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -613,7 +607,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -1114,7 +1108,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -1277,9 +1271,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libcst"
version = "1.3.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f1e25d1b119ab5c2f15a6e081bb94a8d547c5c2ad065f5fd0dbb683f31ced91"
checksum = "890ee958b936e712c6f1c184f208176973e73c2e4f8d3cf499f94eb112f647f9"
dependencies = [
"chic",
"libcst_derive",
@@ -1292,12 +1286,12 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "1.3.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5011f2d59093de14a4a90e01b9d85dee9276e58a25f0107dcee167dd601be0"
checksum = "1dbd2f3cd9346422ebdc3a614aed6969d4e0b3e9c10517f33b30326acf894c11"
dependencies = [
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -1444,14 +1438,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.28.0"
name = "nextest-workspace-hack"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 2.5.0",
"bitflags 1.3.2",
"cfg-if",
"cfg_aliases",
"libc",
]
@@ -1561,6 +1560,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -1758,7 +1763,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -1813,12 +1818,13 @@ dependencies = [
[[package]]
name = "quick-junit"
version = "0.3.6"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1a341ae463320e9f8f34adda49c8a85d81d4e8f34cce4397fb0350481552224"
checksum = "1b9599bffc2cd7511355996e0cfd979266b2cfa3f3ff5247d07a3a6e1ded6158"
dependencies = [
"chrono",
"indexmap",
"nextest-workspace-hack",
"quick-xml",
"strip-ansi-escapes",
"thiserror",
@@ -1975,7 +1981,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -1995,8 +2001,9 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.3.7"
version = "0.3.5"
dependencies = [
"anstream",
"anyhow",
"argfile",
"bincode",
@@ -2006,7 +2013,6 @@ dependencies = [
"clap",
"clap_complete_command",
"clearscreen",
"colored",
"filetime",
"ignore",
"insta",
@@ -2017,6 +2023,7 @@ dependencies = [
"mimalloc",
"notify",
"num_cpus",
"owo-colors",
"path-absolutize",
"rayon",
"regex",
@@ -2157,15 +2164,15 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.3.7"
version = "0.3.5"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
"anstream",
"anyhow",
"bitflags 2.5.0",
"chrono",
"clap",
"colored",
"fern",
"glob",
"globset",
@@ -2179,6 +2186,7 @@ dependencies = [
"memchr",
"natord",
"once_cell",
"owo-colors",
"path-absolutize",
"pathdiff",
"pep440_rs 0.5.0",
@@ -2225,7 +2233,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2429,7 +2437,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.3.7"
version = "0.3.5"
dependencies = [
"anyhow",
"clap",
@@ -2493,7 +2501,6 @@ name = "ruff_workspace"
version = "0.0.0"
dependencies = [
"anyhow",
"colored",
"dirs 5.0.1",
"glob",
"globset",
@@ -2501,6 +2508,7 @@ dependencies = [
"is-macro",
"itertools 0.12.1",
"log",
"owo-colors",
"path-absolutize",
"pep440_rs 0.5.0",
"regex",
@@ -2671,7 +2679,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2704,7 +2712,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2745,7 +2753,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2855,7 +2863,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2877,9 +2885,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.58"
version = "2.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
dependencies = [
"proc-macro2",
"quote",
@@ -2950,7 +2958,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -2961,7 +2969,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
"test-case-core",
]
@@ -2982,7 +2990,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -3103,7 +3111,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -3339,7 +3347,7 @@ checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -3424,7 +3432,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
"wasm-bindgen-shared",
]
@@ -3458,7 +3466,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3491,7 +3499,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]
@@ -3515,14 +3523,14 @@ dependencies = [
[[package]]
name = "which"
version = "6.0.1"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
"winsafe",
]
[[package]]
@@ -3715,12 +3723,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "yansi"
version = "0.5.1"
@@ -3753,7 +3755,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.57",
]
[[package]]

View File

@@ -14,6 +14,7 @@ license = "MIT"
[workspace.dependencies]
aho-corasick = { version = "1.1.3" }
annotate-snippets = { version = "0.9.2", features = ["color"] }
anstream = { version = "0.6.13" }
anyhow = { version = "1.0.80" }
argfile = { version = "0.1.6" }
bincode = { version = "1.3.3" }
@@ -23,9 +24,8 @@ cachedir = { version = "0.3.1" }
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "3.0.0" }
clearscreen = { version = "2.0.0" }
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
colored = { version = "2.1.0" }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
@@ -65,6 +65,7 @@ natord = { version = "1.0.9" }
notify = { version = "6.1.1" }
num_cpus = { version = "1.16.0" }
once_cell = { version = "1.19.0" }
owo-colors = { version = "4.0.0" }
path-absolutize = { version = "3.1.1" }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.5.0", features = ["serde"] }

View File

@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.7
rev: v0.3.5
hooks:
# Run the linter.
- id: ruff

View File

@@ -3,11 +3,9 @@
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
[default.extend-words]
"arange" = "arange" # e.g. `numpy.arange`
hel = "hel"
whos = "whos"
spawnve = "spawnve"
ned = "ned"
pn = "pn" # `import panel as pd` is a thing
poit = "poit"
BA = "BA" # acronym for "Bad Allowed", used in testing.

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.3.7"
version = "0.3.5"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -25,6 +25,7 @@ ruff_source_file = { path = "../ruff_source_file" }
ruff_text_size = { path = "../ruff_text_size" }
ruff_workspace = { path = "../ruff_workspace" }
anstream = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
bincode = { workspace = true }
@@ -34,7 +35,6 @@ chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
clap_complete_command = { workspace = true }
clearscreen = { workspace = true }
colored = { workspace = true }
filetime = { workspace = true }
ignore = { workspace = true }
is-macro = { workspace = true }
@@ -42,6 +42,7 @@ itertools = { workspace = true }
log = { workspace = true }
notify = { workspace = true }
num_cpus = { workspace = true }
owo-colors = { workspace = true }
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
rayon = { workspace = true }
regex = { workspace = true }
@@ -62,8 +63,6 @@ wild = { workspace = true }
[dev-dependencies]
# Enable test rules during development
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
# Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"] }
insta = { workspace = true, features = ["filters", "json"] }
insta-cmd = { workspace = true }
tempfile = { workspace = true }

View File

@@ -8,7 +8,7 @@ use std::sync::Arc;
use anyhow::bail;
use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{command, Parser};
use colored::Colorize;
use owo_colors::OwoColorize;
use path_absolutize::path_dedot;
use regex::Regex;
use rustc_hash::FxHashMap;
@@ -1053,7 +1053,6 @@ pub enum FormatRangeParseError {
impl std::fmt::Display for FormatRangeParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let tip = " tip:".bold().green();
match self {
FormatRangeParseError::StartGreaterThanEnd(start, end) => {
write!(
@@ -1062,7 +1061,8 @@ impl std::fmt::Display for FormatRangeParseError {
start_invalid=start.to_string().bold().yellow(),
end_invalid=end.to_string().bold().yellow(),
start=start.to_string().green().bold(),
end=end.to_string().green().bold()
end=end.to_string().green().bold(),
tip=" tip:".bold().green()
)
}
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
@@ -1148,7 +1148,8 @@ pub enum LineColumnParseError {
impl LineColumnParseError {
fn write(&self, f: &mut std::fmt::Formatter, start_range: bool) -> std::fmt::Result {
let tip = "tip:".bold().green();
let tip = "tip:".bold();
let tip = tip.green();
let range = if start_range { "start" } else { "end" };

View File

@@ -375,17 +375,15 @@ pub(crate) fn init(path: &Path) -> Result<()> {
fs::create_dir_all(path.join(VERSION))?;
// Add the CACHEDIR.TAG.
cachedir::ensure_tag(path)?;
if !cachedir::is_tagged(path)? {
cachedir::add_tag(path)?;
}
// Add the .gitignore.
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path.join(".gitignore"))
{
Ok(mut file) => file.write_all(b"# Automatically created by ruff.\n*\n")?,
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
Err(err) => return Err(err.into()),
let gitignore_path = path.join(".gitignore");
if !gitignore_path.exists() {
let mut file = fs::File::create(gitignore_path)?;
file.write_all(b"# Automatically created by ruff.\n*\n")?;
}
Ok(())

View File

@@ -4,9 +4,9 @@ use std::path::{Path, PathBuf};
use std::time::Instant;
use anyhow::Result;
use colored::Colorize;
use ignore::Error;
use log::{debug, error, warn};
use owo_colors::OwoColorize;
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use rustc_hash::FxHashMap;
@@ -226,6 +226,7 @@ mod test {
use rustc_hash::FxHashMap;
use tempfile::TempDir;
use ruff_linter::colors;
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
use ruff_linter::registry::Rule;
use ruff_linter::settings::types::UnsafeFixes;
@@ -280,7 +281,7 @@ mod test {
UnsafeFixes::Enabled,
)
.unwrap();
let mut output = Vec::new();
let mut output = colors::none(Vec::new());
TextEmitter::default()
.with_show_fix_status(true)
@@ -291,7 +292,7 @@ mod test {
)
.unwrap();
let messages = String::from_utf8(output).unwrap();
let messages = String::from_utf8(output.into_inner()).unwrap();
insta::with_settings!({
omit_expression => true,

View File

@@ -2,7 +2,7 @@ use std::fs::remove_dir_all;
use std::io::{self, BufWriter, Write};
use anyhow::Result;
use colored::Colorize;
use owo_colors::OwoColorize;
use path_absolutize::path_dedot;
use walkdir::WalkDir;

View File

@@ -6,9 +6,9 @@ use std::path::{Path, PathBuf};
use std::time::Instant;
use anyhow::Result;
use colored::Colorize;
use itertools::Itertools;
use log::{error, warn};
use owo_colors::OwoColorize;
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rustc_hash::FxHashSet;
@@ -16,6 +16,7 @@ use thiserror::Error;
use tracing::debug;
use ruff_diagnostics::SourceMap;
use ruff_linter::colors;
use ruff_linter::fs;
use ruff_linter::logging::{DisplayParseError, LogLevel};
use ruff_linter::registry::Rule;
@@ -189,10 +190,10 @@ pub(crate) fn format(
match mode {
FormatMode::Write => {}
FormatMode::Check => {
results.write_changed(&mut stdout().lock())?;
results.write_changed(&mut colors::auto(stdout()).lock())?;
}
FormatMode::Diff => {
results.write_diff(&mut stdout().lock())?;
results.write_diff(&mut colors::auto(stdout()).lock())?;
}
}
@@ -200,9 +201,9 @@ pub(crate) fn format(
if config_arguments.log_level >= LogLevel::Default {
if mode.is_diff() {
// Allow piping the diff to e.g. a file by writing the summary to stderr
results.write_summary(&mut stderr().lock())?;
results.write_summary(&mut colors::auto(stderr()).lock())?;
} else {
results.write_summary(&mut stdout().lock())?;
results.write_summary(&mut colors::auto(stdout()).lock())?;
}
}

View File

@@ -4,6 +4,7 @@ use std::path::Path;
use anyhow::Result;
use log::error;
use ruff_linter::colors;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
@@ -111,7 +112,7 @@ fn format_source_code(
match &formatted {
FormattedSource::Formatted(formatted) => match mode {
FormatMode::Write => {
let mut writer = stdout().lock();
let mut writer = colors::auto(stdout()).lock();
formatted
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
@@ -120,7 +121,7 @@ fn format_source_code(
FormatMode::Diff => {
use std::io::Write;
write!(
&mut stdout().lock(),
&mut colors::auto(stdout()).lock(),
"{}",
source_kind.diff(formatted, path).unwrap()
)
@@ -130,7 +131,7 @@ fn format_source_code(
FormattedSource::Unchanged => {
// Write to stdout regardless of whether the source was formatted
if mode.is_write() {
let mut writer = stdout().lock();
let mut writer = colors::auto(stdout()).lock();
source_kind
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;

View File

@@ -8,11 +8,12 @@ use std::ops::{Add, AddAssign};
use std::path::Path;
use anyhow::{Context, Result};
use colored::Colorize;
use log::{debug, error, warn};
use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
use ruff_linter::colors;
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
use ruff_linter::logging::DisplayParseError;
use ruff_linter::message::Message;
@@ -444,7 +445,7 @@ pub(crate) fn lint_stdin(
// But only write a diff if it's non-empty.
if !fixed.is_empty() {
write!(
&mut io::stdout().lock(),
&mut colors::auto(io::stdout()).lock(),
"{}",
source_kind.diff(&transformed, path).unwrap()
)?;

View File

@@ -10,10 +10,11 @@ use std::sync::mpsc::channel;
use anyhow::Result;
use args::{GlobalConfigArgs, ServerCommand};
use clap::CommandFactory;
use colored::Colorize;
use log::warn;
use notify::{recommended_watcher, RecursiveMode, Watcher};
use owo_colors::OwoColorize;
use ruff_linter::colors;
use ruff_linter::logging::{set_up_logging, LogLevel};
use ruff_linter::settings::flags::FixMode;
use ruff_linter::settings::types::SerializationFormat;
@@ -145,17 +146,6 @@ pub fn run(
}));
}
// Enabled ANSI colors on Windows 10.
#[cfg(windows)]
assert!(colored::control::set_virtual_terminal(true).is_ok());
// support FORCE_COLOR env var
if let Some(force_color) = std::env::var_os("FORCE_COLOR") {
if force_color.len() > 0 {
colored::control::set_override(true);
}
}
set_up_logging(global_options.log_level())?;
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
@@ -232,13 +222,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
let mut writer: Box<dyn Write> = match cli.output_file {
Some(path) if !cli.watch => {
colored::control::set_override(false);
let file = File::create(path)?;
Box::new(BufWriter::new(file))
Box::new(BufWriter::new(colors::none(file)))
}
_ => Box::new(BufWriter::new(io::stdout())),
_ => Box::new(BufWriter::new(colors::auto(io::stdout()))),
};
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
let stderr_writer = Box::new(BufWriter::new(colors::auto(io::stderr())));
let is_stdin = is_stdin(&cli.files, cli.stdin_filename.as_deref());
let files = resolve_default_files(cli.files, is_stdin);

View File

@@ -1,7 +1,8 @@
use std::process::ExitCode;
use anstream::eprintln;
use clap::{Parser, Subcommand};
use colored::Colorize;
use owo_colors::OwoColorize;
use ruff::args::{Args, Command};
use ruff::{run, ExitStatus};

View File

@@ -5,8 +5,8 @@ use std::io::Write;
use anyhow::Result;
use bitflags::bitflags;
use colored::Colorize;
use itertools::{iterate, Itertools};
use owo_colors::OwoColorize;
use serde::Serialize;
use ruff_linter::fs::relativize_path;

View File

@@ -1168,119 +1168,3 @@ def func():
Ok(())
}
/// Per-file selects via ! negation in per-file-ignores
#[test]
fn negated_per_file_ignores() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"!selected.py" = ["RUF"]
"#,
)?;
let selected = tempdir.path().join("selected.py");
fs::write(selected, "")?;
let ignored = tempdir.path().join("ignored.py");
fs::write(ignored, "")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: false
exit_code: 1
----- stdout -----
selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}
#[test]
fn negated_per_file_ignores_absolute() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"!src/**.py" = ["RUF"]
"#,
)?;
let src_dir = tempdir.path().join("src");
fs::create_dir(&src_dir)?;
let selected = src_dir.join("selected.py");
fs::write(selected, "")?;
let ignored = tempdir.path().join("ignored.py");
fs::write(ignored, "")?;
insta::with_settings!({filters => vec![
// Replace windows paths
(r"\\", "/"),
]}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: false
exit_code: 1
----- stdout -----
src/selected.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
});
Ok(())
}
/// patterns are additive, can't use negative patterns to "un-ignore"
#[test]
fn negated_per_file_ignores_overlap() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.per-file-ignores]
"*.py" = ["RUF"]
"!foo.py" = ["RUF"]
"#,
)?;
let foo_file = tempdir.path().join("foo.py");
fs::write(foo_file, "")?;
let bar_file = tempdir.path().join("bar.py");
fs::write(bar_file, "")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.arg("--select")
.arg("RUF901")
.current_dir(&tempdir)
, @r###"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
"###);
Ok(())
}

View File

@@ -50,7 +50,6 @@ file_resolver.exclude = [
"venv",
]
file_resolver.extend_exclude = [
"crates/ruff/resources/",
"crates/ruff_linter/resources/",
"crates/ruff_python_formatter/resources/",
]

View File

@@ -71,14 +71,6 @@ impl Diagnostic {
}
}
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
#[inline]
#[must_use]
pub fn with_parent(mut self, parent: TextSize) -> Self {
self.set_parent(parent);
self
}
/// Set the location of the diagnostic's parent node.
#[inline]
pub fn set_parent(&mut self, parent: TextSize) {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.3.7"
version = "0.3.5"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -30,11 +30,11 @@ ruff_text_size = { path = "../ruff_text_size" }
aho-corasick = { workspace = true }
annotate-snippets = { workspace = true, features = ["color"] }
anstream = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
fern = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }
@@ -47,6 +47,7 @@ log = { workspace = true }
memchr = { workspace = true }
natord = { workspace = true }
once_cell = { workspace = true }
owo-colors = { workspace = true }
path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
@@ -75,8 +76,6 @@ url = { workspace = true }
[dev-dependencies]
insta = { workspace = true }
test-case = { workspace = true }
# Disable colored output in tests
colored = { workspace = true, features = ["no-color"] }
[features]
default = []

View File

@@ -1,160 +0,0 @@
"""
Should emit:
B909 - on lines 11, 25, 26, 40, 46
"""
# lists
some_list = [1, 2, 3]
some_other_list = [1, 2, 3]
for elem in some_list:
# errors
some_list.remove(0)
del some_list[2]
some_list.append(elem)
some_list.sort()
some_list.reverse()
some_list.clear()
some_list.extend([1, 2])
some_list.insert(1, 1)
some_list.pop(1)
some_list.pop()
# conditional break should error
if elem == 2:
some_list.remove(0)
if elem == 3:
break
# non-errors
some_other_list.remove(elem)
del some_list
del some_other_list
found_idx = some_list.index(elem)
some_list = 3
# unconditional break should not error
if elem == 2:
some_list.remove(elem)
break
# dicts
mydicts = {"a": {"foo": 1, "bar": 2}}
for elem in mydicts:
# errors
mydicts.popitem()
mydicts.setdefault("foo", 1)
mydicts.update({"foo": "bar"})
# no errors
elem.popitem()
elem.setdefault("foo", 1)
elem.update({"foo": "bar"})
# sets
myset = {1, 2, 3}
for _ in myset:
# errors
myset.update({4, 5})
myset.intersection_update({4, 5})
myset.difference_update({4, 5})
myset.symmetric_difference_update({4, 5})
myset.add(4)
myset.discard(3)
# no errors
del myset
# members
class A:
some_list: list
def __init__(self, ls):
self.some_list = list(ls)
a = A((1, 2, 3))
# ensure member accesses are handled as errors
for elem in a.some_list:
a.some_list.remove(0)
del a.some_list[2]
# Augassign should error
foo = [1, 2, 3]
bar = [4, 5, 6]
for _ in foo:
foo *= 2
foo += bar
foo[1] = 9
foo[1:2] = bar
foo[1:2:3] = bar
foo = {1, 2, 3}
bar = {4, 5, 6}
for _ in foo: # should error
foo |= bar
foo &= bar
foo -= bar
foo ^= bar
# more tests for unconditional breaks - should not error
for _ in foo:
foo.remove(1)
for _ in bar:
bar.remove(1)
break
break
# should not error
for _ in foo:
foo.remove(1)
for _ in bar:
...
break
# should error (?)
for _ in foo:
foo.remove(1)
if bar:
bar.remove(1)
break
break
# should error
for _ in foo:
if bar:
pass
else:
foo.remove(1)
# should error
for elem in some_list:
if some_list.pop() == 2:
pass
# should not error
for elem in some_list:
if some_list.pop() == 2:
break
# should error
for elem in some_list:
if some_list.pop() == 2:
pass
else:
break
# should not error
for elem in some_list:
del some_list[elem]
some_list[elem] = 1
some_list.remove(elem)
some_list.discard(elem)

View File

@@ -195,13 +195,6 @@ class BadAsyncIterator(collections.abc.AsyncIterator[str]):
def __aiter__(self) -> typing.AsyncIterator[str]:
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
class SubclassOfBadIterator3(BadIterator3):
def __iter__(self) -> Iterator[int]: # Y034
...
class SubclassOfBadAsyncIterator(BadAsyncIterator):
def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
...
class AsyncIteratorReturningAsyncIterable:
def __aiter__(self) -> AsyncIterable[str]:
@@ -232,11 +225,6 @@ class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
class SubclassOfMetaclassInWhichSelfCannotBeUsed(MetaclassInWhichSelfCannotBeUsed4):
def __new__(cls) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
def __enter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
async def __aenter__(self) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
def __isub__(self, other: SubclassOfMetaclassInWhichSelfCannotBeUsed) -> SubclassOfMetaclassInWhichSelfCannotBeUsed: ...
class Abstract(Iterator[str]):
@abstractmethod

View File

@@ -80,13 +80,5 @@ def test_single_list_of_lists(param):
@pytest.mark.parametrize("a", [1, 2])
@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
@pytest.mark.parametrize("d", [3,])
@pytest.mark.parametrize(
"d",
[("3", "4")],
)
@pytest.mark.parametrize(
"e",
[("3", "4"),],
)
def test_multiple_decorators(a, b, c, d, e):
def test_multiple_decorators(a, b, c):
pass

View File

@@ -5,5 +5,3 @@ this_should_be_linted = f'double {"quote"} string'
# https://github.com/astral-sh/ruff/issues/10546
x: "Literal['foo', 'bar']"
# https://github.com/astral-sh/ruff/issues/10761
f"Before {f'x {x}' if y else f'foo {z}'} after"

View File

@@ -52,32 +52,32 @@ def f():
return False
def f():
# SIM103
if a:
return False
else:
return True
def f():
# OK
if a:
return False
else:
return False
def f():
# OK
if a:
return True
else:
return True
def f():
# SIM103 (but not fixable)
if a:
return False
else:
return True
def f():
# OK
if a:
return False
else:
return False
def f():
# OK
if a:
return True
else:
return True
def f():
# OK
def bool():
return False
if a:
@@ -86,14 +86,6 @@ def f():
return False
def f():
# SIM103
if keys is not None and notice.key not in keys:
return False
else:
return True
###
# Positive cases (preview)
###

View File

@@ -6,14 +6,6 @@ class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
pass
class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
pass
class UnusualButOkay(NamedTuple("foo", [("x", int, "y", int)])):
__slots__ = ()
class Good(namedtuple("foo", ["str", "int"])): # OK
__slots__ = ("foo",)

View File

@@ -25,9 +25,3 @@ def non_ascii():
def all_caps():
"""th•s is not capitalized."""
def single_word():
"""singleword."""
def single_word_no_dot():
"""singleword"""

View File

@@ -1,6 +1,6 @@
"""Tests for constructs allowed in `.pyi` stub files but not at runtime"""
from typing import Generic, NewType, Optional, TypeAlias, TypeVar, Union
from typing import Optional, TypeAlias, Union
__version__: str
__author__: str
@@ -33,19 +33,6 @@ class Leaf: ...
class Tree(list[Tree | Leaf]): ... # valid in a `.pyi` stub file, not in a `.py` runtime file
class Tree2(list["Tree | Leaf"]): ... # always okay
# Generic bases can have forward references in stubs
class Foo(Generic[T]): ...
T = TypeVar("T")
class Bar(Foo[Baz]): ...
class Baz: ...
# bases in general can be forward references in stubs
class Eggs(Spam): ...
class Spam: ...
# NewType can have forward references
MyNew = NewType("MyNew", MyClass)
# Annotations are treated as assignments in .pyi files, but not in .py files
class MyClass:
foo: int
@@ -55,6 +42,3 @@ class MyClass:
baz: MyClass
eggs = baz # valid in a `.pyi` stub file, not in a `.py` runtime file
eggs = "baz" # always okay
class Blah:
class Blah2(Blah): ...

View File

@@ -1,13 +0,0 @@
"""Respect `# noqa` directives on `__all__` definitions."""
__all__ = [ # noqa: F822
"Bernoulli",
"Beta",
"Binomial",
]
__all__ += [
"ContinuousBernoulli", # noqa: F822
"ExponentialFamily",
]

View File

@@ -9,22 +9,3 @@ x = 1
x = 1 # noqa: F401, W203
# noqa: F401
# noqa: F401, W203
# OK
x = 2 # noqa: X100
x = 2 # noqa:X100
# PGH004
x = 2 # noqa X100
# PGH004
x = 2 # noqa X100, X200
# PGH004
x = 2 # noqa : X300
# PGH004
x = 2 # noqa : X400
# PGH004
x = 2 # noqa :X500

View File

@@ -1,44 +0,0 @@
class Wolf:
@staticmethod
def eat(self): # [bad-staticmethod-argument]
pass
class Wolf:
@staticmethod
def eat(sheep):
pass
class Sheep:
@staticmethod
def eat(cls, x, y, z): # [bad-staticmethod-argument]
pass
@staticmethod
def sleep(self, x, y, z): # [bad-staticmethod-argument]
pass
def grow(self, x, y, z):
pass
@classmethod
def graze(cls, x, y, z):
pass
class Foo:
@staticmethod
def eat(x, self, z):
pass
@staticmethod
def sleep(x, cls, z):
pass
def grow(self, x, y, z):
pass
@classmethod
def graze(cls, x, y, z):
pass

View File

@@ -1,136 +0,0 @@
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, redefined-outer-name
value = 10
value2 = 0
value3 = 3
# Positive
if value < 10: # [max-instead-of-if]
value = 10
if value <= 10: # [max-instead-of-if]
value = 10
if value < value2: # [max-instead-of-if]
value = value2
if value > 10: # [min-instead-of-if]
value = 10
if value >= 10: # [min-instead-of-if]
value = 10
if value > value2: # [min-instead-of-if]
value = value2
class A:
def __init__(self):
self.value = 13
A1 = A()
if A1.value < 10: # [max-instead-of-if]
A1.value = 10
if A1.value > 10: # [min-instead-of-if]
A1.value = 10
class AA:
def __init__(self, value):
self.value = value
def __gt__(self, b):
return self.value > b
def __ge__(self, b):
return self.value >= b
def __lt__(self, b):
return self.value < b
def __le__(self, b):
return self.value <= b
A1 = AA(0)
A2 = AA(3)
if A2 < A1: # [max-instead-of-if]
A2 = A1
if A2 <= A1: # [max-instead-of-if]
A2 = A1
if A2 > A1: # [min-instead-of-if]
A2 = A1
if A2 >= A1: # [min-instead-of-if]
A2 = A1
# Negative
if value < 10:
value = 2
if value <= 3:
value = 5
if value < 10:
value = 2
value2 = 3
if value < value2:
value = value3
if value < 5:
value = value3
if 2 < value <= 3:
value = 1
if value < 10:
value = 10
else:
value = 3
if value <= 3:
value = 5
elif value == 3:
value = 2
if value > 10:
value = 2
if value >= 3:
value = 5
if value > 10:
value = 2
value2 = 3
if value > value2:
value = value3
if value > 5:
value = value3
if 2 > value >= 3:
value = 1
if value > 10:
value = 10
else:
value = 3
if value >= 3:
value = 5
elif value == 3:
value = 2
# Parenthesized expressions
if value.attr > 3:
(
value.
attr
) = 3

View File

@@ -1,55 +0,0 @@
# Errors
some_string = "some string"
index, a_number, to_multiply, to_divide, to_cube, timeDiffSeconds, flags = (
0,
1,
2,
3,
4,
5,
0x3,
)
a_list = [1, 2]
some_set = {"elem"}
mat1, mat2 = None, None
some_string = some_string + "a very long end of string"
index = index - 1
a_list = a_list + ["to concat"]
some_set = some_set | {"to concat"}
to_multiply = to_multiply * 5
to_divide = to_divide / 5
to_divide = to_divide // 5
to_cube = to_cube**3
to_cube = 3**to_cube
to_cube = to_cube**to_cube
timeDiffSeconds = timeDiffSeconds % 60
flags = flags & 0x1
flags = flags | 0x1
flags = flags ^ 0x1
flags = flags << 1
flags = flags >> 1
mat1 = mat1 @ mat2
a_list[1] = a_list[1] + 1
a_list[0:2] = a_list[0:2] * 3
a_list[:2] = a_list[:2] * 3
a_list[1:] = a_list[1:] * 3
a_list[:] = a_list[:] * 3
index = index * (index + 10)
class T:
def t(self):
self.a = self.a + 1
obj = T()
obj.a = obj.a + 1
# OK
a_list[0] = a_list[:] * 3
index = a_number = a_number + 1
a_number = index = a_number + 1
index = index * index + 10

View File

@@ -1,13 +0,0 @@
from enum import Enum
class A(str, Enum): ...
class B(Enum, str): ...
class D(int, str, Enum): ...
class E(str, int, Enum): ...

View File

@@ -28,6 +28,10 @@ with open("file.txt", encoding="utf8") as f:
with open("file.txt", errors="ignore") as f:
x = f.read()
# FURB101
with open("file.txt", errors="ignore", mode="rb") as f:
x = f.read()
# FURB101
with open("file.txt", mode="r") as f: # noqa: FURB120
x = f.read()
@@ -56,11 +60,6 @@ with foo() as a, open("file.txt") as b, foo() as c:
# Non-errors.
# Path.read_bytes does not support any kwargs
with open("file.txt", errors="ignore", mode="rb") as f:
x = f.read()
f2 = open("file2.txt")
with open("file.txt") as f:
x = f2.read()

View File

@@ -1,132 +0,0 @@
def foo():
...
def bar(x):
...
# Errors.
# FURB103
with open("file.txt", "w") as f:
f.write("test")
# FURB103
with open("file.txt", "wb") as f:
f.write(foobar)
# FURB103
with open("file.txt", mode="wb") as f:
f.write(b"abc")
# FURB103
with open("file.txt", "w", encoding="utf8") as f:
f.write(foobar)
# FURB103
with open("file.txt", "w", errors="ignore") as f:
f.write(foobar)
# FURB103
with open("file.txt", mode="w") as f:
f.write(foobar)
# FURB103
with open(foo(), "wb") as f:
# The body of `with` is non-trivial, but the recommendation holds.
bar("pre")
f.write(bar())
bar("post")
print("Done")
# FURB103
with open("a.txt", "w") as a, open("b.txt", "wb") as b:
a.write(x)
b.write(y)
# FURB103
with foo() as a, open("file.txt", "w") as b, foo() as c:
# We have other things in here, multiple with items, but the user
# writes a single time to file and that bit they can replace.
bar(a)
b.write(bar(bar(a + x)))
bar(c)
# FURB103
with open("file.txt", "w", newline="\r\n") as f:
f.write(foobar)
# Non-errors.
with open("file.txt", errors="ignore", mode="wb") as f:
# Path.write_bytes() does not support errors
f.write(foobar)
f2 = open("file2.txt", "w")
with open("file.txt", "w") as f:
f2.write(x)
# mode is dynamic
with open("file.txt", foo()) as f:
f.write(x)
# keyword mode is incorrect
with open("file.txt", mode="a+") as f:
f.write(x)
# enables line buffering, not supported in write_text()
with open("file.txt", buffering=1) as f:
f.write(x)
# dont mistake "newline" for "mode"
with open("file.txt", newline="wb") as f:
f.write(x)
# I guess we can possibly also report this case, but the question
# is why the user would put "w+" here in the first place.
with open("file.txt", "w+") as f:
f.write(x)
# Even though we write the whole file, we do other things.
with open("file.txt", "w") as f:
f.write(x)
f.seek(0)
x += f.read(100)
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
with open(*filename, mode="w") as f:
f.write(x)
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
with open(**kwargs) as f:
f.write(x)
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
with open("file.txt", **kwargs) as f:
f.write(x)
# This shouldn't error, since it could contain unsupported arguments, like `buffering`.
with open("file.txt", mode="w", **kwargs) as f:
f.write(x)
# This could error (but doesn't), since it can't contain unsupported arguments, like
# `buffering`.
with open(*filename, mode="w") as f:
f.write(x)
# This could error (but doesn't), since it can't contain unsupported arguments, like
# `buffering`.
with open(*filename, file="file.txt", mode="w") as f:
f.write(x)
# Loops imply multiple writes
with open("file.txt", "w") as f:
while x < 0:
f.write(foobar)
with open("file.txt", "w") as f:
for line in text:
f.write(line)

View File

@@ -1,40 +0,0 @@
z = x if x else y # FURB110
z = x \
if x else y # FURB110
z = x if x \
else \
y # FURB110
z = x() if x() else y() # FURB110
# FURB110
z = x if (
# Test for x.
x
) else (
# Test for y.
y
)
# FURB110
z = (
x if (
# Test for x.
x
) else (
# Test for y.
y
)
)
# FURB110
z = (
x if
# If true, use x.
x
# Otherwise, use y.
else
y
)

View File

@@ -10,7 +10,7 @@ op_mult = lambda x, y: x * y
op_matmutl = lambda x, y: x @ y
op_truediv = lambda x, y: x / y
op_mod = lambda x, y: x % y
op_pow = lambda x, y: x**y
op_pow = lambda x, y: x ** y
op_lshift = lambda x, y: x << y
op_rshift = lambda x, y: x >> y
op_bitor = lambda x, y: x | y
@@ -27,10 +27,6 @@ op_gte = lambda x, y: x >= y
op_is = lambda x, y: x is y
op_isnot = lambda x, y: x is not y
op_in = lambda x, y: y in x
op_itemgetter = lambda x: x[0]
op_itemgetter = lambda x: (x[0], x[1], x[2])
op_itemgetter = lambda x: (x[1:], x[2])
op_itemgetter = lambda x: x[:]
def op_not2(x):
@@ -45,30 +41,21 @@ class Adder:
def add(x, y):
return x + y
# OK.
op_add3 = lambda x, y=1: x + y
op_add3 = lambda x, y = 1: x + y
op_neg2 = lambda x, y: y - x
op_notin = lambda x, y: y not in x
op_and = lambda x, y: y and x
op_or = lambda x, y: y or x
op_in = lambda x, y: x in y
op_itemgetter = lambda x: (1, x[1], x[2])
op_itemgetter = lambda x: (x.y, x[1], x[2])
op_itemgetter = lambda x, y: (x[0], y[0])
op_itemgetter = lambda x, y: (x[0], y[0])
op_itemgetter = lambda x: ()
op_itemgetter = lambda x: (*x[0], x[1])
def op_neg3(x, y):
return y - x
def op_add4(x, y=1):
def op_add4(x, y = 1):
return x + y
def op_add5(x, y):
print("op_add5")
return x + y

View File

@@ -30,9 +30,6 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
if checker.enabled(Rule::EnumerateForLoop) {
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
}
if checker.enabled(Rule::LoopIteratorMutation) {
flake8_bugbear::rules::loop_iterator_mutation(checker, stmt_for);
}
}
}
}

View File

@@ -15,19 +15,15 @@ use crate::rules::{
pub(crate) fn deferred_scopes(checker: &mut Checker) {
if !checker.any_enabled(&[
Rule::AsyncioDanglingTask,
Rule::BadStaticmethodArgument,
Rule::BuiltinAttributeShadowing,
Rule::GlobalVariableNotAssigned,
Rule::ImportPrivateName,
Rule::ImportShadowedByLoopVar,
Rule::InvalidFirstArgumentNameForClassMethod,
Rule::InvalidFirstArgumentNameForMethod,
Rule::InvalidFirstArgumentNameForClassMethod,
Rule::NoSelfUse,
Rule::RedefinedArgumentFromLocal,
Rule::RedefinedWhileUnused,
Rule::RuntimeImportInTypeCheckingBlock,
Rule::SingledispatchMethod,
Rule::SingledispatchmethodFunction,
Rule::TooManyLocals,
Rule::TypingOnlyFirstPartyImport,
Rule::TypingOnlyStandardLibraryImport,
@@ -35,16 +31,19 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
Rule::UndefinedLocal,
Rule::UnusedAnnotation,
Rule::UnusedClassMethodArgument,
Rule::BuiltinAttributeShadowing,
Rule::UnusedFunctionArgument,
Rule::UnusedImport,
Rule::UnusedLambdaArgument,
Rule::UnusedMethodArgument,
Rule::UnusedPrivateProtocol,
Rule::UnusedPrivateTypeAlias,
Rule::UnusedPrivateTypedDict,
Rule::UnusedPrivateTypeVar,
Rule::UnusedPrivateTypedDict,
Rule::UnusedStaticMethodArgument,
Rule::UnusedVariable,
Rule::SingledispatchMethod,
Rule::SingledispatchmethodFunction,
]) {
return;
}
@@ -425,10 +424,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::BadStaticmethodArgument) {
pylint::rules::bad_staticmethod_argument(checker, scope, &mut diagnostics);
}
if checker.any_enabled(&[
Rule::InvalidFirstArgumentNameForClassMethod,
Rule::InvalidFirstArgumentNameForMethod,

View File

@@ -32,9 +32,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
{
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
if !checker.source_type.is_stub()
&& checker.settings.target_version < PythonVersion::Py310
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing
{
@@ -47,7 +48,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.source_type.is_stub()
|| checker.settings.target_version >= PythonVersion::Py310
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations_or_stub()
&& checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing)
{
@@ -59,8 +60,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
// Ex) list[...]
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
if !checker.source_type.is_stub()
&& checker.settings.target_version < PythonVersion::Py39
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& typing::is_pep585_generic(value, &checker.semantic)
{
@@ -184,9 +186,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
typing::to_pep585_generic(expr, &checker.semantic)
{
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
if !checker.source_type.is_stub()
&& checker.settings.target_version < PythonVersion::Py39
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing
{
@@ -197,7 +200,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.source_type.is_stub()
|| checker.settings.target_version >= PythonVersion::Py39
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations_or_stub()
&& checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing)
{
@@ -267,9 +270,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
]) {
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
if !checker.source_type.is_stub()
&& checker.settings.target_version < PythonVersion::Py39
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing
{
@@ -282,7 +286,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.source_type.is_stub()
|| checker.settings.target_version >= PythonVersion::Py39
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations_or_stub()
&& checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing)
{
@@ -1172,8 +1176,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}) => {
// Ex) `str | None`
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
if !checker.semantic.future_annotations_or_stub()
if !checker.source_type.is_stub()
&& checker.settings.target_version < PythonVersion::Py310
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
{
flake8_future_annotations::rules::future_required_type_annotation(
@@ -1358,9 +1363,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::IfExprMinMax) {
refurb::rules::if_expr_min_max(checker, if_exp);
}
if checker.enabled(Rule::IfExpInsteadOfOrOperator) {
refurb::rules::if_exp_instead_of_or_operator(checker, if_exp);
}
}
Expr::ListComp(
comp @ ast::ExprListComp {

View File

@@ -406,11 +406,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UselessObjectInheritance) {
pyupgrade::rules::useless_object_inheritance(checker, class_def);
}
if checker.enabled(Rule::ReplaceStrEnum) {
if checker.settings.target_version >= PythonVersion::Py311 {
pyupgrade::rules::replace_str_enum(checker, class_def);
}
}
if checker.enabled(Rule::UnnecessaryClassParentheses) {
pyupgrade::rules::unnecessary_class_parentheses(checker, class_def);
}
@@ -1117,9 +1112,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::TooManyBooleanExpressions) {
pylint::rules::too_many_boolean_expressions(checker, if_);
}
if checker.enabled(Rule::IfStmtMinMax) {
pylint::rules::if_stmt_min_max(checker, if_);
}
if checker.source_type.is_stub() {
if checker.any_enabled(&[
Rule::UnrecognizedVersionInfoCheck,
@@ -1225,9 +1217,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ReadWholeFile) {
refurb::rules::read_whole_file(checker, with_stmt);
}
if checker.enabled(Rule::WriteWholeFile) {
refurb::rules::write_whole_file(checker, with_stmt);
}
if checker.enabled(Rule::UselessWithLock) {
pylint::rules::useless_with_lock(checker, with_stmt);
}
@@ -1268,7 +1257,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.any_enabled(&[
Rule::EnumerateForLoop,
Rule::IncorrectDictIterator,
Rule::LoopIteratorMutation,
Rule::UnnecessaryEnumerate,
Rule::UnusedLoopControlVariable,
Rule::YieldInForLoop,
@@ -1479,9 +1467,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.settings.rules.enabled(Rule::TypeBivariance) {
pylint::rules::type_bivariance(checker, value);
}
if checker.enabled(Rule::NonAugmentedAssignment) {
pylint::rules::non_augmented_assignment(checker, assign);
}
if checker.settings.rules.enabled(Rule::UnsortedDunderAll) {
ruff::rules::sort_dunder_all_assign(checker, assign);
}

View File

@@ -56,10 +56,9 @@ impl AnnotationContext {
_ => {}
}
// If `__future__` annotations are enabled or it's a stub file,
// then annotations are never evaluated at runtime,
// so we can treat them as typing-only.
if semantic.future_annotations_or_stub() {
// If `__future__` annotations are enabled, then annotations are never evaluated
// at runtime, so we can treat them as typing-only.
if semantic.future_annotations() {
return Self::TypingOnly;
}
@@ -88,7 +87,7 @@ impl AnnotationContext {
semantic,
) {
Self::RuntimeRequired
} else if semantic.future_annotations_or_stub() {
} else if semantic.future_annotations() {
Self::TypingOnly
} else {
Self::RuntimeEvaluated

View File

@@ -12,8 +12,6 @@ pub(crate) struct Visit<'a> {
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
pub(crate) functions: Vec<Snapshot>,
pub(crate) lambdas: Vec<Snapshot>,
/// N.B. This field should always be empty unless it's a stub file
pub(crate) class_bases: Vec<(&'a Expr, Snapshot)>,
}
impl Visit<'_> {
@@ -24,7 +22,6 @@ impl Visit<'_> {
&& self.type_param_definitions.is_empty()
&& self.functions.is_empty()
&& self.lambdas.is_empty()
&& self.class_bases.is_empty()
}
}

View File

@@ -31,14 +31,15 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext, FStringElement,
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_notebook::{CellOffsets, NotebookIndex};
use ruff_python_ast::all::{extract_all_names, DunderAllDefinition, DunderAllFlags};
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
use ruff_python_ast::helpers::{
collect_import_from_member, extract_handled_exceptions, is_docstring_stmt, to_module_path,
};
@@ -571,7 +572,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
match stmt {
Stmt::FunctionDef(
function_def @ ast::StmtFunctionDef {
name,
body,
parameters,
decorator_list,
@@ -691,21 +691,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
if let Some(globals) = Globals::from_body(body) {
self.semantic.set_globals(globals);
}
let scope_id = self.semantic.scope_id;
self.analyze.scopes.push(scope_id);
self.semantic.pop_scope(); // Function scope
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
BindingKind::FunctionDefinition(scope_id),
BindingFlags::empty(),
);
}
Stmt::ClassDef(
class_def @ ast::StmtClassDef {
name,
body,
arguments,
decorator_list,
@@ -724,9 +712,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
}
if let Some(arguments) = arguments {
self.semantic.flags |= SemanticModelFlags::CLASS_BASE;
self.visit_arguments(arguments);
self.semantic.flags -= SemanticModelFlags::CLASS_BASE;
}
let definition = docstrings::extraction::extract_definition(
@@ -746,18 +732,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
// Set the docstring state before visiting the class body.
self.docstring_state = DocstringState::Expected;
self.visit_body(body);
let scope_id = self.semantic.scope_id;
self.analyze.scopes.push(scope_id);
self.semantic.pop_scope(); // Class scope
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
BindingKind::ClassDefinition(scope_id),
BindingFlags::empty(),
);
}
Stmt::TypeAlias(ast::StmtTypeAlias {
range: _,
@@ -914,6 +888,35 @@ impl<'a> Visitor<'a> for Checker<'a> {
};
// Step 3: Clean-up
match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
let scope_id = self.semantic.scope_id;
self.analyze.scopes.push(scope_id);
self.semantic.pop_scope(); // Function scope
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
BindingKind::FunctionDefinition(scope_id),
BindingFlags::empty(),
);
}
Stmt::ClassDef(ast::StmtClassDef { name, .. }) => {
let scope_id = self.semantic.scope_id;
self.analyze.scopes.push(scope_id);
self.semantic.pop_scope(); // Class scope
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
BindingKind::ClassDefinition(scope_id),
BindingFlags::empty(),
);
}
_ => {}
}
// Step 4: Analysis
analyze::statement(stmt, self);
@@ -932,23 +935,10 @@ impl<'a> Visitor<'a> for Checker<'a> {
fn visit_expr(&mut self, expr: &'a Expr) {
// Step 0: Pre-processing
if self.source_type.is_stub()
&& self.semantic.in_class_base()
&& !self.semantic.in_deferred_class_base()
{
self.visit
.class_bases
.push((expr, self.semantic.snapshot()));
return;
}
if !self.semantic.in_typing_literal()
// `in_deferred_type_definition()` will only be `true` if we're now visiting the deferred nodes
// after having already traversed the source tree once. If we're now visiting the deferred nodes,
// we can't defer again, or we'll infinitely recurse!
&& !self.semantic.in_deferred_type_definition()
&& self.semantic.in_type_definition()
&& self.semantic.future_annotations_or_stub()
&& self.semantic.future_annotations()
&& (self.semantic.in_annotation() || self.source_type.is_stub())
{
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
@@ -1590,15 +1580,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
.push((bound, self.semantic.snapshot()));
}
}
fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) {
let snapshot = self.semantic.flags;
if f_string_element.is_expression() {
self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD;
}
visitor::walk_f_string_element(self, f_string_element);
self.semantic.flags = snapshot;
}
}
impl<'a> Checker<'a> {
@@ -1975,52 +1956,6 @@ impl<'a> Checker<'a> {
scope.add(id, binding_id);
}
/// After initial traversal of the AST, visit all class bases that were deferred.
///
/// This method should only be relevant in stub files, where forward references are
/// legal in class bases. For other kinds of Python files, using a forward reference
/// in a class base is never legal, so `self.visit.class_bases` should always be empty.
///
/// For example, in a stub file:
/// ```python
/// class Foo(list[Bar]): ... # <-- `Bar` is a forward reference in a class base
/// class Bar: ...
/// ```
fn visit_deferred_class_bases(&mut self) {
let snapshot = self.semantic.snapshot();
let deferred_bases = std::mem::take(&mut self.visit.class_bases);
debug_assert!(
self.source_type.is_stub() || deferred_bases.is_empty(),
"Class bases should never be deferred outside of stub files"
);
for (expr, snapshot) in deferred_bases {
self.semantic.restore(snapshot);
// Set this flag to avoid infinite recursion, or we'll just defer it again:
self.semantic.flags |= SemanticModelFlags::DEFERRED_CLASS_BASE;
self.visit_expr(expr);
}
self.semantic.restore(snapshot);
}
/// After initial traversal of the AST, visit all "future type definitions".
///
/// A "future type definition" is a type definition where [PEP 563] semantics
/// apply (i.e., an annotation in a module that has `from __future__ import annotations`
/// at the top of the file, or an annotation in a stub file). These type definitions
/// support forward references, so they are deferred on initial traversal
/// of the source tree.
///
/// For example:
/// ```python
/// from __future__ import annotations
///
/// def foo() -> Bar: # <-- return annotation is a "future type definition"
/// return Bar()
///
/// class Bar: pass
/// ```
///
/// [PEP 563]: https://peps.python.org/pep-0563/
fn visit_deferred_future_type_definitions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.visit.future_type_definitions.is_empty() {
@@ -2028,14 +1963,6 @@ impl<'a> Checker<'a> {
for (expr, snapshot) in type_definitions {
self.semantic.restore(snapshot);
// Type definitions should only be considered "`__future__` type definitions"
// if they are annotations in a module where `from __future__ import
// annotations` is active, or they are type definitions in a stub file.
debug_assert!(
self.semantic.future_annotations_or_stub()
&& (self.source_type.is_stub() || self.semantic.in_annotation())
);
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION
| SemanticModelFlags::FUTURE_TYPE_DEFINITION;
self.visit_expr(expr);
@@ -2044,19 +1971,6 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
}
/// After initial traversal of the AST, visit all [type parameter definitions].
///
/// Type parameters natively support forward references,
/// so are always deferred during initial traversal of the source tree.
///
/// For example:
/// ```python
/// class Foo[T: Bar]: pass # <-- Forward reference used in definition of type parameter `T`
/// type X[T: Bar] = Foo[T] # <-- Ditto
/// class Bar: pass
/// ```
///
/// [type parameter definitions]: https://docs.python.org/3/reference/executionmodel.html#annotation-scopes
fn visit_deferred_type_param_definitions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.visit.type_param_definitions.is_empty() {
@@ -2072,17 +1986,6 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
}
/// After initial traversal of the AST, visit all "string type definitions",
/// i.e., type definitions that are enclosed within quotes so as to allow
/// the type definition to use forward references.
///
/// For example:
/// ```python
/// def foo() -> "Bar": # <-- return annotation is a "string type definition"
/// return Bar()
///
/// class Bar: pass
/// ```
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
let snapshot = self.semantic.snapshot();
while !self.visit.string_type_definitions.is_empty() {
@@ -2095,7 +1998,7 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
if self.semantic.in_annotation() && self.semantic.future_annotations_or_stub() {
if self.semantic.in_annotation() && self.semantic.future_annotations() {
if self.enabled(Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, value, range);
}
@@ -2131,11 +2034,6 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
}
/// After initial traversal of the AST, visit all function bodies.
///
/// Function bodies are always deferred on initial traversal of the source tree,
/// as the body of a function may validly contain references to global-scope symbols
/// that were not yet defined at the point when the function was defined.
fn visit_deferred_functions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.visit.functions.is_empty() {
@@ -2159,9 +2057,8 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
}
/// After initial traversal of the source tree has been completed,
/// visit all lambdas. Lambdas are deferred during the initial traversal
/// for the same reason as function bodies.
/// Visit all deferred lambdas. Returns a list of snapshots, such that the caller can restore
/// the semantic model to the state it was in before visiting the deferred lambdas.
fn visit_deferred_lambdas(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.visit.lambdas.is_empty() {
@@ -2187,12 +2084,10 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
}
/// After initial traversal of the source tree has been completed,
/// recursively visit all AST nodes that were deferred on the first pass.
/// This includes lambdas, functions, type parameters, and type annotations.
/// Recursively visit all deferred AST nodes, including lambdas, functions, and type
/// annotations.
fn visit_deferred(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
while !self.visit.is_empty() {
self.visit_deferred_class_bases();
self.visit_deferred_functions();
self.visit_deferred_type_param_definitions();
self.visit_deferred_lambdas();
@@ -2205,54 +2100,45 @@ impl<'a> Checker<'a> {
fn visit_exports(&mut self) {
let snapshot = self.semantic.snapshot();
let definitions: Vec<DunderAllDefinition> = self
let exports: Vec<DunderAllName> = self
.semantic
.global_scope()
.get_all("__all__")
.map(|binding_id| &self.semantic.bindings[binding_id])
.filter_map(|binding| match &binding.kind {
BindingKind::Export(Export { names }) => {
Some(DunderAllDefinition::new(binding.range(), names.to_vec()))
}
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
_ => None,
})
.flatten()
.collect();
for definition in definitions {
for export in definition.names() {
let (name, range) = (export.name(), export.range());
if let Some(binding_id) = self.semantic.global_scope().get(name) {
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
// Mark anything referenced in `__all__` as used.
self.semantic
.add_global_reference(binding_id, ExprContext::Load, range);
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
for export in exports {
let (name, range) = (export.name(), export.range());
if let Some(binding_id) = self.semantic.global_scope().get(name) {
self.semantic.flags |= SemanticModelFlags::DUNDER_ALL_DEFINITION;
// Mark anything referenced in `__all__` as used.
self.semantic
.add_global_reference(binding_id, ExprContext::Load, range);
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
} else {
if self.semantic.global_scope().uses_star_imports() {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.diagnostics.push(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: name.to_string(),
},
range,
));
}
} else {
if self.semantic.global_scope().uses_star_imports() {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.diagnostics.push(
Diagnostic::new(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: name.to_string(),
},
range,
)
.with_parent(definition.start()),
);
}
} else {
if self.enabled(Rule::UndefinedExport) {
if !self.path.ends_with("__init__.py") {
self.diagnostics.push(
Diagnostic::new(
pyflakes::rules::UndefinedExport {
name: name.to_string(),
},
range,
)
.with_parent(definition.start()),
);
}
if self.enabled(Rule::UndefinedExport) {
if !self.path.ends_with("__init__.py") {
self.diagnostics.push(Diagnostic::new(
pyflakes::rules::UndefinedExport {
name: name.to_string(),
},
range,
));
}
}
}

View File

@@ -225,12 +225,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
#[allow(deprecated)]
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
(Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall),
#[allow(deprecated)]
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName),
(Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall),
(Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit),
(Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit),
@@ -272,7 +272,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R0203") => (RuleGroup::Preview, rules::pylint::rules::NoStaticmethodDecorator),
(Pylint, "R0206") => (RuleGroup::Stable, rules::pylint::rules::PropertyWithParameters),
(Pylint, "R0402") => (RuleGroup::Stable, rules::pylint::rules::ManualFromImport),
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
(Pylint, "R0911") => (RuleGroup::Stable, rules::pylint::rules::TooManyReturnStatements),
(Pylint, "R0912") => (RuleGroup::Stable, rules::pylint::rules::TooManyBranches),
(Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments),
@@ -283,34 +282,31 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
(Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks),
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
(Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary),
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
(Pylint, "R1730") => (RuleGroup::Preview, rules::pylint::rules::IfStmtMinMax),
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
(Pylint, "R2044") => (RuleGroup::Preview, rules::pylint::rules::EmptyComment),
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
(Pylint, "R6104") => (RuleGroup::Preview, rules::pylint::rules::NonAugmentedAssignment),
(Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership),
#[allow(deprecated)]
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
(Pylint, "W0177") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
(Pylint, "W0117") => (RuleGroup::Preview, rules::pylint::rules::NanComparison),
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
(Pylint, "W0128") => (RuleGroup::Preview, rules::pylint::rules::RedeclaredAssignedName),
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),
(Pylint, "W0211") => (RuleGroup::Preview, rules::pylint::rules::BadStaticmethodArgument),
(Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets),
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
@@ -320,6 +316,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
#[allow(deprecated)]
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
#[allow(deprecated)]
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
@@ -379,7 +376,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
// flake8-blind-except
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
@@ -550,7 +546,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
(Pyupgrade, "042") => (RuleGroup::Preview, rules::pyupgrade::rules::ReplaceStrEnum),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -1039,9 +1034,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// refurb
(Refurb, "101") => (RuleGroup::Preview, rules::refurb::rules::ReadWholeFile),
(Refurb, "103") => (RuleGroup::Preview, rules::refurb::rules::WriteWholeFile),
(Refurb, "105") => (RuleGroup::Preview, rules::refurb::rules::PrintEmptyString),
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
#[allow(deprecated)]
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),

View File

@@ -0,0 +1,19 @@
use anstream::stream::RawStream;
use anstream::{AutoStream, ColorChoice};
pub fn none<S: RawStream>(stream: S) -> AutoStream<S> {
AutoStream::new(stream, ColorChoice::Never)
}
pub fn auto<S: RawStream>(stream: S) -> AutoStream<S> {
let choice = choice(&stream);
AutoStream::new(stream, choice)
}
pub fn choice<S: RawStream>(stream: &S) -> ColorChoice {
AutoStream::choice(stream)
}
pub fn enabled<S: RawStream>(stream: &S) -> bool {
choice(stream) != ColorChoice::Never
}

View File

@@ -1,46 +1,36 @@
use std::path::{Path, PathBuf};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::Absolutize;
use crate::registry::RuleSet;
use crate::settings::types::CompiledPerFileIgnoreList;
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path(path: &Path, ignore_list: &CompiledPerFileIgnoreList) -> RuleSet {
pub(crate) fn ignores_from_path(
path: &Path,
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, RuleSet)],
) -> RuleSet {
let file_name = path.file_name().expect("Unable to parse filename");
ignore_list
pattern_code_pairs
.iter()
.filter_map(|entry| {
if entry.basename_matcher.is_match(file_name) {
if entry.negated { None } else {
debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
entry.basename_matcher.glob().regex(),
entry.rules
);
Some(&entry.rules)
}
} else if entry.absolute_matcher.is_match(path) {
if entry.negated { None } else {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
entry.absolute_matcher.glob().regex(),
entry.rules
);
Some(&entry.rules)
}
} else if entry.negated {
.filter_map(|(absolute, basename, rules)| {
if basename.is_match(file_name) {
debug!(
"Adding per-file ignores for {:?} due to negated pattern matching neither {:?} nor {:?}: {:?}",
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
entry.basename_matcher.glob().regex(),
entry.absolute_matcher.glob().regex(),
entry.rules
basename.glob().regex(),
rules
);
Some(&entry.rules)
Some(rules)
} else if absolute.is_match(path) {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.glob().regex(),
rules
);
Some(rules)
} else {
None
}

View File

@@ -16,6 +16,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
mod checkers;
pub mod codes;
pub mod colors;
mod comments;
mod cst;
pub mod directives;

View File

@@ -2,10 +2,11 @@ use std::borrow::Cow;
use std::ops::Deref;
use std::path::Path;
use anstream::eprintln;
use anyhow::{anyhow, Result};
use colored::Colorize;
use itertools::Itertools;
use log::error;
use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;

View File

@@ -3,15 +3,16 @@ use std::path::{Path, PathBuf};
use std::sync::Mutex;
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
use once_cell::sync::Lazy;
use owo_colors::OwoColorize;
use ruff_python_parser::{ParseError, ParseErrorType};
use rustc_hash::FxHashSet;
use ruff_source_file::{LineIndex, OneIndexed, SourceCode, SourceLocation};
use crate::colors;
use crate::fs;
use crate::source_kind::SourceKind;
use ruff_notebook::Notebook;
@@ -22,7 +23,7 @@ pub static IDENTIFIERS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::defaul
#[macro_export]
macro_rules! warn_user_once_by_id {
($id:expr, $($arg:tt)*) => {
use colored::Colorize;
use owo_colors::OwoColorize;
use log::warn;
if let Ok(mut states) = $crate::logging::IDENTIFIERS.lock() {
@@ -42,7 +43,7 @@ pub static MESSAGES: Lazy<Mutex<FxHashSet<String>>> = Lazy::new(Mutex::default);
#[macro_export]
macro_rules! warn_user_once_by_message {
($($arg:tt)*) => {
use colored::Colorize;
use owo_colors::OwoColorize;
use log::warn;
if let Ok(mut states) = $crate::logging::MESSAGES.lock() {
@@ -59,7 +60,7 @@ macro_rules! warn_user_once_by_message {
#[macro_export]
macro_rules! warn_user_once {
($($arg:tt)*) => {
use colored::Colorize;
use owo_colors::OwoColorize;
use log::warn;
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
@@ -73,7 +74,7 @@ macro_rules! warn_user_once {
#[macro_export]
macro_rules! warn_user {
($($arg:tt)*) => {{
use colored::Colorize;
use owo_colors::OwoColorize;
use log::warn;
let message = format!("{}", format_args!($($arg)*));
@@ -152,7 +153,10 @@ pub fn set_up_logging(level: LogLevel) -> Result<()> {
})
.level(level.level_filter())
.level_for("globset", log::LevelFilter::Warn)
.chain(std::io::stderr())
.chain(fern::Output::writer(
Box::new(colors::auto(std::io::stderr())),
"\n",
))
.apply()?;
Ok(())
}

View File

@@ -1,7 +1,7 @@
use std::fmt::{Display, Formatter};
use std::num::NonZeroUsize;
use colored::{Color, ColoredString, Colorize, Styles};
use owo_colors::{OwoColorize, Style};
use ruff_text_size::{Ranged, TextRange, TextSize};
use similar::{ChangeTag, TextDiff};
@@ -81,7 +81,7 @@ impl Display for Diff<'_> {
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag());
let line_style = diff_line_style(change.tag());
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
@@ -97,14 +97,14 @@ impl Display for Diff<'_> {
index: new_index,
width: digit_with
},
line_style.apply_to(sign).bold()
sign.style(line_style).bold()
)?;
for (emphasized, value) in change.iter_strings_lossy() {
if emphasized {
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
write!(f, "{}", value.style(line_style).underline().on_black())?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
write!(f, "{}", value.style(line_style))?;
}
}
if change.missing_newline() {
@@ -118,52 +118,11 @@ impl Display for Diff<'_> {
}
}
struct LineStyle {
fgcolor: Option<Color>,
style: Option<Styles>,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> ColoredString {
let mut colored = ColoredString::from(input);
if let Some(color) = self.fgcolor {
colored = colored.color(color);
}
if let Some(style) = self.style {
match style {
Styles::Clear => colored.clear(),
Styles::Bold => colored.bold(),
Styles::Dimmed => colored.dimmed(),
Styles::Underline => colored.underline(),
Styles::Reversed => colored.reversed(),
Styles::Italic => colored.italic(),
Styles::Blink => colored.blink(),
Styles::Hidden => colored.hidden(),
Styles::Strikethrough => colored.strikethrough(),
}
} else {
colored
}
}
}
impl From<ChangeTag> for LineStyle {
fn from(value: ChangeTag) -> Self {
match value {
ChangeTag::Equal => LineStyle {
fgcolor: None,
style: Some(Styles::Dimmed),
},
ChangeTag::Delete => LineStyle {
fgcolor: Some(Color::Red),
style: None,
},
ChangeTag::Insert => LineStyle {
fgcolor: Some(Color::Green),
style: None,
},
}
fn diff_line_style(change_tag: ChangeTag) -> Style {
match change_tag {
ChangeTag::Equal => Style::new().dimmed(),
ChangeTag::Delete => Style::new().red(),
ChangeTag::Insert => Style::new().green(),
}
}

View File

@@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
use std::io::Write;
use std::num::NonZeroUsize;
use colored::Colorize;
use owo_colors::OwoColorize;
use ruff_notebook::NotebookIndex;
use ruff_source_file::OneIndexed;

View File

@@ -156,6 +156,7 @@ mod tests {
use ruff_source_file::{OneIndexed, SourceFileBuilder};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::colors;
use crate::message::{Emitter, EmitterContext, Message};
pub(super) fn create_messages() -> Vec<Message> {
@@ -337,10 +338,10 @@ def foo():
) -> String {
let notebook_indexes = FxHashMap::default();
let context = EmitterContext::new(&notebook_indexes);
let mut output: Vec<u8> = Vec::new();
let mut output = colors::none(Vec::new());
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
}
pub(super) fn capture_emitter_notebook_output(
@@ -349,9 +350,9 @@ def foo():
notebook_indexes: &FxHashMap<String, NotebookIndex>,
) -> String {
let context = EmitterContext::new(notebook_indexes);
let mut output: Vec<u8> = Vec::new();
let mut output = colors::none(Vec::new());
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
String::from_utf8(output.into_inner()).expect("Output to be valid UTF-8")
}
}

View File

@@ -5,12 +5,13 @@ use std::io::Write;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use bitflags::bitflags;
use colored::Colorize;
use owo_colors::OwoColorize;
use ruff_notebook::NotebookIndex;
use ruff_source_file::{OneIndexed, SourceLocation};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::colors;
use crate::fs::relativize_path;
use crate::line_width::{IndentWidth, LineWidthBuilder};
use crate::message::diff::Diff;
@@ -284,10 +285,7 @@ impl Display for MessageCodeFrame<'_> {
}],
footer,
opt: FormatOptions {
#[cfg(test)]
color: false,
#[cfg(not(test))]
color: colored::control::SHOULD_COLORIZE.should_colorize(),
color: colors::enabled(&std::io::stdout()),
..FormatOptions::default()
},
};

View File

@@ -147,7 +147,7 @@ impl<'a> Directive<'a> {
/// Lex an individual rule code (e.g., `F401`).
#[inline]
pub(crate) fn lex_code(line: &str) -> Option<&str> {
fn lex_code(line: &str) -> Option<&str> {
// Extract, e.g., the `F` in `F401`.
let prefix = line.chars().take_while(char::is_ascii_uppercase).count();
// Extract, e.g., the `401` in `F401`.

View File

@@ -1,5 +1,5 @@
use colored::Colorize;
use log::warn;
use owo_colors::OwoColorize;
use pyproject_toml::PyProjectToml;
use ruff_text_size::{TextRange, TextSize};

View File

@@ -109,7 +109,5 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// Test redirect by prefix
#[cfg(feature = "test-rules")]
("RUF96", "RUF95"),
// See: https://github.com/astral-sh/ruff/issues/10791
("PLW0117", "PLW0177"),
])
});

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,

View File

@@ -10,7 +10,7 @@ pub fn default_tmp_dirs() -> Vec<String> {
.to_vec()
}
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub hardcoded_tmp_directory: Vec<String>,
pub check_typed_exception: bool,

View File

@@ -6,7 +6,7 @@ use ruff_macros::CacheKey;
use crate::display_settings;
#[derive(Debug, Clone, CacheKey, Default)]
#[derive(Debug, CacheKey, Default)]
pub struct Settings {
pub extend_allowed_calls: Vec<String>,
}

View File

@@ -61,7 +61,6 @@ mod tests {
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
#[test_case(Rule::LoopIteratorMutation, Path::new("B909.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,295 +0,0 @@
use std::collections::HashMap;
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::name::UnqualifiedName;
use ruff_python_ast::{
visitor::{self, Visitor},
Arguments, Expr, ExprAttribute, ExprCall, ExprSubscript, Stmt, StmtAssign, StmtAugAssign,
StmtBreak, StmtDelete, StmtFor, StmtIf,
};
use ruff_text_size::TextRange;
use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet;
/// ## What it does
/// Checks for mutations to an iterable during a loop iteration.
///
/// ## Why is this bad?
/// When iterating over an iterable, mutating the iterable can lead to unexpected
/// behavior, like skipping elements or infinite loops.
///
/// ## Example
/// ```python
/// items = [1, 2, 3]
///
/// for item in items:
/// print(item)
///
/// # Create an infinite loop by appending to the list.
/// items.append(item)
/// ```
///
/// ## References
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#typesseq-mutable)
#[violation]
pub struct LoopIteratorMutation {
name: Option<SourceCodeSnippet>,
}
impl Violation for LoopIteratorMutation {
#[derive_message_formats]
fn message(&self) -> String {
let LoopIteratorMutation { name } = self;
if let Some(name) = name.as_ref().and_then(SourceCodeSnippet::full_display) {
format!("Mutation to loop iterable `{name}` during iteration")
} else {
format!("Mutation to loop iterable during iteration")
}
}
}
/// B909
pub(crate) fn loop_iterator_mutation(checker: &mut Checker, stmt_for: &StmtFor) {
let StmtFor {
target,
iter,
body,
orelse: _,
is_async: _,
range: _,
} = stmt_for;
if !matches!(iter.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
return;
}
// Collect mutations to the iterable.
let mutations = {
let mut visitor = LoopMutationsVisitor::new(iter, target);
visitor.visit_body(body);
visitor.mutations
};
// Create a diagnostic for each mutation.
for mutation in mutations.values().flatten() {
let name = UnqualifiedName::from_expr(iter)
.map(|name| name.to_string())
.map(SourceCodeSnippet::new);
checker
.diagnostics
.push(Diagnostic::new(LoopIteratorMutation { name }, *mutation));
}
}
/// Returns `true` if the method mutates when called on an iterator.
fn is_mutating_function(function_name: &str) -> bool {
matches!(
function_name,
"append"
| "sort"
| "reverse"
| "remove"
| "clear"
| "extend"
| "insert"
| "pop"
| "popitem"
| "setdefault"
| "update"
| "intersection_update"
| "difference_update"
| "symmetric_difference_update"
| "add"
| "discard"
)
}
/// A visitor to collect mutations to a variable in a loop.
#[derive(Debug, Clone)]
struct LoopMutationsVisitor<'a> {
iter: &'a Expr,
target: &'a Expr,
mutations: HashMap<u8, Vec<TextRange>>,
branches: Vec<u8>,
branch: u8,
}
impl<'a> LoopMutationsVisitor<'a> {
/// Initialize the visitor.
fn new(iter: &'a Expr, target: &'a Expr) -> Self {
Self {
iter,
target,
mutations: HashMap::new(),
branches: vec![0],
branch: 0,
}
}
/// Register a mutation.
fn add_mutation(&mut self, range: TextRange) {
self.mutations.entry(self.branch).or_default().push(range);
}
/// Handle, e.g., `del items[0]`.
fn handle_delete(&mut self, range: TextRange, targets: &[Expr]) {
for target in targets {
if let Expr::Subscript(ExprSubscript {
range: _,
value,
slice,
ctx: _,
}) = target
{
// Find, e.g., `del items[0]`.
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
// But allow, e.g., `for item in items: del items[item]`.
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
self.add_mutation(range);
}
}
}
}
}
/// Handle, e.g., `items[0] = 1`.
fn handle_assign(&mut self, range: TextRange, targets: &[Expr]) {
for target in targets {
if let Expr::Subscript(ExprSubscript {
range: _,
value,
slice,
ctx: _,
}) = target
{
// Find, e.g., `items[0] = 1`.
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
// But allow, e.g., `for item in items: items[item] = 1`.
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
self.add_mutation(range);
}
}
}
}
}
/// Handle, e.g., `items += [1]`.
fn handle_aug_assign(&mut self, range: TextRange, target: &Expr) {
if ComparableExpr::from(self.iter) == ComparableExpr::from(target) {
self.add_mutation(range);
}
}
/// Handle, e.g., `items.append(1)`.
fn handle_call(&mut self, func: &Expr, arguments: &Arguments) {
if let Expr::Attribute(ExprAttribute {
range,
value,
attr,
ctx: _,
}) = func
{
if is_mutating_function(attr.as_str()) {
// Find, e.g., `items.remove(1)`.
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
// But allow, e.g., `for item in items: items.remove(item)`.
if matches!(attr.as_str(), "remove" | "discard" | "pop") {
if arguments.len() == 1 {
if let [arg] = &*arguments.args {
if ComparableExpr::from(self.target) == ComparableExpr::from(arg) {
return;
}
}
}
}
self.add_mutation(*range);
}
}
}
}
}
/// `Visitor` to collect all used identifiers in a statement.
impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match stmt {
// Ex) `del items[0]`
Stmt::Delete(StmtDelete { range, targets }) => {
self.handle_delete(*range, targets);
visitor::walk_stmt(self, stmt);
}
// Ex) `items[0] = 1`
Stmt::Assign(StmtAssign { range, targets, .. }) => {
self.handle_assign(*range, targets);
visitor::walk_stmt(self, stmt);
}
// Ex) `items += [1]`
Stmt::AugAssign(StmtAugAssign { range, target, .. }) => {
self.handle_aug_assign(*range, target);
visitor::walk_stmt(self, stmt);
}
// Ex) `if True: items.append(1)`
Stmt::If(StmtIf {
test,
body,
elif_else_clauses,
..
}) => {
// Handle the `if` branch.
self.branch += 1;
self.branches.push(self.branch);
self.visit_expr(test);
self.visit_body(body);
self.branches.pop();
// Handle the `elif` and `else` branches.
for clause in elif_else_clauses {
self.branch += 1;
self.branches.push(self.branch);
if let Some(test) = &clause.test {
self.visit_expr(test);
}
self.visit_body(&clause.body);
self.branches.pop();
}
}
// On break, clear the mutations for the current branch.
Stmt::Break(StmtBreak { range: _ }) => {
if let Some(mutations) = self.mutations.get_mut(&self.branch) {
mutations.clear();
}
visitor::walk_stmt(self, stmt);
}
// Avoid recursion for class and function definitions.
Stmt::ClassDef(_) | Stmt::FunctionDef(_) => {}
// Default case.
_ => {
visitor::walk_stmt(self, stmt);
}
}
}
fn visit_expr(&mut self, expr: &'a Expr) {
// Ex) `items.append(1)`
if let Expr::Call(ExprCall {
func, arguments, ..
}) = expr
{
self.handle_call(func, arguments);
}
visitor::walk_expr(self, expr);
}
}

View File

@@ -12,7 +12,6 @@ pub(crate) use function_call_in_argument_default::*;
pub(crate) use function_uses_loop_variable::*;
pub(crate) use getattr_with_constant::*;
pub(crate) use jump_statement_in_finally::*;
pub(crate) use loop_iterator_mutation::*;
pub(crate) use loop_variable_overrides_iterator::*;
pub(crate) use mutable_argument_default::*;
pub(crate) use no_explicit_stacklevel::*;
@@ -48,7 +47,6 @@ mod function_call_in_argument_default;
mod function_uses_loop_variable;
mod getattr_with_constant;
mod jump_statement_in_finally;
mod loop_iterator_mutation;
mod loop_variable_overrides_iterator;
mod mutable_argument_default;
mod no_explicit_stacklevel;

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub extend_immutable_calls: Vec<String>,
}

View File

@@ -1,341 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B909.py:12:5: B909 Mutation to loop iterable `some_list` during iteration
|
10 | for elem in some_list:
11 | # errors
12 | some_list.remove(0)
| ^^^^^^^^^^^^^^^^ B909
13 | del some_list[2]
14 | some_list.append(elem)
|
B909.py:13:5: B909 Mutation to loop iterable `some_list` during iteration
|
11 | # errors
12 | some_list.remove(0)
13 | del some_list[2]
| ^^^^^^^^^^^^^^^^ B909
14 | some_list.append(elem)
15 | some_list.sort()
|
B909.py:14:5: B909 Mutation to loop iterable `some_list` during iteration
|
12 | some_list.remove(0)
13 | del some_list[2]
14 | some_list.append(elem)
| ^^^^^^^^^^^^^^^^ B909
15 | some_list.sort()
16 | some_list.reverse()
|
B909.py:15:5: B909 Mutation to loop iterable `some_list` during iteration
|
13 | del some_list[2]
14 | some_list.append(elem)
15 | some_list.sort()
| ^^^^^^^^^^^^^^ B909
16 | some_list.reverse()
17 | some_list.clear()
|
B909.py:16:5: B909 Mutation to loop iterable `some_list` during iteration
|
14 | some_list.append(elem)
15 | some_list.sort()
16 | some_list.reverse()
| ^^^^^^^^^^^^^^^^^ B909
17 | some_list.clear()
18 | some_list.extend([1, 2])
|
B909.py:17:5: B909 Mutation to loop iterable `some_list` during iteration
|
15 | some_list.sort()
16 | some_list.reverse()
17 | some_list.clear()
| ^^^^^^^^^^^^^^^ B909
18 | some_list.extend([1, 2])
19 | some_list.insert(1, 1)
|
B909.py:18:5: B909 Mutation to loop iterable `some_list` during iteration
|
16 | some_list.reverse()
17 | some_list.clear()
18 | some_list.extend([1, 2])
| ^^^^^^^^^^^^^^^^ B909
19 | some_list.insert(1, 1)
20 | some_list.pop(1)
|
B909.py:19:5: B909 Mutation to loop iterable `some_list` during iteration
|
17 | some_list.clear()
18 | some_list.extend([1, 2])
19 | some_list.insert(1, 1)
| ^^^^^^^^^^^^^^^^ B909
20 | some_list.pop(1)
21 | some_list.pop()
|
B909.py:20:5: B909 Mutation to loop iterable `some_list` during iteration
|
18 | some_list.extend([1, 2])
19 | some_list.insert(1, 1)
20 | some_list.pop(1)
| ^^^^^^^^^^^^^ B909
21 | some_list.pop()
|
B909.py:21:5: B909 Mutation to loop iterable `some_list` during iteration
|
19 | some_list.insert(1, 1)
20 | some_list.pop(1)
21 | some_list.pop()
| ^^^^^^^^^^^^^ B909
22 |
23 | # conditional break should error
|
B909.py:25:9: B909 Mutation to loop iterable `some_list` during iteration
|
23 | # conditional break should error
24 | if elem == 2:
25 | some_list.remove(0)
| ^^^^^^^^^^^^^^^^ B909
26 | if elem == 3:
27 | break
|
B909.py:47:5: B909 Mutation to loop iterable `mydicts` during iteration
|
45 | for elem in mydicts:
46 | # errors
47 | mydicts.popitem()
| ^^^^^^^^^^^^^^^ B909
48 | mydicts.setdefault("foo", 1)
49 | mydicts.update({"foo": "bar"})
|
B909.py:48:5: B909 Mutation to loop iterable `mydicts` during iteration
|
46 | # errors
47 | mydicts.popitem()
48 | mydicts.setdefault("foo", 1)
| ^^^^^^^^^^^^^^^^^^ B909
49 | mydicts.update({"foo": "bar"})
|
B909.py:49:5: B909 Mutation to loop iterable `mydicts` during iteration
|
47 | mydicts.popitem()
48 | mydicts.setdefault("foo", 1)
49 | mydicts.update({"foo": "bar"})
| ^^^^^^^^^^^^^^ B909
50 |
51 | # no errors
|
B909.py:62:5: B909 Mutation to loop iterable `myset` during iteration
|
60 | for _ in myset:
61 | # errors
62 | myset.update({4, 5})
| ^^^^^^^^^^^^ B909
63 | myset.intersection_update({4, 5})
64 | myset.difference_update({4, 5})
|
B909.py:63:5: B909 Mutation to loop iterable `myset` during iteration
|
61 | # errors
62 | myset.update({4, 5})
63 | myset.intersection_update({4, 5})
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B909
64 | myset.difference_update({4, 5})
65 | myset.symmetric_difference_update({4, 5})
|
B909.py:64:5: B909 Mutation to loop iterable `myset` during iteration
|
62 | myset.update({4, 5})
63 | myset.intersection_update({4, 5})
64 | myset.difference_update({4, 5})
| ^^^^^^^^^^^^^^^^^^^^^^^ B909
65 | myset.symmetric_difference_update({4, 5})
66 | myset.add(4)
|
B909.py:65:5: B909 Mutation to loop iterable `myset` during iteration
|
63 | myset.intersection_update({4, 5})
64 | myset.difference_update({4, 5})
65 | myset.symmetric_difference_update({4, 5})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B909
66 | myset.add(4)
67 | myset.discard(3)
|
B909.py:66:5: B909 Mutation to loop iterable `myset` during iteration
|
64 | myset.difference_update({4, 5})
65 | myset.symmetric_difference_update({4, 5})
66 | myset.add(4)
| ^^^^^^^^^ B909
67 | myset.discard(3)
|
B909.py:67:5: B909 Mutation to loop iterable `myset` during iteration
|
65 | myset.symmetric_difference_update({4, 5})
66 | myset.add(4)
67 | myset.discard(3)
| ^^^^^^^^^^^^^ B909
68 |
69 | # no errors
|
B909.py:84:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
82 | # ensure member accesses are handled as errors
83 | for elem in a.some_list:
84 | a.some_list.remove(0)
| ^^^^^^^^^^^^^^^^^^ B909
85 | del a.some_list[2]
|
B909.py:85:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
83 | for elem in a.some_list:
84 | a.some_list.remove(0)
85 | del a.some_list[2]
| ^^^^^^^^^^^^^^^^^^ B909
|
B909.py:93:5: B909 Mutation to loop iterable `foo` during iteration
|
91 | bar = [4, 5, 6]
92 | for _ in foo:
93 | foo *= 2
| ^^^^^^^^ B909
94 | foo += bar
95 | foo[1] = 9
|
B909.py:94:5: B909 Mutation to loop iterable `foo` during iteration
|
92 | for _ in foo:
93 | foo *= 2
94 | foo += bar
| ^^^^^^^^^^ B909
95 | foo[1] = 9
96 | foo[1:2] = bar
|
B909.py:95:5: B909 Mutation to loop iterable `foo` during iteration
|
93 | foo *= 2
94 | foo += bar
95 | foo[1] = 9
| ^^^^^^^^^^ B909
96 | foo[1:2] = bar
97 | foo[1:2:3] = bar
|
B909.py:96:5: B909 Mutation to loop iterable `foo` during iteration
|
94 | foo += bar
95 | foo[1] = 9
96 | foo[1:2] = bar
| ^^^^^^^^^^^^^^ B909
97 | foo[1:2:3] = bar
|
B909.py:97:5: B909 Mutation to loop iterable `foo` during iteration
|
95 | foo[1] = 9
96 | foo[1:2] = bar
97 | foo[1:2:3] = bar
| ^^^^^^^^^^^^^^^^ B909
98 |
99 | foo = {1, 2, 3}
|
B909.py:102:5: B909 Mutation to loop iterable `foo` during iteration
|
100 | bar = {4, 5, 6}
101 | for _ in foo: # should error
102 | foo |= bar
| ^^^^^^^^^^ B909
103 | foo &= bar
104 | foo -= bar
|
B909.py:103:5: B909 Mutation to loop iterable `foo` during iteration
|
101 | for _ in foo: # should error
102 | foo |= bar
103 | foo &= bar
| ^^^^^^^^^^ B909
104 | foo -= bar
105 | foo ^= bar
|
B909.py:104:5: B909 Mutation to loop iterable `foo` during iteration
|
102 | foo |= bar
103 | foo &= bar
104 | foo -= bar
| ^^^^^^^^^^ B909
105 | foo ^= bar
|
B909.py:105:5: B909 Mutation to loop iterable `foo` during iteration
|
103 | foo &= bar
104 | foo -= bar
105 | foo ^= bar
| ^^^^^^^^^^ B909
|
B909.py:125:5: B909 Mutation to loop iterable `foo` during iteration
|
123 | # should error (?)
124 | for _ in foo:
125 | foo.remove(1)
| ^^^^^^^^^^ B909
126 | if bar:
127 | bar.remove(1)
|
B909.py:136:9: B909 Mutation to loop iterable `foo` during iteration
|
134 | pass
135 | else:
136 | foo.remove(1)
| ^^^^^^^^^^ B909
137 |
138 | # should error
|
B909.py:140:8: B909 Mutation to loop iterable `some_list` during iteration
|
138 | # should error
139 | for elem in some_list:
140 | if some_list.pop() == 2:
| ^^^^^^^^^^^^^ B909
141 | pass
|
B909.py:150:8: B909 Mutation to loop iterable `some_list` during iteration
|
148 | # should error
149 | for elem in some_list:
150 | if some_list.pop() == 2:
| ^^^^^^^^^^^^^ B909
151 | pass
152 | else:
|

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub builtins_ignorelist: Vec<String>,
}

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub allow_dict_calls_with_keyword_arguments: bool,
}

View File

@@ -7,7 +7,7 @@ use std::fmt::{Display, Formatter};
use crate::display_settings;
use ruff_macros::CacheKey;
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub notice_rgx: Regex,
pub author: Option<String>,

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub max_string_length: usize,
}

View File

@@ -2,7 +2,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub functions_names: Vec<String>,
}

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub allow_multiline: bool,
}

View File

@@ -57,7 +57,7 @@ impl FromIterator<String> for BannedAliases {
}
}
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub aliases: FxHashMap<String, String>,
pub banned_aliases: FxHashMap<String, BannedAliases>,

View File

@@ -1,10 +1,9 @@
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt};
use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, Parameters, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze;
use ruff_python_semantic::analyze::visibility::{is_abstract, is_final, is_overload};
use ruff_python_semantic::{ScopeKind, SemanticModel};
@@ -120,9 +119,7 @@ pub(crate) fn non_self_return_type(
returns: Option<&Expr>,
parameters: &Parameters,
) {
let semantic = checker.semantic();
let ScopeKind::Class(class_def) = semantic.current_scope().kind else {
let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
return;
};
@@ -135,19 +132,21 @@ pub(crate) fn non_self_return_type(
};
// PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses.
if is_metaclass(class_def, semantic) {
if is_metaclass(class_def, checker.semantic()) {
return;
}
// Skip any abstract or overloaded methods.
if is_abstract(decorator_list, semantic) || is_overload(decorator_list, semantic) {
if is_abstract(decorator_list, checker.semantic())
|| is_overload(decorator_list, checker.semantic())
{
return;
}
if is_async {
if name == "__aenter__"
&& is_name(returns, &class_def.name)
&& !is_final(&class_def.decorator_list, semantic)
&& !is_final(&class_def.decorator_list, checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@@ -162,7 +161,7 @@ pub(crate) fn non_self_return_type(
// In-place methods that are expected to return `Self`.
if is_inplace_bin_op(name) {
if !is_self(returns, semantic) {
if !is_self(returns, checker.semantic()) {
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
class_name: class_def.name.to_string(),
@@ -175,7 +174,8 @@ pub(crate) fn non_self_return_type(
}
if is_name(returns, &class_def.name) {
if matches!(name, "__enter__" | "__new__") && !is_final(&class_def.decorator_list, semantic)
if matches!(name, "__enter__" | "__new__")
&& !is_final(&class_def.decorator_list, checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@@ -190,8 +190,8 @@ pub(crate) fn non_self_return_type(
match name {
"__iter__" => {
if is_iterable_or_iterator(returns, semantic)
&& subclasses_iterator(class_def, semantic)
if is_iterable(returns, checker.semantic())
&& is_iterator(class_def.arguments.as_deref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@@ -203,8 +203,8 @@ pub(crate) fn non_self_return_type(
}
}
"__aiter__" => {
if is_async_iterable_or_iterator(returns, semantic)
&& subclasses_async_iterator(class_def, semantic)
if is_async_iterable(returns, checker.semantic())
&& is_async_iterator(class_def.arguments.as_deref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
NonSelfReturnType {
@@ -221,14 +221,26 @@ pub(crate) fn non_self_return_type(
/// Returns `true` if the given class is a metaclass.
fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
matches!(
qualified_name.segments(),
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
)
class_def.arguments.as_ref().is_some_and(|arguments| {
arguments
.args
.iter()
.any(|expr| is_metaclass_base(expr, semantic))
})
}
/// Returns `true` if the given expression resolves to a metaclass.
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(base)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
)
})
}
/// Returns `true` if the method is an in-place binary operator.
fn is_inplace_bin_op(name: &str) -> bool {
matches!(
@@ -263,17 +275,24 @@ fn is_self(expr: &Expr, semantic: &SemanticModel) -> bool {
}
/// Return `true` if the given class extends `collections.abc.Iterator`.
fn subclasses_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
matches!(
qualified_name.segments(),
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
)
fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
bases.iter().any(|expr| {
semantic
.resolve_qualified_name(map_subscript(expr))
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["typing", "Iterator"] | ["collections", "abc", "Iterator"]
)
})
})
}
/// Return `true` if the given expression resolves to `collections.abc.Iterable` or `collections.abc.Iterator`.
fn is_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
/// Return `true` if the given expression resolves to `collections.abc.Iterable`.
fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(map_subscript(expr))
.is_some_and(|qualified_name| {
@@ -286,17 +305,24 @@ fn is_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
}
/// Return `true` if the given class extends `collections.abc.AsyncIterator`.
fn subclasses_async_iterator(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
matches!(
qualified_name.segments(),
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
)
fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
let Some(Arguments { args: bases, .. }) = arguments else {
return false;
};
bases.iter().any(|expr| {
semantic
.resolve_qualified_name(map_subscript(expr))
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
)
})
})
}
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable` or `collections.abc.AsyncIterator`.
fn is_async_iterable_or_iterator(expr: &Expr, semantic: &SemanticModel) -> bool {
/// Return `true` if the given expression resolves to `collections.abc.AsyncIterable`.
fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(map_subscript(expr))
.is_some_and(|qualified_name| {

View File

@@ -89,20 +89,4 @@ PYI034.py:195:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` u
|
= help: Consider using `typing_extensions.Self` as return type
PYI034.py:199:9: PYI034 `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
|
198 | class SubclassOfBadIterator3(BadIterator3):
199 | def __iter__(self) -> Iterator[int]: # Y034
| ^^^^^^^^ PYI034
200 | ...
|
= help: Consider using `typing_extensions.Self` as return type
PYI034.py:203:9: PYI034 `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
|
202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
203 | def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
| ^^^^^^^^^ PYI034
204 | ...
|
= help: Consider using `typing_extensions.Self` as return type

View File

@@ -567,7 +567,7 @@ fn check_values(checker: &mut Checker, names: &Expr, values: &Expr) {
// Replace `]` with `)` or `,)`.
let values_end = Edit::replacement(
if needs_trailing_comma {
",)".into()
"),".into()
} else {
")".into()
},

View File

@@ -24,7 +24,7 @@ pub fn default_broad_exceptions() -> Vec<IdentifierPattern> {
.to_vec()
}
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub fixture_parentheses: bool,
pub parametrize_names_type: types::ParametrizeNameType,

View File

@@ -168,7 +168,7 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^^^^^^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `list` of `list` for parameter values
@@ -179,8 +179,8 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), [(3, 4), (5, 6)])
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass
PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `list` of `list`
|
@@ -188,7 +188,7 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `list` of `list` for parameter values
@@ -199,8 +199,8 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), ([3, 4], (5, 6)))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass
PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `list` of `list`
|
@@ -208,7 +208,7 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `list` of `list` for parameter values
@@ -219,5 +219,5 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), ((3, 4), [5, 6]))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass

View File

@@ -210,7 +210,7 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^^^^^^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `list` of `tuple` for parameter values
@@ -221,5 +221,5 @@ PT007.py:81:38: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), [(3, 4), (5, 6)])
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass

View File

@@ -237,7 +237,7 @@ PT007.py:80:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
80 |+@pytest.mark.parametrize("a", (1, 2))
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
83 83 | def test_multiple_decorators(a, b, c):
PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
@@ -245,7 +245,7 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `tuple` of `list` for parameter values
@@ -256,8 +256,8 @@ PT007.py:81:39: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), ([3, 4], (5, 6)))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass
PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
@@ -265,7 +265,7 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
| ^^^^^^ PT007
82 | @pytest.mark.parametrize("d", [3,])
83 | @pytest.mark.parametrize(
83 | def test_multiple_decorators(a, b, c):
|
= help: Use `tuple` of `list` for parameter values
@@ -276,8 +276,8 @@ PT007.py:81:47: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 |-@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
81 |+@pytest.mark.parametrize(("b", "c"), ((3, 4), [5, 6]))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass
PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
@@ -285,8 +285,8 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 | @pytest.mark.parametrize("d", [3,])
| ^^^^ PT007
83 | @pytest.mark.parametrize(
84 | "d",
83 | def test_multiple_decorators(a, b, c):
84 | pass
|
= help: Use `tuple` of `list` for parameter values
@@ -296,48 +296,5 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 |-@pytest.mark.parametrize("d", [3,])
82 |+@pytest.mark.parametrize("d", (3,))
83 83 | @pytest.mark.parametrize(
84 84 | "d",
85 85 | [("3", "4")],
PT007.py:85:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
83 | @pytest.mark.parametrize(
84 | "d",
85 | [("3", "4")],
| ^^^^^^^^^^^^ PT007
86 | )
87 | @pytest.mark.parametrize(
|
= help: Use `tuple` of `list` for parameter values
Unsafe fix
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
85 |- [("3", "4")],
85 |+ (("3", "4"),),
86 86 | )
87 87 | @pytest.mark.parametrize(
88 88 | "e",
PT007.py:89:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `list`
|
87 | @pytest.mark.parametrize(
88 | "e",
89 | [("3", "4"),],
| ^^^^^^^^^^^^^ PT007
90 | )
91 | def test_multiple_decorators(a, b, c, d, e):
|
= help: Use `tuple` of `list` for parameter values
Unsafe fix
86 86 | )
87 87 | @pytest.mark.parametrize(
88 88 | "e",
89 |- [("3", "4"),],
89 |+ (("3", "4"),),
90 90 | )
91 91 | def test_multiple_decorators(a, b, c, d, e):
92 92 | pass
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass

View File

@@ -279,7 +279,7 @@ PT007.py:80:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
80 |+@pytest.mark.parametrize("a", (1, 2))
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
83 83 | def test_multiple_decorators(a, b, c):
PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
@@ -287,8 +287,8 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 | @pytest.mark.parametrize("d", [3,])
| ^^^^ PT007
83 | @pytest.mark.parametrize(
84 | "d",
83 | def test_multiple_decorators(a, b, c):
84 | pass
|
= help: Use `tuple` of `tuple` for parameter values
@@ -298,48 +298,5 @@ PT007.py:82:31: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expect
81 81 | @pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
82 |-@pytest.mark.parametrize("d", [3,])
82 |+@pytest.mark.parametrize("d", (3,))
83 83 | @pytest.mark.parametrize(
84 84 | "d",
85 85 | [("3", "4")],
PT007.py:85:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
83 | @pytest.mark.parametrize(
84 | "d",
85 | [("3", "4")],
| ^^^^^^^^^^^^ PT007
86 | )
87 | @pytest.mark.parametrize(
|
= help: Use `tuple` of `tuple` for parameter values
Unsafe fix
82 82 | @pytest.mark.parametrize("d", [3,])
83 83 | @pytest.mark.parametrize(
84 84 | "d",
85 |- [("3", "4")],
85 |+ (("3", "4"),),
86 86 | )
87 87 | @pytest.mark.parametrize(
88 88 | "e",
PT007.py:89:5: PT007 [*] Wrong values type in `@pytest.mark.parametrize` expected `tuple` of `tuple`
|
87 | @pytest.mark.parametrize(
88 | "e",
89 | [("3", "4"),],
| ^^^^^^^^^^^^^ PT007
90 | )
91 | def test_multiple_decorators(a, b, c, d, e):
|
= help: Use `tuple` of `tuple` for parameter values
Unsafe fix
86 86 | )
87 87 | @pytest.mark.parametrize(
88 88 | "e",
89 |- [("3", "4"),],
89 |+ (("3", "4"),),
90 90 | )
91 91 | def test_multiple_decorators(a, b, c, d, e):
92 92 | pass
83 83 | def test_multiple_decorators(a, b, c):
84 84 | pass

View File

@@ -449,8 +449,13 @@ pub(crate) fn check_string_quotes(checker: &mut Checker, string_like: StringLike
return;
}
// TODO(dhruvmanila): Support checking for escaped quotes in f-strings.
if checker.semantic().in_f_string_replacement_field() {
// If the string is part of a f-string, ignore it.
if checker
.indexer()
.fstring_ranges()
.outermost(string_like.start())
.is_some_and(|outer| outer.start() < string_like.start() && string_like.end() < outer.end())
{
return;
}

View File

@@ -31,7 +31,7 @@ impl From<ruff_python_ast::str::Quote> for Quote {
}
}
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub inline_quotes: Quote,
pub multiline_quotes: Quote,

View File

@@ -17,7 +17,7 @@ pub const IGNORE_NAMES: [&str; 7] = [
"_value_",
];
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub ignore_names: Vec<String>,
}

View File

@@ -41,8 +41,8 @@ use crate::fix::snippet::SourceCodeSnippet;
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct NeedlessBool {
condition: Option<SourceCodeSnippet>,
negate: bool,
condition: SourceCodeSnippet,
replacement: Option<SourceCodeSnippet>,
}
impl Violation for NeedlessBool {
@@ -50,22 +50,21 @@ impl Violation for NeedlessBool {
#[derive_message_formats]
fn message(&self) -> String {
let NeedlessBool { condition, negate } = self;
if let Some(condition) = condition.as_ref().and_then(SourceCodeSnippet::full_display) {
let NeedlessBool { condition, .. } = self;
if let Some(condition) = condition.full_display() {
format!("Return the condition `{condition}` directly")
} else if *negate {
format!("Return the negated condition directly")
} else {
format!("Return the condition directly")
}
}
fn fix_title(&self) -> Option<String> {
let NeedlessBool { condition, .. } = self;
if let Some(condition) = condition.as_ref().and_then(SourceCodeSnippet::full_display) {
Some(format!("Replace with `return {condition}`"))
let NeedlessBool { replacement, .. } = self;
if let Some(replacement) = replacement
.as_ref()
.and_then(SourceCodeSnippet::full_display)
{
Some(format!("Replace with `{replacement}`"))
} else {
Some(format!("Inline condition"))
}
@@ -192,21 +191,29 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
return;
}
// Generate the replacement condition.
let condition = if checker.indexer().has_comments(&range, checker.locator()) {
let condition = checker.locator().slice(if_test);
let replacement = if checker.indexer().has_comments(&range, checker.locator()) {
None
} else {
// If the return values are inverted, wrap the condition in a `not`.
if inverted {
Some(Expr::UnaryOp(ast::ExprUnaryOp {
op: ast::UnaryOp::Not,
operand: Box::new(if_test.clone()),
let node = ast::StmtReturn {
value: Some(Box::new(Expr::UnaryOp(ast::ExprUnaryOp {
op: ast::UnaryOp::Not,
operand: Box::new(if_test.clone()),
range: TextRange::default(),
}))),
range: TextRange::default(),
}))
};
Some(checker.generator().stmt(&node.into()))
} else if if_test.is_compare_expr() {
// If the condition is a comparison, we can replace it with the condition, since we
// know it's a boolean.
Some(if_test.clone())
let node = ast::StmtReturn {
value: Some(Box::new(if_test.clone())),
range: TextRange::default(),
};
Some(checker.generator().stmt(&node.into()))
} else if checker.semantic().is_builtin("bool") {
// Otherwise, we need to wrap the condition in a call to `bool`.
let func_node = ast::ExprName {
@@ -214,7 +221,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
ctx: ExprContext::Load,
range: TextRange::default(),
};
let call_node = ast::ExprCall {
let value_node = ast::ExprCall {
func: Box::new(func_node.into()),
arguments: Arguments {
args: Box::from([if_test.clone()]),
@@ -223,32 +230,20 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
},
range: TextRange::default(),
};
Some(Expr::Call(call_node))
let return_node = ast::StmtReturn {
value: Some(Box::new(value_node.into())),
range: TextRange::default(),
};
Some(checker.generator().stmt(&return_node.into()))
} else {
None
}
};
// Generate the replacement `return` statement.
let replacement = condition.as_ref().map(|expr| {
Stmt::Return(ast::StmtReturn {
value: Some(Box::new(expr.clone())),
range: TextRange::default(),
})
});
// Generate source code.
let replacement = replacement
.as_ref()
.map(|stmt| checker.generator().stmt(stmt));
let condition = condition
.as_ref()
.map(|expr| checker.generator().expr(expr));
let mut diagnostic = Diagnostic::new(
NeedlessBool {
condition: condition.map(SourceCodeSnippet::new),
negate: inverted,
condition: SourceCodeSnippet::from_str(condition),
replacement: replacement.clone().map(SourceCodeSnippet::new),
},
range,
);

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM103.py:3:5: SIM103 [*] Return the condition `bool(a)` directly
SIM103.py:3:5: SIM103 [*] Return the condition `a` directly
|
1 | def f():
2 | # SIM103
@@ -52,7 +52,7 @@ SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly
16 13 |
17 14 | def f():
SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
SIM103.py:21:5: SIM103 [*] Return the condition `b` directly
|
19 | if a:
20 | return 1
@@ -78,7 +78,7 @@ SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
26 23 |
27 24 | def f():
SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
SIM103.py:32:9: SIM103 [*] Return the condition `b` directly
|
30 | return 1
31 | else:
@@ -104,10 +104,10 @@ SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
37 34 |
38 35 | def f():
SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
SIM103.py:57:5: SIM103 [*] Return the condition `a` directly
|
55 | def f():
56 | # SIM103
56 | # SIM103 (but not fixable)
57 | if a:
| _____^
58 | | return False
@@ -120,7 +120,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
Unsafe fix
54 54 |
55 55 | def f():
56 56 | # SIM103
56 56 | # SIM103 (but not fixable)
57 |- if a:
58 |- return False
59 |- else:
@@ -130,7 +130,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
62 59 |
63 60 | def f():
SIM103.py:83:5: SIM103 Return the condition directly
SIM103.py:83:5: SIM103 Return the condition `a` directly
|
81 | def bool():
82 | return False
@@ -142,29 +142,3 @@ SIM103.py:83:5: SIM103 Return the condition directly
| |____________________^ SIM103
|
= help: Inline condition
SIM103.py:91:5: SIM103 [*] Return the condition `not (keys is not None and notice.key not in keys)` directly
|
89 | def f():
90 | # SIM103
91 | if keys is not None and notice.key not in keys:
| _____^
92 | | return False
93 | | else:
94 | | return True
| |___________________^ SIM103
|
= help: Replace with `return not (keys is not None and notice.key not in keys)`
Unsafe fix
88 88 |
89 89 | def f():
90 90 | # SIM103
91 |- if keys is not None and notice.key not in keys:
92 |- return False
93 |- else:
94 |- return True
91 |+ return not (keys is not None and notice.key not in keys)
95 92 |
96 93 |
97 94 | ###

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM103.py:3:5: SIM103 [*] Return the condition `bool(a)` directly
SIM103.py:3:5: SIM103 [*] Return the condition `a` directly
|
1 | def f():
2 | # SIM103
@@ -52,7 +52,7 @@ SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly
16 13 |
17 14 | def f():
SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
SIM103.py:21:5: SIM103 [*] Return the condition `b` directly
|
19 | if a:
20 | return 1
@@ -78,7 +78,7 @@ SIM103.py:21:5: SIM103 [*] Return the condition `bool(b)` directly
26 23 |
27 24 | def f():
SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
SIM103.py:32:9: SIM103 [*] Return the condition `b` directly
|
30 | return 1
31 | else:
@@ -104,10 +104,10 @@ SIM103.py:32:9: SIM103 [*] Return the condition `bool(b)` directly
37 34 |
38 35 | def f():
SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
SIM103.py:57:5: SIM103 [*] Return the condition `a` directly
|
55 | def f():
56 | # SIM103
56 | # SIM103 (but not fixable)
57 | if a:
| _____^
58 | | return False
@@ -120,7 +120,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
Unsafe fix
54 54 |
55 55 | def f():
56 56 | # SIM103
56 56 | # SIM103 (but not fixable)
57 |- if a:
58 |- return False
59 |- else:
@@ -130,7 +130,7 @@ SIM103.py:57:5: SIM103 [*] Return the condition `not a` directly
62 59 |
63 60 | def f():
SIM103.py:83:5: SIM103 Return the condition directly
SIM103.py:83:5: SIM103 Return the condition `a` directly
|
81 | def bool():
82 | return False
@@ -143,73 +143,47 @@ SIM103.py:83:5: SIM103 Return the condition directly
|
= help: Inline condition
SIM103.py:91:5: SIM103 [*] Return the condition `not (keys is not None and notice.key not in keys)` directly
SIM103.py:96:5: SIM103 [*] Return the condition `a` directly
|
89 | def f():
90 | # SIM103
91 | if keys is not None and notice.key not in keys:
94 | def f():
95 | # SIM103
96 | if a:
| _____^
92 | | return False
93 | | else:
94 | | return True
| |___________________^ SIM103
97 | | return True
98 | | return False
| |________________^ SIM103
|
= help: Replace with `return not (keys is not None and notice.key not in keys)`
= help: Replace with `return bool(a)`
Unsafe fix
88 88 |
89 89 | def f():
90 90 | # SIM103
91 |- if keys is not None and notice.key not in keys:
92 |- return False
93 |- else:
94 |- return True
91 |+ return not (keys is not None and notice.key not in keys)
95 92 |
96 93 |
97 94 | ###
93 93 |
94 94 | def f():
95 95 | # SIM103
96 |- if a:
97 |- return True
98 |- return False
96 |+ return bool(a)
99 97 |
100 98 |
101 99 | def f():
SIM103.py:104:5: SIM103 [*] Return the condition `bool(a)` directly
SIM103.py:103:5: SIM103 [*] Return the condition `a` directly
|
102 | def f():
103 | # SIM103
104 | if a:
101 | def f():
102 | # SIM103
103 | if a:
| _____^
105 | | return True
106 | | return False
| |________________^ SIM103
|
= help: Replace with `return bool(a)`
Unsafe fix
101 101 |
102 102 | def f():
103 103 | # SIM103
104 |- if a:
105 |- return True
106 |- return False
104 |+ return bool(a)
107 105 |
108 106 |
109 107 | def f():
SIM103.py:111:5: SIM103 [*] Return the condition `not a` directly
|
109 | def f():
110 | # SIM103
111 | if a:
| _____^
112 | | return False
113 | | return True
104 | | return False
105 | | return True
| |_______________^ SIM103
|
= help: Replace with `return not a`
Unsafe fix
108 108 |
109 109 | def f():
110 110 | # SIM103
111 |- if a:
112 |- return False
113 |- return True
111 |+ return not a
100 100 |
101 101 | def f():
102 102 | # SIM103
103 |- if a:
104 |- return False
105 |- return True
103 |+ return not a

View File

@@ -1,16 +1,17 @@
use std::fmt;
use ruff_python_ast as ast;
use ruff_python_ast::{Arguments, Expr, StmtClassDef};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, identifier::Identifier, Arguments, Expr, Stmt, StmtClassDef};
use ruff_python_semantic::SemanticModel;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::Stmt;
use crate::checkers::ast::Checker;
use crate::rules::flake8_slots::rules::helpers::has_slots;
/// ## What it does
/// Checks for subclasses of `collections.namedtuple` or `typing.NamedTuple`
/// that lack a `__slots__` definition.
/// Checks for subclasses of `collections.namedtuple` that lack a `__slots__`
/// definition.
///
/// ## Why is this bad?
/// In Python, the `__slots__` attribute allows you to explicitly define the
@@ -47,28 +48,12 @@ use crate::rules::flake8_slots::rules::helpers::has_slots;
/// ## References
/// - [Python documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)
#[violation]
pub struct NoSlotsInNamedtupleSubclass(NamedTupleKind);
pub struct NoSlotsInNamedtupleSubclass;
impl Violation for NoSlotsInNamedtupleSubclass {
#[derive_message_formats]
fn message(&self) -> String {
let NoSlotsInNamedtupleSubclass(namedtuple_kind) = self;
format!("Subclasses of {namedtuple_kind} should define `__slots__`")
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum NamedTupleKind {
Collections,
Typing,
}
impl fmt::Display for NamedTupleKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Collections => "`collections.namedtuple()`",
Self::Typing => "call-based `typing.NamedTuple()`",
})
format!("Subclasses of `collections.namedtuple()` should define `__slots__`")
}
}
@@ -82,33 +67,22 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
return;
};
if let Some(namedtuple_kind) = namedtuple_base(bases, checker.semantic()) {
if bases.iter().any(|base| {
let Expr::Call(ast::ExprCall { func, .. }) = base else {
return false;
};
checker
.semantic()
.resolve_qualified_name(func)
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["collections", "namedtuple"])
})
}) {
if !has_slots(&class.body) {
checker.diagnostics.push(Diagnostic::new(
NoSlotsInNamedtupleSubclass(namedtuple_kind),
NoSlotsInNamedtupleSubclass,
stmt.identifier(),
));
}
}
}
/// If the class has a call-based namedtuple in its bases,
/// return the kind of namedtuple it is
/// (either `collections.namedtuple()`, or `typing.NamedTuple()`).
/// Else, return `None`.
fn namedtuple_base(bases: &[Expr], semantic: &SemanticModel) -> Option<NamedTupleKind> {
for base in bases {
let Expr::Call(ast::ExprCall { func, .. }) = base else {
continue;
};
let Some(qualified_name) = semantic.resolve_qualified_name(func) else {
continue;
};
match qualified_name.segments() {
["collections", "namedtuple"] => return Some(NamedTupleKind::Collections),
["typing", "NamedTuple"] => return Some(NamedTupleKind::Typing),
_ => continue,
}
}
None
}

View File

@@ -8,9 +8,4 @@ SLOT002.py:5:7: SLOT002 Subclasses of `collections.namedtuple()` should define `
6 | pass
|
SLOT002.py:9:7: SLOT002 Subclasses of call-based `typing.NamedTuple()` should define `__slots__`
|
9 | class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
| ^^^^^^^^^^^^^^^^^^ SLOT002
10 | pass
|

View File

@@ -39,7 +39,7 @@ impl Display for Strictness {
}
}
#[derive(Debug, Clone, CacheKey, Default)]
#[derive(Debug, CacheKey, Default)]
pub struct Settings {
pub ban_relative_imports: Strictness,
pub banned_api: FxHashMap<String, ApiBan>,

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub strict: bool,
pub exempt_modules: Vec<String>,

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub ignore_variadic_names: bool,
}

View File

@@ -270,7 +270,7 @@ pub(crate) fn categorize_imports<'a>(
block_by_type
}
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct KnownModules {
/// A map of known modules to their section.
known: Vec<(glob::Pattern, ImportSection)>,

View File

@@ -44,7 +44,7 @@ impl Display for RelativeImportsOrder {
}
}
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub required_imports: BTreeSet<String>,

View File

@@ -4,7 +4,7 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub max_complexity: usize,
}

View File

@@ -11,7 +11,7 @@ use ruff_macros::CacheKey;
use crate::display_settings;
#[derive(Debug, Clone, CacheKey)]
#[derive(Debug, CacheKey)]
pub struct Settings {
pub ignore_names: IgnoreNames,
pub classmethod_decorators: Vec<String>,
@@ -85,7 +85,7 @@ static DEFAULTS: &[&str] = &[
"maxDiff",
];
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum IgnoreNames {
Default,
UserProvided {

View File

@@ -6,7 +6,7 @@ use std::fmt;
use crate::line_width::LineLength;
#[derive(Debug, Clone, Default, CacheKey)]
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub max_line_length: LineLength,
pub max_doc_length: Option<LineLength>,

View File

@@ -59,36 +59,26 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
}
let body = docstring.body();
let first_word = body.split_once(' ').map_or_else(
|| {
// If the docstring is a single word, trim the punctuation marks because
// it makes the ASCII test below fail.
body.trim_end_matches(['.', '!', '?'])
},
|(first_word, _)| first_word,
);
let Some(first_word) = body.split(' ').next() else {
return;
};
// Like pydocstyle, we only support ASCII for now.
for char in first_word.chars() {
if !char.is_ascii_alphabetic() && char != '\'' {
return;
}
}
let mut first_word_chars = first_word.chars();
let Some(first_char) = first_word_chars.next() else {
return;
};
if !first_char.is_ascii() {
return;
}
let uppercase_first_char = first_char.to_ascii_uppercase();
if first_char == uppercase_first_char {
return;
}
// Like pydocstyle, we only support ASCII for now.
for char in first_word.chars().skip(1) {
if !char.is_ascii_alphabetic() && char != '\'' {
return;
}
}
let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str();
let mut diagnostic = Diagnostic::new(

Some files were not shown because too many files have changed in this diff Show More