Compare commits

..

2 Commits

Author SHA1 Message Date
konstin
63ef7652ed Fix test 2023-10-13 17:14:17 +02:00
konstin
1743ef8398 Remove empty line before raw dostrings
**Summary** This fixes a deviation with black where black would remove
empty lines before raw docstrings for some reason.
2023-10-13 16:04:01 +02:00
190 changed files with 2044 additions and 3767 deletions

View File

@@ -47,7 +47,7 @@ jobs:
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.3.1
uses: cloudflare/wrangler-action@v3.2.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -40,7 +40,7 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.3.1
uses: cloudflare/wrangler-action@v3.2.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -2,16 +2,6 @@
## 0.1.0
### The deprecated `format` setting has been removed
Ruff previously used the `format` setting, `--format` CLI option, and `RUFF_FORMAT` environment variable to
configure the output format of the CLI. This usage was deprecated in `v0.0.291` — the `format` setting is now used
to control Ruff's code formatting. As of this release:
- The `format` setting cannot be used to configure the output format, use `output-format` instead
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
### Unsafe fixes are not applied by default ([#7769](https://github.com/astral-sh/ruff/pull/7769))
Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe

View File

@@ -1,115 +0,0 @@
# Changelog
This is the first release which uses the `CHANGELOG` file. See [GitHub Releases](https://github.com/astral-sh/ruff/releases) for prior changelog entries.
Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
## 0.1.0
### Breaking changes
- Unsafe fixes are no longer displayed or applied without opt-in ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Drop formatting specific rules from the default set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
- The deprecated `format` setting has been removed ([#7984](https://github.com/astral-sh/ruff/pull/7984))
- The `format` setting cannot be used to configure the output format, use `output-format` instead
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
### Rule changes
- Extend `reimplemented-starmap` (`FURB140`) to catch calls with a single and starred argument ([#7768](https://github.com/astral-sh/ruff/pull/7768))
- Improve cases covered by `RUF015` ([#7848](https://github.com/astral-sh/ruff/pull/7848))
- Update `SIM15` to allow `open` followed by `close` ([#7916](https://github.com/astral-sh/ruff/pull/7916))
- Respect `msgspec.Struct` default-copy semantics in `RUF012` ([#7786](https://github.com/astral-sh/ruff/pull/7786))
- Add `sqlalchemy` methods to \`flake8-boolean-trap\`\` exclusion list ([#7874](https://github.com/astral-sh/ruff/pull/7874))
- Add fix for `PLR1714` ([#7910](https://github.com/astral-sh/ruff/pull/7910))
- Add fix for `PIE804` ([#7884](https://github.com/astral-sh/ruff/pull/7884))
- Add fix for `PLC0208` ([#7887](https://github.com/astral-sh/ruff/pull/7887))
- Add fix for `PYI055` ([#7886](https://github.com/astral-sh/ruff/pull/7886))
- Update `non-pep695-type-alias` to require `--unsafe-fixes` outside of stub files ([#7836](https://github.com/astral-sh/ruff/pull/7836))
- Improve fix message for `UP018` ([#7913](https://github.com/astral-sh/ruff/pull/7913))
- Update `PLW3201` to support `Enum` [sunder names](https://docs.python.org/3/library/enum.html#supported-sunder-names) ([#7987](https://github.com/astral-sh/ruff/pull/7987))
### Preview features
- Only show warnings for empty preview selectors when enabling rules ([#7842](https://github.com/astral-sh/ruff/pull/7842))
- Add `unnecessary-key-check` to simplify `key in dct and dct[key]` to `dct.get(key)` ([#7895](https://github.com/astral-sh/ruff/pull/7895))
- Add `assignment-in-assert` to prevent walrus expressions in assert statements ([#7856](https://github.com/astral-sh/ruff/pull/7856))
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
### Configuration
- Add `unsafe-fixes` setting ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Add `extend-safe-fixes` and `extend-unsafe-fixes` for promoting and demoting fixes ([#7841](https://github.com/astral-sh/ruff/pull/7841))
### CLI
- Added `--unsafe-fixes` option for opt-in to display and apply unsafe fixes ([#7769](https://github.com/astral-sh/ruff/pull/7769))
- Fix use of deprecated `--format` option in warning ([#7837](https://github.com/astral-sh/ruff/pull/7837))
- Show changed files when running under `--check` ([#7788](https://github.com/astral-sh/ruff/pull/7788))
- Write summary messages to stderr when fixing via stdin instead of omitting them ([#7838](https://github.com/astral-sh/ruff/pull/7838))
- Update fix summary message in `check --diff` to include unsafe fix hints ([#7790](https://github.com/astral-sh/ruff/pull/7790))
- Add notebook `cell` field to JSON output format ([#7664](https://github.com/astral-sh/ruff/pull/7664))
- Rename applicability levels to `Safe`, `Unsafe`, and `Display` ([#7843](https://github.com/astral-sh/ruff/pull/7843))
### Bug fixes
- Fix bug where f-strings were allowed in match pattern literal ([#7857](https://github.com/astral-sh/ruff/pull/7857))
- Fix `SIM110` with a yield in the condition ([#7801](https://github.com/astral-sh/ruff/pull/7801))
- Preserve trailing comments in `C414` fixes ([#7775](https://github.com/astral-sh/ruff/pull/7775))
- Check sequence type before triggering `unnecessary-enumerate` `len` suggestion ([#7781](https://github.com/astral-sh/ruff/pull/7781))
- Use correct start location for class/function clause header ([#7802](https://github.com/astral-sh/ruff/pull/7802))
- Fix incorrect fixes for `SIM101` ([#7798](https://github.com/astral-sh/ruff/pull/7798))
- Format comment before parameter default correctly ([#7870](https://github.com/astral-sh/ruff/pull/7870))
- Fix `E251` false positive inside f-strings ([#7894](https://github.com/astral-sh/ruff/pull/7894))
- Allow bindings to be created and referenced within annotations ([#7885](https://github.com/astral-sh/ruff/pull/7885))
- Show per-cell diffs when analyzing notebooks over `stdin` ([#7789](https://github.com/astral-sh/ruff/pull/7789))
- Avoid curly brace escape in f-string format spec ([#7780](https://github.com/astral-sh/ruff/pull/7780))
- Fix lexing single-quoted f-string with multi-line format spec ([#7787](https://github.com/astral-sh/ruff/pull/7787))
- Consider nursery rules to be in-preview for `ruff rule` ([#7812](https://github.com/astral-sh/ruff/pull/7812))
- Report precise location for invalid conversion flag ([#7809](https://github.com/astral-sh/ruff/pull/7809))
- Visit pattern match guard as a boolean test ([#7911](https://github.com/astral-sh/ruff/pull/7911))
- Respect `--unfixable` in `ISC` rules ([#7917](https://github.com/astral-sh/ruff/pull/7917))
- Fix edge case with `PIE804` ([#7922](https://github.com/astral-sh/ruff/pull/7922))
- Show custom message in `PTH118` for `Path.joinpath` with starred arguments ([#7852](https://github.com/astral-sh/ruff/pull/7852))
- Fix false negative in `outdated-version-block` when using greater than comparisons ([#7920](https://github.com/astral-sh/ruff/pull/7920))
- Avoid converting f-strings within Django `gettext` calls ([#7898](https://github.com/astral-sh/ruff/pull/7898))
- Fix false positive in `PLR6301` ([#7933](https://github.com/astral-sh/ruff/pull/7933))
- Treat type aliases as typing-only expressions e.g. resolves false positive in `TCH004` ([#7968](https://github.com/astral-sh/ruff/pull/7968))
- Resolve `cache-dir` relative to project root ([#7962](https://github.com/astral-sh/ruff/pull/7962))
- Respect subscripted base classes in type-checking rules e.g. resolves false positive in `TCH003` ([#7954](https://github.com/astral-sh/ruff/pull/7954))
- Fix JSON schema limit for `line-length` ([#7883](https://github.com/astral-sh/ruff/pull/7883))
- Fix commented-out `coalesce` keyword ([#7876](https://github.com/astral-sh/ruff/pull/7876))
### Documentation
- Document `reimplemented-starmap` performance effects ([#7846](https://github.com/astral-sh/ruff/pull/7846))
- Default to following the system dark/light mode ([#7888](https://github.com/astral-sh/ruff/pull/7888))
- Add documentation for fixes ([#7901](https://github.com/astral-sh/ruff/pull/7901))
- Fix typo in docs of `PLR6301` ([#7831](https://github.com/astral-sh/ruff/pull/7831))
- Update `UP038` docs to note that it results in slower code ([#7872](https://github.com/astral-sh/ruff/pull/7872))
- crlf -> cr-lf ([#7766](https://github.com/astral-sh/ruff/pull/7766))
- Add an example of an unsafe fix ([#7924](https://github.com/astral-sh/ruff/pull/7924))
- Fix documented examples for `unnecessary-subscript-reversal` ([#7774](https://github.com/astral-sh/ruff/pull/7774))
- Correct error in tuple example in ruff formatter docs ([#7822](https://github.com/astral-sh/ruff/pull/7822))
- Add versioning policy to documentation ([#7923](https://github.com/astral-sh/ruff/pull/7923))
- Fix invalid code in `FURB177` example ([#7832](https://github.com/astral-sh/ruff/pull/7832))
### Formatter
- Less scary `ruff format` message ([#7867](https://github.com/astral-sh/ruff/pull/7867))
- Remove spaces from import statements ([#7859](https://github.com/astral-sh/ruff/pull/7859))
- Formatter quoting for f-strings with triple quotes ([#7826](https://github.com/astral-sh/ruff/pull/7826))
- Update `ruff_python_formatter` generate.py comment ([#7850](https://github.com/astral-sh/ruff/pull/7850))
- Document one-call chaining deviation ([#7767](https://github.com/astral-sh/ruff/pull/7767))
- Allow f-string modifications in line-shrinking cases ([#7818](https://github.com/astral-sh/ruff/pull/7818))
- Add trailing comment deviation to README ([#7827](https://github.com/astral-sh/ruff/pull/7827))
- Add trailing zero between dot and exponential ([#7956](https://github.com/astral-sh/ruff/pull/7956))
- Force parentheses for power operations in unary expressions ([#7955](https://github.com/astral-sh/ruff/pull/7955))
### Playground
- Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824))

View File

@@ -170,8 +170,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
expressions, like function calls).
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
should be added in `RuleGroup::Preview`.
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`).
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.

85
Cargo.lock generated
View File

@@ -28,9 +28,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
dependencies = [
"memchr",
]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"anyhow",
"clap",
@@ -1084,9 +1084,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.34.0"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
dependencies = [
"console",
"globset",
@@ -1925,14 +1925,14 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
"regex-automata 0.3.9",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1949,16 +1949,10 @@ name = "regex-automata"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.2",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1973,12 +1967,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "result-like"
version = "0.4.6"
@@ -2051,7 +2039,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2188,7 +2176,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
@@ -2259,7 +2247,6 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"serde_derive_internals 0.29.0",
"syn 2.0.38",
]
@@ -2625,7 +2612,7 @@ checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals 0.26.0",
"serde_derive_internals",
"syn 1.0.109",
]
@@ -2659,15 +2646,15 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
version = "1.0.20"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
[[package]]
name = "serde"
version = "1.0.189"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
@@ -2685,9 +2672,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.189"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
@@ -2705,17 +2692,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "serde_derive_internals"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "serde_json"
version = "1.0.107"
@@ -3116,10 +3092,11 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.39"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
@@ -3128,9 +3105,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.27"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
@@ -3139,9 +3116,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.32"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
"valuable",
@@ -3271,9 +3248,8 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5506ae2c3c1ccbdf468e52fc5ef536c2ccd981f01273a4cb81aa61021f3a5f"
version = "1.1.0"
source = "git+https://github.com/konstin/unicode_names2?rev=e2ee8155795a13afbea5caa4dbce8d1f93bc26eb#e2ee8155795a13afbea5caa4dbce8d1f93bc26eb"
dependencies = [
"phf",
"unicode_names2_generator",
@@ -3281,9 +3257,8 @@ dependencies = [
[[package]]
name = "unicode_names2_generator"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6dfc680313e95bc6637fa278cd7a22390c3c2cd7b8b2bd28755bc6c0fc811e7"
version = "1.1.0"
source = "git+https://github.com/konstin/unicode_names2?rev=e2ee8155795a13afbea5caa4dbce8d1f93bc26eb#e2ee8155795a13afbea5caa4dbce8d1f93bc26eb"
dependencies = [
"getopts",
"log",

View File

@@ -21,7 +21,7 @@ filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.34.0", feature = ["filters", "glob"] }
insta = { version = "1.33.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
itertools = { version = "0.11.0" }
libcst = { version = "1.1.0", default-features = false }
@@ -31,10 +31,10 @@ once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.1.1" }
proc-macro2 = { version = "1.0.69" }
quote = { version = "1.0.23" }
regex = { version = "1.10.2" }
regex = { version = "1.9.6" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.189", features = ["derive"] }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
@@ -46,11 +46,11 @@ syn = { version = "2.0.38" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.49" }
toml = { version = "0.7.8" }
tracing = { version = "0.1.39" }
tracing = { version = "0.1.37" }
tracing-indicatif = { version = "0.3.4" }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-ident = { version = "1.0.12" }
unicode_names2 = { version = "1.2.0" }
unicode_names2 = { git = "https://github.com/konstin/unicode_names2", rev = "e2ee8155795a13afbea5caa4dbce8d1f93bc26eb" }
unicode-width = { version = "0.1.11" }
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }

View File

@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.0
rev: v0.0.292
hooks:
- id: ruff
```

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.0"
version = "0.0.292"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -17,8 +17,8 @@ use ruff_linter::settings::DEFAULT_SELECTORS;
use ruff_linter::warn_user;
use ruff_workspace::options::{
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintCommonOptions,
LintOptions, McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintOptions,
McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
};
use ruff_workspace::pyproject::Pyproject;
@@ -99,7 +99,7 @@ pub(crate) fn convert(
// Parse each supported option.
let mut options = Options::default();
let mut lint_options = LintCommonOptions::default();
let mut lint_options = LintOptions::default();
let mut flake8_annotations = Flake8AnnotationsOptions::default();
let mut flake8_bugbear = Flake8BugbearOptions::default();
let mut flake8_builtins = Flake8BuiltinsOptions::default();
@@ -433,11 +433,8 @@ pub(crate) fn convert(
}
}
if lint_options != LintCommonOptions::default() {
options.lint = Some(LintOptions {
common: lint_options,
..LintOptions::default()
});
if lint_options != LintOptions::default() {
options.lint = Some(lint_options);
}
// Create the pyproject.toml.
@@ -468,9 +465,7 @@ mod tests {
use ruff_linter::rules::flake8_quotes;
use ruff_linter::rules::pydocstyle::settings::Convention;
use ruff_linter::settings::types::PythonVersion;
use ruff_workspace::options::{
Flake8QuotesOptions, LintCommonOptions, LintOptions, Options, PydocstyleOptions,
};
use ruff_workspace::options::{Flake8QuotesOptions, LintOptions, Options, PydocstyleOptions};
use ruff_workspace::pyproject::Pyproject;
use crate::converter::DEFAULT_SELECTORS;
@@ -480,8 +475,8 @@ mod tests {
use super::super::plugin::Plugin;
use super::convert;
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintCommonOptions {
LintCommonOptions {
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintOptions {
LintOptions {
ignore: Some(vec![]),
select: Some(
DEFAULT_SELECTORS
@@ -491,7 +486,7 @@ mod tests {
.sorted_by_key(RuleSelector::prefix_and_code)
.collect(),
),
..LintCommonOptions::default()
..LintOptions::default()
}
}
@@ -503,10 +498,7 @@ mod tests {
None,
);
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
lint: Some(lint_default_options([])),
..Options::default()
});
assert_eq!(actual, expected);
@@ -524,10 +516,7 @@ mod tests {
);
let expected = Pyproject::new(Options {
line_length: Some(LineLength::try_from(100).unwrap()),
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
lint: Some(lint_default_options([])),
..Options::default()
});
assert_eq!(actual, expected);
@@ -545,10 +534,7 @@ mod tests {
);
let expected = Pyproject::new(Options {
line_length: Some(LineLength::try_from(100).unwrap()),
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
lint: Some(lint_default_options([])),
..Options::default()
});
assert_eq!(actual, expected);
@@ -565,10 +551,7 @@ mod tests {
Some(vec![]),
);
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
lint: Some(lint_default_options([])),
..Options::default()
});
assert_eq!(actual, expected);
@@ -586,16 +569,13 @@ mod tests {
);
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: LintCommonOptions {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([])
},
..LintOptions::default()
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([])
}),
..Options::default()
});
@@ -617,15 +597,12 @@ mod tests {
);
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: LintCommonOptions {
pydocstyle: Some(PydocstyleOptions {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
}),
..lint_default_options([Linter::Pydocstyle.into()])
},
..LintOptions::default()
pydocstyle: Some(PydocstyleOptions {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
}),
..lint_default_options([Linter::Pydocstyle.into()])
}),
..Options::default()
});
@@ -644,16 +621,13 @@ mod tests {
);
let expected = Pyproject::new(Options {
lint: Some(LintOptions {
common: LintCommonOptions {
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([Linter::Flake8Quotes.into()])
},
..LintOptions::default()
flake8_quotes: Some(Flake8QuotesOptions {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
..lint_default_options([Linter::Flake8Quotes.into()])
}),
..Options::default()
});
@@ -674,10 +648,7 @@ mod tests {
);
let expected = Pyproject::new(Options {
target_version: Some(PythonVersion::Py38),
lint: Some(LintOptions {
common: lint_default_options([]),
..LintOptions::default()
}),
lint: Some(lint_default_options([])),
..Options::default()
});
assert_eq!(actual, expected);

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.0"
version = "0.0.292"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -117,6 +117,16 @@ pub struct CheckCommand {
#[arg(long)]
ignore_noqa: bool,
/// Output serialization format for violations. (Deprecated: Use `--output-format` instead).
#[arg(
long,
value_enum,
env = "RUFF_FORMAT",
conflicts_with = "output_format",
hide = true
)]
pub format: Option<SerializationFormat>,
/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
pub output_format: Option<SerializationFormat>,
@@ -366,15 +376,6 @@ pub struct FormatCommand {
respect_gitignore: bool,
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
no_respect_gitignore: bool,
/// List of paths, used to omit files and/or directories from analysis.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN",
help_heading = "File selection"
)]
pub exclude: Option<Vec<FilePattern>>,
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
/// Use `--no-force-exclude` to disable.
#[arg(
@@ -395,9 +396,9 @@ pub struct FormatCommand {
#[arg(long, help_heading = "Miscellaneous")]
pub stdin_filename: Option<PathBuf>,
/// Enable preview mode; enables unstable formatting.
/// Enable preview mode; checks will include unstable rules and fixes.
/// Use `--no-preview` to disable.
#[arg(long, overrides_with("no_preview"))]
#[arg(long, overrides_with("no_preview"), hide = true)]
preview: bool,
#[clap(long, overrides_with("preview"), hide = true)]
no_preview: bool,
@@ -506,7 +507,7 @@ impl CheckCommand {
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
.map(UnsafeFixes::from),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
output_format: self.output_format,
output_format: self.output_format.or(self.format),
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
},
)
@@ -531,7 +532,6 @@ impl FormatCommand {
self.respect_gitignore,
self.no_respect_gitignore,
),
exclude: self.exclude,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
// Unsupported on the formatter CLI, but required on `Overrides`.
@@ -668,8 +668,6 @@ impl ConfigurationTransformer for CliOverrides {
}
if let Some(preview) = &self.preview {
config.preview = Some(*preview);
config.lint.preview = Some(*preview);
config.format.preview = Some(*preview);
}
if let Some(per_file_ignores) = &self.per_file_ignores {
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));

View File

@@ -10,7 +10,7 @@ use ruff_linter::linter::add_noqa_to_path;
use ruff_linter::source_kind::SourceKind;
use ruff_linter::warn_user_once;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
use crate::args::CliOverrides;
@@ -36,7 +36,7 @@ pub(crate) fn add_noqa(
&paths
.iter()
.flatten()
.map(ResolvedFile::path)
.map(ignore::DirEntry::path)
.collect::<Vec<_>>(),
pyproject_config,
);
@@ -45,15 +45,14 @@ pub(crate) fn add_noqa(
let modifications: usize = paths
.par_iter()
.flatten()
.filter_map(|resolved_file| {
.filter_map(|entry| {
let path = entry.path();
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
SourceType::from(resolved_file.path())
SourceType::from(path)
else {
return None;
};
let path = resolved_file.path();
let package = resolved_file
.path()
let package = path
.parent()
.and_then(|parent| package_roots.get(parent))
.and_then(|package| *package);

View File

@@ -22,10 +22,7 @@ use ruff_linter::{fs, warn_user_once, IOError};
use ruff_python_ast::imports::ImportMap;
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{TextRange, TextSize};
use ruff_workspace::resolver::{
match_exclusion, python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy,
ResolvedFile,
};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy};
use crate::args::CliOverrides;
use crate::cache::{self, Cache};
@@ -45,7 +42,8 @@ pub(crate) fn check(
// Collect all the Python files to check.
let start = Instant::now();
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
debug!("Identified files to lint in: {:?}", start.elapsed());
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
if paths.is_empty() {
warn_user_once!("No Python files found under the given path(s)");
@@ -79,7 +77,7 @@ pub(crate) fn check(
&paths
.iter()
.flatten()
.map(ResolvedFile::path)
.map(ignore::DirEntry::path)
.collect::<Vec<_>>(),
pyproject_config,
);
@@ -100,114 +98,95 @@ pub(crate) fn check(
});
let start = Instant::now();
let diagnostics_per_file = paths.par_iter().filter_map(|resolved_file| {
let result = match resolved_file {
Ok(resolved_file) => {
let path = resolved_file.path();
let package = path
.parent()
.and_then(|parent| package_roots.get(parent))
.and_then(|package| *package);
let mut diagnostics: Diagnostics = paths
.par_iter()
.map(|entry| {
match entry {
Ok(entry) => {
let path = entry.path();
let package = path
.parent()
.and_then(|parent| package_roots.get(parent))
.and_then(|package| *package);
let settings = resolver.resolve(path, pyproject_config);
let settings = resolver.resolve(path, pyproject_config);
if !resolved_file.is_root()
&& match_exclusion(
resolved_file.path(),
resolved_file.file_name(),
&settings.linter.exclude,
)
{
return None;
}
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
let cache = caches.as_ref().and_then(|caches| {
if let Some(cache) = caches.get(&cache_root) {
Some(cache)
} else {
debug!("No cache found for {}", cache_root.display());
None
}
});
lint_path(
path,
package,
&settings.linter,
cache,
noqa,
fix_mode,
unsafe_fixes,
)
.map_err(|e| {
(Some(path.to_path_buf()), {
let mut error = e.to_string();
for cause in e.chain() {
write!(&mut error, "\n Cause: {cause}").unwrap();
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
let cache = caches.as_ref().and_then(|caches| {
if let Some(cache) = caches.get(&cache_root) {
Some(cache)
} else {
debug!("No cache found for {}", cache_root.display());
None
}
error
})
})
}
Err(e) => Err((
if let Error::WithPath { path, .. } = e {
Some(path.clone())
} else {
None
},
e.io_error()
.map_or_else(|| e.to_string(), io::Error::to_string),
)),
};
});
Some(result.unwrap_or_else(|(path, message)| {
if let Some(path) = &path {
let settings = resolver.resolve(path, pyproject_config);
if settings.linter.rules.enabled(Rule::IOError) {
let dummy =
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
Diagnostics::new(
vec![Message::from_diagnostic(
Diagnostic::new(IOError { message }, TextRange::default()),
dummy,
TextSize::default(),
)],
ImportMap::default(),
FxHashMap::default(),
lint_path(
path,
package,
&settings.linter,
cache,
noqa,
fix_mode,
unsafe_fixes,
)
.map_err(|e| {
(Some(path.to_owned()), {
let mut error = e.to_string();
for cause in e.chain() {
write!(&mut error, "\n Cause: {cause}").unwrap();
}
error
})
})
}
Err(e) => Err((
if let Error::WithPath { path, .. } = e {
Some(path.clone())
} else {
None
},
e.io_error()
.map_or_else(|| e.to_string(), io::Error::to_string),
)),
}
.unwrap_or_else(|(path, message)| {
if let Some(path) = &path {
let settings = resolver.resolve(path, pyproject_config);
if settings.linter.rules.enabled(Rule::IOError) {
let dummy =
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
Diagnostics::new(
vec![Message::from_diagnostic(
Diagnostic::new(IOError { message }, TextRange::default()),
dummy,
TextSize::default(),
)],
ImportMap::default(),
FxHashMap::default(),
)
} else {
warn!(
"{}{}{} {message}",
"Failed to lint ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);
Diagnostics::default()
}
} else {
warn!(
"{}{}{} {message}",
"Failed to lint ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);
warn!("{} {message}", "Encountered error:".bold());
Diagnostics::default()
}
} else {
warn!("{} {message}", "Encountered error:".bold());
Diagnostics::default()
}
}))
});
})
})
.reduce(Diagnostics::default, |mut acc, item| {
acc += item;
acc
});
// Aggregate the diagnostics of all checked files and count the checked files.
// This can't be a regular for loop because we use `par_iter`.
let (mut all_diagnostics, checked_files) = diagnostics_per_file
.fold(
|| (Diagnostics::default(), 0u64),
|(all_diagnostics, checked_files), file_diagnostics| {
(all_diagnostics + file_diagnostics, checked_files + 1)
},
)
.reduce(
|| (Diagnostics::default(), 0u64),
|a, b| (a.0 + b.0, a.1 + b.1),
);
all_diagnostics.messages.sort();
diagnostics.messages.sort();
// Store the caches.
if let Some(caches) = caches {
@@ -217,9 +196,9 @@ pub(crate) fn check(
}
let duration = start.elapsed();
debug!("Checked {:?} files in: {:?}", checked_files, duration);
debug!("Checked {:?} files in: {:?}", paths.len(), duration);
Ok(all_diagnostics)
Ok(diagnostics)
}
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits

View File

@@ -4,7 +4,7 @@ use anyhow::Result;
use ruff_linter::packaging;
use ruff_linter::settings::flags;
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
use ruff_workspace::resolver::{python_file_at_path, PyprojectConfig};
use crate::args::CliOverrides;
use crate::diagnostics::{lint_stdin, Diagnostics};
@@ -22,14 +22,6 @@ pub(crate) fn check_stdin(
if !python_file_at_path(filename, pyproject_config, overrides)? {
return Ok(Diagnostics::default());
}
let lint_settings = &pyproject_config.settings.linter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
{
return Ok(Diagnostics::default());
}
}
let package_root = filename.and_then(Path::parent).and_then(|path| {
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)

View File

@@ -20,7 +20,7 @@ use ruff_linter::warn_user_once;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_python_formatter::{format_module_source, FormatModuleError};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_workspace::resolver::{match_exclusion, python_files_in_path};
use ruff_workspace::resolver::python_files_in_path;
use ruff_workspace::FormatterSettings;
use crate::args::{CliOverrides, FormatArguments};
@@ -61,42 +61,26 @@ pub(crate) fn format(
}
let start = Instant::now();
let (mut results, errors): (Vec<_>, Vec<_>) = paths
let (results, errors): (Vec<_>, Vec<_>) = paths
.into_par_iter()
.filter_map(|entry| {
match entry {
Ok(resolved_file) => {
let path = resolved_file.path();
Ok(entry) => {
let path = entry.into_path();
let SourceType::Python(source_type) = SourceType::from(&path) else {
// Ignore any non-Python files.
return None;
};
let resolved_settings = resolver.resolve(path, &pyproject_config);
// Ignore files that are excluded from formatting
if !resolved_file.is_root()
&& match_exclusion(
path,
resolved_file.file_name(),
&resolved_settings.formatter.exclude,
)
{
return None;
}
let resolved_settings = resolver.resolve(&path, &pyproject_config);
Some(
match catch_unwind(|| {
format_path(path, &resolved_settings.formatter, source_type, mode)
format_path(&path, &resolved_settings.formatter, source_type, mode)
}) {
Ok(inner) => inner.map(|result| FormatPathResult {
path: resolved_file.into_path(),
result,
}),
Err(error) => Err(FormatCommandError::Panic(
Some(resolved_file.into_path()),
error,
)),
Ok(inner) => inner.map(|result| FormatPathResult { path, result }),
Err(error) => Err(FormatCommandError::Panic(Some(path), error)),
},
)
}
@@ -120,8 +104,6 @@ pub(crate) fn format(
error!("{error}");
}
results.sort_unstable_by(|a, b| a.path.cmp(&b.path));
let summary = FormatSummary::new(results.as_slice(), mode);
// Report on the formatting changes.
@@ -155,7 +137,7 @@ pub(crate) fn format(
}
/// Format the file at the given [`Path`].
#[tracing::instrument(level="debug", skip_all, fields(path = %path.display()))]
#[tracing::instrument(skip_all, fields(path = %path.display()))]
fn format_path(
path: &Path,
settings: &FormatterSettings,

View File

@@ -6,7 +6,7 @@ use log::error;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
use ruff_workspace::resolver::python_file_at_path;
use ruff_workspace::FormatterSettings;
use crate::args::{CliOverrides, FormatArguments};
@@ -33,14 +33,6 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
if !python_file_at_path(filename, &pyproject_config, overrides)? {
return Ok(ExitStatus::Success);
}
let format_settings = &pyproject_config.settings.formatter;
if filename
.file_name()
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
{
return Ok(ExitStatus::Success);
}
}
let path = cli.stdin_filename.as_deref();

View File

@@ -5,7 +5,7 @@ use anyhow::Result;
use itertools::Itertools;
use ruff_linter::warn_user_once;
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
use crate::args::CliOverrides;
@@ -25,13 +25,12 @@ pub(crate) fn show_files(
}
// Print the list of files.
for path in paths
.into_iter()
for entry in paths
.iter()
.flatten()
.map(ResolvedFile::into_path)
.sorted_unstable()
.sorted_by(|a, b| a.path().cmp(b.path()))
{
writeln!(writer, "{}", path.to_string_lossy())?;
writeln!(writer, "{}", entry.path().to_string_lossy())?;
}
Ok(())

View File

@@ -4,7 +4,7 @@ use std::path::PathBuf;
use anyhow::{bail, Result};
use itertools::Itertools;
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
use crate::args::CliOverrides;
@@ -19,17 +19,16 @@ pub(crate) fn show_settings(
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
// Print the list of files.
let Some(path) = paths
.into_iter()
let Some(entry) = paths
.iter()
.flatten()
.map(ResolvedFile::into_path)
.sorted_unstable()
.sorted_by(|a, b| a.path().cmp(b.path()))
.next()
else {
bail!("No files found under the given path");
};
let settings = resolver.resolve(&path, pyproject_config);
let path = entry.path();
let settings = resolver.resolve(path, pyproject_config);
writeln!(writer, "Resolved settings for: {path:?}")?;
if let Some(settings_path) = pyproject_config.path.as_ref() {

View File

@@ -2,7 +2,7 @@
use std::fs::File;
use std::io;
use std::ops::{Add, AddAssign};
use std::ops::AddAssign;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
@@ -11,6 +11,7 @@ use anyhow::{Context, Result};
use colored::Colorize;
use filetime::FileTime;
use log::{debug, error, warn};
use ruff_linter::settings::types::UnsafeFixes;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
@@ -19,7 +20,6 @@ use ruff_linter::logging::DisplayParseError;
use ruff_linter::message::Message;
use ruff_linter::pyproject_toml::lint_pyproject_toml;
use ruff_linter::registry::AsRule;
use ruff_linter::settings::types::UnsafeFixes;
use ruff_linter::settings::{flags, LinterSettings};
use ruff_linter::source_kind::{SourceError, SourceKind};
use ruff_linter::{fs, IOError, SyntaxError};
@@ -61,7 +61,7 @@ impl FileCacheKey {
#[derive(Debug, Default, PartialEq)]
pub(crate) struct Diagnostics {
pub(crate) messages: Vec<Message>,
pub(crate) fixed: FixMap,
pub(crate) fixed: FxHashMap<String, FixTable>,
pub(crate) imports: ImportMap,
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
}
@@ -74,7 +74,7 @@ impl Diagnostics {
) -> Self {
Self {
messages,
fixed: FixMap::default(),
fixed: FxHashMap::default(),
imports,
notebook_indexes,
}
@@ -142,68 +142,22 @@ impl Diagnostics {
}
}
impl Add for Diagnostics {
type Output = Diagnostics;
fn add(mut self, other: Self) -> Self::Output {
self += other;
self
}
}
impl AddAssign for Diagnostics {
fn add_assign(&mut self, other: Self) {
self.messages.extend(other.messages);
self.imports.extend(other.imports);
self.fixed += other.fixed;
self.notebook_indexes.extend(other.notebook_indexes);
}
}
/// A collection of fixes indexed by file path.
#[derive(Debug, Default, PartialEq)]
pub(crate) struct FixMap(FxHashMap<String, FixTable>);
impl FixMap {
/// Returns `true` if there are no fixes in the map.
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Returns an iterator over the fixes in the map, along with the file path.
pub(crate) fn iter(&self) -> impl Iterator<Item = (&String, &FixTable)> {
self.0.iter()
}
/// Returns an iterator over the fixes in the map.
pub(crate) fn values(&self) -> impl Iterator<Item = &FixTable> {
self.0.values()
}
}
impl FromIterator<(String, FixTable)> for FixMap {
fn from_iter<T: IntoIterator<Item = (String, FixTable)>>(iter: T) -> Self {
Self(
iter.into_iter()
.filter(|(_, fixes)| !fixes.is_empty())
.collect(),
)
}
}
impl AddAssign for FixMap {
fn add_assign(&mut self, rhs: Self) {
for (filename, fixed) in rhs.0 {
for (filename, fixed) in other.fixed {
if fixed.is_empty() {
continue;
}
let fixed_in_file = self.0.entry(filename).or_default();
let fixed_in_file = self.fixed.entry(filename).or_default();
for (rule, count) in fixed {
if count > 0 {
*fixed_in_file.entry(rule).or_default() += count;
}
}
}
self.notebook_indexes.extend(other.notebook_indexes);
}
}
@@ -364,7 +318,7 @@ pub(crate) fn lint_path(
Ok(Diagnostics {
messages,
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
fixed: FxHashMap::from_iter([(fs::relativize_path(path), fixed)]),
imports,
notebook_indexes,
})
@@ -482,7 +436,7 @@ pub(crate) fn lint_stdin(
Ok(Diagnostics {
messages,
fixed: FixMap::from_iter([(
fixed: FxHashMap::from_iter([(
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
fixed,
)]),

View File

@@ -177,6 +177,14 @@ fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
}
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
if args.format.is_some() {
if std::env::var("RUFF_FORMAT").is_ok() {
warn_user!("The environment variable `RUFF_FORMAT` is deprecated. Use `RUFF_OUTPUT_FORMAT` instead.");
} else {
warn_user!("The argument `--format=<FORMAT>` is deprecated. Use `--output-format=<FORMAT>` instead.");
}
}
let (cli, overrides) = args.partition();
// Construct the "default" settings. These are used when no `pyproject.toml`

View File

@@ -7,9 +7,11 @@ use anyhow::Result;
use bitflags::bitflags;
use colored::Colorize;
use itertools::{iterate, Itertools};
use rustc_hash::FxHashMap;
use serde::Serialize;
use ruff_linter::fs::relativize_path;
use ruff_linter::linter::FixTable;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
@@ -20,7 +22,7 @@ use ruff_linter::registry::{AsRule, Rule};
use ruff_linter::settings::flags::{self};
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
use crate::diagnostics::{Diagnostics, FixMap};
use crate::diagnostics::Diagnostics;
bitflags! {
#[derive(Default, Debug, Copy, Clone)]
@@ -159,7 +161,7 @@ impl Printer {
"es"
};
writeln!(writer,
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
"{} hidden fix{es} can be enabled with the `--unsafe-fixes` option.",
fixables.unapplicable_unsafe
)?;
}
@@ -460,7 +462,7 @@ fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics
(!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes)
}
fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
let total = fixed
.values()
.map(|table| table.values().sum::<usize>())

View File

@@ -13,7 +13,7 @@ const BIN_NAME: &str = "ruff";
#[test]
fn default_options() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["format", "--isolated", "--stdin-filename", "test.py"])
.args(["format", "--isolated"])
.arg("-")
.pass_stdin(r#"
def foo(arg1, arg2,):
@@ -50,9 +50,6 @@ fn format_options() -> Result<()> {
fs::write(
&ruff_toml,
r#"
tab-size = 8
line-length = 84
[format]
indent-style = "tab"
quote-style = "single"
@@ -67,7 +64,7 @@ line-ending = "cr-lf"
.arg("-")
.pass_stdin(r#"
def foo(arg1, arg2,):
print("Shouldn't change quotes. It exceeds the line width with the tab size 8")
print("Shouldn't change quotes")
if condition:
@@ -79,9 +76,7 @@ if condition:
exit_code: 0
----- stdout -----
def foo(arg1, arg2):
print(
"Shouldn't change quotes. It exceeds the line width with the tab size 8"
)
print("Shouldn't change quotes")
if condition:
@@ -93,108 +88,6 @@ if condition:
Ok(())
}
#[test]
fn exclude() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-exclude = ["out"]
[format]
exclude = ["test.py", "generated.py"]
"#,
)?;
fs::write(
tempdir.path().join("main.py"),
r#"
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#,
)?;
// Excluded file but passed to the CLI directly, should be formatted
let test_path = tempdir.path().join("test.py");
fs::write(
&test_path,
r#"
def say_hy(name: str):
print(f"Hy {name}")"#,
)?;
fs::write(
tempdir.path().join("generated.py"),
r#"NUMBERS = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
]
OTHER = "OTHER"
"#,
)?;
let out_dir = tempdir.path().join("out");
fs::create_dir(&out_dir)?;
fs::write(out_dir.join("a.py"), "a = a")?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.args(["format", "--check", "--config"])
.arg(ruff_toml.file_name().unwrap())
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
.arg(test_path.file_name().unwrap())
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @r###"
success: false
exit_code: 1
----- stdout -----
Would reformat: main.py
Would reformat: test.py
2 files would be reformatted
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
"###);
Ok(())
}
#[test]
fn exclude_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
[format]
exclude = ["generated.py"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "-"])
.pass_stdin(r#"
from test import say_hy
if __name__ == '__main__':
say_hy("dear Ruff contributor")
"#), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
"###);
Ok(())
}
#[test]
fn format_option_inheritance() -> Result<()> {
let tempdir = TempDir::new()?;
@@ -251,7 +144,7 @@ if condition:
Ok(())
}
/// Since 0.1.0 the legacy format option is no longer supported
/// Tests that the legacy `format` option continues to work but emits a warning.
#[test]
fn legacy_format_option() -> Result<()> {
let tempdir = TempDir::new()?;
@@ -263,29 +156,53 @@ format = "json"
"#,
)?;
insta::with_settings!({filters => vec![
(&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"),
]}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--select", "F401", "--no-cache", "--config"])
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to parse `[RUFF-TOML-PATH]`: TOML parse error at line 2, column 10
|
2 | format = "json"
| ^^^^^^
invalid type: string "json", expected struct FormatOptions
"###);
});
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--select", "F401", "--no-cache", "--config"])
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 1
----- stdout -----
[
{
"cell": null,
"code": "F401",
"end_location": {
"column": 10,
"row": 2
},
"filename": "-",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 3
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `os`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`os` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
}
]
----- stderr -----
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
"###);
Ok(())
}

View File

@@ -945,7 +945,7 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
----- stderr -----
"###);
@@ -1002,7 +1002,7 @@ fn fix_applies_safe_fixes_by_default() {
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
"###);
}
@@ -1112,7 +1112,7 @@ fn fix_only_unsafe_fixes_available() {
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
"###);
}
@@ -1252,8 +1252,8 @@ fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
])
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r###"
success: true
exit_code: 0
success: false
exit_code: 1
----- stdout -----
----- stderr -----
@@ -1276,8 +1276,8 @@ fn diff_only_unsafe_fixes_available() {
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: true
exit_code: 0
success: false
exit_code: 1
----- stdout -----
----- stderr -----
@@ -1317,7 +1317,7 @@ extend-unsafe-fixes = ["UP034"]
-:1:14: F601 Dictionary key literal `'a'` repeated
-:2:7: UP034 Avoid extraneous parentheses
Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
2 hidden fixes can be enabled with the `--unsafe-fixes` option.
----- stderr -----
"###);
@@ -1397,7 +1397,7 @@ extend-safe-fixes = ["UP034"]
-:1:14: F601 Dictionary key literal `'a'` repeated
-:2:7: UP034 Avoid extraneous parentheses
Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
2 hidden fixes can be enabled with the `--unsafe-fixes` option.
----- stderr -----
"###);

View File

@@ -31,15 +31,14 @@ inline-quotes = "single"
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
.args(["--stdin-filename", "test.py"])
.arg("-")
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
success: false
exit_code: 1
----- stdout -----
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
-:1:5: Q000 [*] Double quotes found but single quotes preferred
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
@@ -156,117 +155,3 @@ inline-quotes = "single"
"###);
Ok(())
}
#[test]
fn exclude() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
extend-exclude = ["out"]
[lint]
exclude = ["test.py", "generated.py"]
[lint.flake8-quotes]
inline-quotes = "single"
"#,
)?;
fs::write(
tempdir.path().join("main.py"),
r#"
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#,
)?;
// Excluded file but passed to the CLI directly, should be linted
let test_path = tempdir.path().join("test.py");
fs::write(
&test_path,
r#"
def say_hy(name: str):
print(f"Hy {name}")"#,
)?;
fs::write(
tempdir.path().join("generated.py"),
r#"NUMBERS = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
]
OTHER = "OTHER"
"#,
)?;
let out_dir = tempdir.path().join("out");
fs::create_dir(&out_dir)?;
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
.arg(test_path.file_name().unwrap())
// Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options
.arg("."), @r###"
success: false
exit_code: 1
----- stdout -----
main.py:4:16: Q000 [*] Double quotes found but single quotes preferred
main.py:5:12: Q000 [*] Double quotes found but single quotes preferred
test.py:3:15: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 3 fixable with the `--fix` option.
----- stderr -----
"###);
Ok(())
}
#[test]
fn exclude_stdin() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
extend-select = ["B", "Q"]
[lint]
exclude = ["generated.py"]
[lint.flake8-quotes]
inline-quotes = "single"
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(tempdir.path())
.arg("check")
.args(STDIN_BASE_OPTIONS)
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
.args(["--stdin-filename", "generated.py"])
.arg("-")
.pass_stdin(r#"
from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
"###);
Ok(())
}

View File

@@ -11,6 +11,7 @@ use std::{fmt, fs, io, iter};
use anyhow::{bail, format_err, Context, Error};
use clap::{CommandFactory, FromArgMatches};
use ignore::DirEntry;
use imara_diff::intern::InternedInput;
use imara_diff::sink::Counter;
use imara_diff::{diff, Algorithm};
@@ -35,14 +36,14 @@ use ruff_linter::settings::types::{FilePattern, FilePatternSet};
use ruff_python_formatter::{
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, Resolver};
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
#[allow(clippy::type_complexity)]
fn ruff_check_paths(
dirs: &[PathBuf],
) -> anyhow::Result<(
Vec<Result<ResolvedFile, ignore::Error>>,
Vec<Result<DirEntry, ignore::Error>>,
Resolver,
PyprojectConfig,
)> {
@@ -466,9 +467,9 @@ fn format_dev_project(
let iter = { paths.into_par_iter() };
#[cfg(feature = "singlethreaded")]
let iter = { paths.into_iter() };
iter.map(|path| {
iter.map(|dir_entry| {
let result = format_dir_entry(
path,
dir_entry,
stability_check,
write,
&black_options,
@@ -526,20 +527,24 @@ fn format_dev_project(
/// Error handling in between walkdir and `format_dev_file`
fn format_dir_entry(
resolved_file: Result<ResolvedFile, ignore::Error>,
dir_entry: Result<DirEntry, ignore::Error>,
stability_check: bool,
write: bool,
options: &BlackOptions,
resolver: &Resolver,
pyproject_config: &PyprojectConfig,
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
let dir_entry = match dir_entry.context("Iterating the files in the repository failed") {
Ok(dir_entry) => dir_entry,
Err(err) => return Err(err),
};
let file = dir_entry.path().to_path_buf();
// For some reason it does not filter in the beginning
if resolved_file.file_name() == "pyproject.toml" {
return Ok((Ok(Statistics::default()), resolved_file.into_path()));
if dir_entry.file_name() == "pyproject.toml" {
return Ok((Ok(Statistics::default()), file));
}
let path = resolved_file.into_path();
let path = dir_entry.path().to_path_buf();
let mut options = options.to_py_format_options(&path);
let settings = resolver.resolve(&path, pyproject_config);

View File

@@ -1,7 +1,6 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
//!
//! Used for <https://docs.astral.sh/ruff/settings/>.
use itertools::Itertools;
use std::fmt::Write;
use ruff_workspace::options::Options;
@@ -108,24 +107,6 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
if !field.aliases.is_empty() {
let title = if field.aliases.len() == 1 {
"Alias"
} else {
"Aliases"
};
output.push_str(&format!(
"**{title}**: {}\n",
field
.aliases
.iter()
.map(|alias| format!("`{alias}`"))
.join(", ")
));
output.push('\n');
}
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if let Some(set_name) = parent_set.name() {

View File

@@ -95,7 +95,7 @@ impl std::fmt::Display for IndentStyle {
///
/// Determines the visual width of a tab character (`\t`) and the number of
/// spaces per indent when using [`IndentStyle::Space`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndentWidth(NonZeroU8);

View File

@@ -54,7 +54,7 @@ impl<'a> Printer<'a> {
/// Prints the passed in element as well as all its content,
/// starting at the specified indentation level
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
#[tracing::instrument(name = "Printer::print", skip_all)]
pub fn print_with_indent(
mut self,
document: &'a Document,

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.0"
version = "0.0.292"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -29,7 +29,7 @@ ruff_python_parser = { path = "../ruff_python_parser" }
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
ruff_text_size = { path = "../ruff_text_size" }
aho-corasick = { version = "1.1.2" }
aho-corasick = { version = "1.1.1" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
bitflags = { workspace = true }
@@ -59,7 +59,7 @@ regex = { workspace = true }
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
semver = { version = "1.0.20" }
semver = { version = "1.0.19" }
serde = { workspace = true }
serde_json = { workspace = true }
similar = { workspace = true }

View File

@@ -1,19 +0,0 @@
from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .foo import Record
type RecordOrThings = Record | int | str
type RecordCallback[R: Record] = Callable[[R], None]
def process_record[R: Record](record: R) -> None:
...
class RecordContainer[R: Record]:
def add_record(self, record: R) -> None:
...

View File

@@ -22,10 +22,3 @@ class C:
class D(C):
x: UUID
import collections
class E(BaseModel[int]):
x: collections.Awaitable

View File

@@ -1,10 +0,0 @@
def with_backslash():
"""Sum\\mary."""
def ends_in_quote():
'Sum\\mary."'
def contains_quote():
'Sum"\\mary.'

View File

@@ -10,10 +10,6 @@ def double_quotes_backslash_uppercase():
R"""Sum\\mary."""
def shouldnt_add_raw_here():
"Ruff \U000026a1"
def make_unique_pod_id(pod_id: str) -> str | None:
r"""
Generate a unique Pod name.

View File

@@ -1,17 +0,0 @@
"""Test that type parameters are considered used."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable
from .foo import Record as Record1
from .bar import Record as Record2
type RecordCallback[R: Record1] = Callable[[R], None]
def process_record[R: Record2](record: R) -> None:
...

View File

@@ -1,5 +0,0 @@
"""Test lazy evaluation of type alias values."""
type RecordCallback[R: Record] = Callable[[R], None]
from collections.abc import Callable

View File

@@ -63,15 +63,6 @@ class Apples:
def __html__(self):
pass
# Allow _missing_, used by enum.Enum.
@classmethod
def _missing_(cls, value):
pass
# Allow anonymous functions.
def _(self):
pass
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
...

View File

@@ -6,14 +6,6 @@ for item in {1}:
for item in {"apples", "lemons", "water"}: # flags in-line set literals
print(f"I like {item}.")
for item in {1,}:
print(f"I can count to {item}!")
for item in {
"apples", "lemons", "water"
}: # flags in-line set literals
print(f"I like {item}.")
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions

View File

@@ -1,10 +0,0 @@
# Errors
1 in [1, 2, 3]
1 in (1, 2, 3)
1 in (
1, 2, 3
)
# OK
fruits = ["cherry", "grapes"]
"cherry" in fruits

View File

@@ -1,71 +0,0 @@
# bare raise is an except block
try:
pass
except Exception:
raise
try:
pass
except Exception:
if True:
raise
# bare raise is in a finally block which is in an except block
try:
raise Exception
except Exception:
try:
raise Exception
finally:
raise
# bare raise is in an __exit__ method
class ContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
raise
try:
raise # [misplaced-bare-raise]
except Exception:
pass
def f():
try:
raise # [misplaced-bare-raise]
except Exception:
pass
def g():
raise # [misplaced-bare-raise]
def h():
try:
if True:
def i():
raise # [misplaced-bare-raise]
except Exception:
pass
raise # [misplaced-bare-raise]
raise # [misplaced-bare-raise]
try:
pass
except:
def i():
raise # [misplaced-bare-raise]
try:
pass
except:
class C:
raise # [misplaced-bare-raise]
try:
pass
except:
pass
finally:
raise # [misplaced-bare-raise]

View File

@@ -1,7 +1,5 @@
import abc
from typing_extensions import override
class Person:
def developer_greeting(self, name): # [no-self-use]
@@ -62,24 +60,3 @@ class Prop:
@property
def count(self):
return 24
class A:
def foo(self):
...
class B(A):
@override
def foo(self):
...
def foobar(self):
super()
def bar(self):
super().foo()
def baz(self):
if super().foo():
...

View File

@@ -1,54 +0,0 @@
if a:
...
elif (a and b):
...
elif (a and b) and c:
...
elif (a and b) and c and d:
...
elif (a and b) and c and d and e:
...
elif (a and b) and c and d and e and f:
...
elif (a and b) and c and d and e and f and g:
...
elif (a and b) and c and d and e and f and g and h:
...
elif (a and b) and c and d and e and f and g and h and i:
...
elif (a and b) and c and d and e and f and g and h and i and j:
...
elif (a and b) and c and d and e and f and g and h and i and j and k:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
...
else:
...

View File

@@ -1,44 +0,0 @@
import io
import sys
import tempfile
import io as hugo
import codecs
# Errors.
open("test.txt")
io.TextIOWrapper(io.FileIO("test.txt"))
hugo.TextIOWrapper(hugo.FileIO("test.txt"))
tempfile.NamedTemporaryFile("w")
tempfile.TemporaryFile("w")
codecs.open("test.txt")
tempfile.SpooledTemporaryFile(0, "w")
# Non-errors.
open("test.txt", encoding="utf-8")
open("test.bin", "wb")
open("test.bin", mode="wb")
open("test.txt", "r", -1, "utf-8")
open("test.txt", mode=sys.argv[1])
def func(*args, **kwargs):
open(*args)
open("text.txt", **kwargs)
io.TextIOWrapper(io.FileIO("test.txt"), encoding="utf-8")
io.TextIOWrapper(io.FileIO("test.txt"), "utf-8")
tempfile.TemporaryFile("w", encoding="utf-8")
tempfile.TemporaryFile("w", -1, "utf-8")
tempfile.TemporaryFile("wb")
tempfile.TemporaryFile()
tempfile.NamedTemporaryFile("w", encoding="utf-8")
tempfile.NamedTemporaryFile("w", -1, "utf-8")
tempfile.NamedTemporaryFile("wb")
tempfile.NamedTemporaryFile()
codecs.open("test.txt", encoding="utf-8")
codecs.open("test.bin", "wb")
codecs.open("test.bin", mode="wb")
codecs.open("test.txt", "r", -1, "utf-8")
tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
tempfile.SpooledTemporaryFile(0, "wb")
tempfile.SpooledTemporaryFile(0, )

View File

@@ -306,7 +306,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if scope.kind.is_function() {
if checker.enabled(Rule::NoSelfUse) {
pylint::rules::no_self_use(checker, scope_id, scope, &mut diagnostics);
pylint::rules::no_self_use(checker, scope, &mut diagnostics);
}
}
}

View File

@@ -786,9 +786,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
pylint::rules::subprocess_run_without_check(checker, call);
}
if checker.enabled(Rule::UnspecifiedEncoding) {
pylint::rules::unspecified_encoding(checker, call);
}
if checker.any_enabled(&[
Rule::PytestRaisesWithoutException,
Rule::PytestRaisesTooBroad,
@@ -1197,9 +1194,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ComparisonWithItself) {
pylint::rules::comparison_with_itself(checker, left, ops, comparators);
}
if checker.enabled(Rule::LiteralMembership) {
pylint::rules::literal_membership(checker, compare);
}
if checker.enabled(Rule::ComparisonOfConstant) {
pylint::rules::comparison_of_constant(checker, left, ops, comparators);
}

View File

@@ -964,7 +964,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
}
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
Stmt::Raise(ast::StmtRaise { exc, .. }) => {
if checker.enabled(Rule::RaiseNotImplemented) {
if let Some(expr) = exc {
pyflakes::rules::raise_not_implemented(checker, expr);
@@ -1004,9 +1004,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_raise::rules::unnecessary_paren_on_raise_exception(checker, expr);
}
}
if checker.enabled(Rule::MisplacedBareRaise) {
pylint::rules::misplaced_bare_raise(checker, raise);
}
}
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
if checker.enabled(Rule::GlobalStatement) {
@@ -1070,9 +1067,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::CheckAndRemoveFromSet) {
refurb::rules::check_and_remove_from_set(checker, if_);
}
if checker.enabled(Rule::TooManyBooleanExpressions) {
pylint::rules::too_many_boolean_expressions(checker, if_);
}
if checker.source_type.is_stub() {
if checker.any_enabled(&[
Rule::UnrecognizedVersionInfoCheck,

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::Expr;
use ruff_python_ast::{Expr, TypeParam};
use ruff_python_semantic::{ScopeId, Snapshot};
use ruff_text_size::TextRange;
@@ -10,7 +10,7 @@ pub(crate) struct Deferred<'a> {
pub(crate) scopes: Vec<ScopeId>,
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
pub(crate) functions: Vec<Snapshot>,
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
pub(crate) for_loops: Vec<Snapshot>,

View File

@@ -524,7 +524,6 @@ where
);
self.semantic.push_definition(definition);
self.semantic.push_scope(ScopeKind::Function(function_def));
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
self.deferred.functions.push(self.semantic.snapshot());
@@ -563,7 +562,6 @@ where
);
self.semantic.push_definition(definition);
self.semantic.push_scope(ScopeKind::Class(class_def));
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
// Extract any global bindings from the class body.
if let Some(globals) = Globals::from_body(body) {
@@ -582,9 +580,7 @@ where
if let Some(type_params) = type_params {
self.visit_type_params(type_params);
}
self.deferred
.type_param_definitions
.push((value, self.semantic.snapshot()));
self.visit_expr(value);
self.semantic.pop_scope();
self.visit_expr(name);
}
@@ -1389,14 +1385,9 @@ where
}
}
// Step 2: Traversal
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
bound: Some(bound), ..
}) = type_param
{
self.deferred
.type_param_definitions
.push((bound, self.semantic.snapshot()));
}
self.deferred
.type_param_definitions
.push((type_param, self.semantic.snapshot()));
}
}
@@ -1771,9 +1762,12 @@ impl<'a> Checker<'a> {
for (type_param, snapshot) in type_params {
self.semantic.restore(snapshot);
self.semantic.flags |=
SemanticModelFlags::TYPE_PARAM_DEFINITION | SemanticModelFlags::TYPE_DEFINITION;
self.visit_expr(type_param);
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
bound: Some(bound), ..
}) = type_param
{
self.visit_expr(bound);
}
}
}
self.semantic.restore(snapshot);

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
use anyhow::{Context, Result};
use ruff_diagnostics::Edit;
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;

View File

@@ -253,9 +253,3 @@ impl From<NonZeroU8> for TabSize {
Self(tab_size)
}
}
impl From<TabSize> for NonZeroU8 {
fn from(value: TabSize) -> Self {
value.0
}
}

View File

@@ -34,7 +34,7 @@ use crate::checkers::ast::Checker;
///
/// ## Example
/// ```python
/// "text.txt".strip(".txt") # "e"
/// "text.txt".strip(".txt") # "ex"
/// ```
///
/// Use instead:

View File

@@ -1,9 +1,10 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_pyi::helpers::traverse_union;
/// ## What it does
@@ -31,7 +32,7 @@ pub struct UnnecessaryLiteralUnion {
members: Vec<String>,
}
impl Violation for UnnecessaryLiteralUnion {
impl AlwaysFixableViolation for UnnecessaryLiteralUnion {
#[derive_message_formats]
fn message(&self) -> String {
format!(
@@ -39,13 +40,17 @@ impl Violation for UnnecessaryLiteralUnion {
self.members.join(", ")
)
}
fn fix_title(&self) -> String {
format!("Replace with a single `Literal`")
}
}
/// PYI030
pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Expr) {
let mut literal_exprs = Vec::new();
// Adds a member to `literal_exprs` if it is a `Literal` annotation
// Adds a member to `literal_exprs` if it is a `Literal` annotation.
let mut collect_literal_expr = |expr: &'a Expr, _| {
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if checker.semantic().match_typing_expr(value, "Literal") {
@@ -54,21 +59,28 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
}
};
// Traverse the union, collect all literal members
// Traverse the union, collect all literal members.
traverse_union(&mut collect_literal_expr, checker.semantic(), expr, None);
// Raise a violation if more than one
// Raise a violation if more than one.
if literal_exprs.len() > 1 {
let diagnostic = Diagnostic::new(
let literal_members: Vec<String> = literal_exprs
.into_iter()
.map(|expr| checker.locator().slice(expr.as_ref()).to_string())
.collect();
let mut diagnostic = Diagnostic::new(
UnnecessaryLiteralUnion {
members: literal_exprs
.into_iter()
.map(|expr| checker.locator().slice(expr.as_ref()).to_string())
.collect(),
members: literal_members.clone(),
},
expr.range(),
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("Literal[{}]", literal_members.join(", ")),
expr.range(),
)));
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI030.py:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
PYI030.py:9:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
8 | # Should emit for duplicate field types.
9 | field2: Literal[1] | Literal[2] # Error
@@ -9,24 +9,57 @@ PYI030.py:9:9: PYI030 Multiple literal members in a union. Use a single literal,
10 |
11 | # Should emit for union types in arguments.
|
= help: Replace with a single `Literal`
PYI030.py:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
6 6 | field1: Literal[1] # OK
7 7 |
8 8 | # Should emit for duplicate field types.
9 |-field2: Literal[1] | Literal[2] # Error
9 |+field2: Literal[1, 2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 12 | def func1(arg1: Literal[1] | Literal[2]): # Error
PYI030.py:12:17: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
11 | # Should emit for union types in arguments.
12 | def func1(arg1: Literal[1] | Literal[2]): # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
13 | print(arg1)
|
= help: Replace with a single `Literal`
PYI030.py:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
9 9 | field2: Literal[1] | Literal[2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 |-def func1(arg1: Literal[1] | Literal[2]): # Error
12 |+def func1(arg1: Literal[1, 2]): # Error
13 13 | print(arg1)
14 14 |
15 15 |
PYI030.py:17:16: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
16 | # Should emit for unions in return types.
17 | def func2() -> Literal[1] | Literal[2]: # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
18 | return "my Literal[1]ing"
|
= help: Replace with a single `Literal`
PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
14 14 |
15 15 |
16 16 | # Should emit for unions in return types.
17 |-def func2() -> Literal[1] | Literal[2]: # Error
17 |+def func2() -> Literal[1, 2]: # Error
18 18 | return "my Literal[1]ing"
19 19 |
20 20 |
PYI030.py:22:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -34,8 +67,19 @@ PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
19 19 |
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 |-field3: Literal[1] | Literal[2] | str # Error
22 |+field3: Literal[1, 2] # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
PYI030.py:23:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -44,8 +88,19 @@ PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal
24 | field5: Literal[1] | str | Literal[2] # Error
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 |-field4: str | Literal[1] | Literal[2] # Error
23 |+field4: Literal[1, 2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
PYI030.py:24:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
22 | field3: Literal[1] | Literal[2] | str # Error
23 | field4: str | Literal[1] | Literal[2] # Error
@@ -53,8 +108,19 @@ PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 |-field5: Literal[1] | str | Literal[2] # Error
24 |+field5: Literal[1, 2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
PYI030.py:25:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
@@ -63,8 +129,19 @@ PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal
26 |
27 | # Should emit for non-type unions.
|
= help: Replace with a single `Literal`
PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 |-field6: Literal[1] | bool | Literal[2] | str # Error
25 |+field6: Literal[1, 2] # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 28 | field7 = Literal[1] | Literal[2] # Error
PYI030.py:28:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
27 | # Should emit for non-type unions.
28 | field7 = Literal[1] | Literal[2] # Error
@@ -72,8 +149,19 @@ PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single litera
29 |
30 | # Should emit for parenthesized unions.
|
= help: Replace with a single `Literal`
PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 |-field7 = Literal[1] | Literal[2] # Error
28 |+field7 = Literal[1, 2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
PYI030.py:31:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
30 | # Should emit for parenthesized unions.
31 | field8: Literal[1] | (Literal[2] | str) # Error
@@ -81,16 +169,38 @@ PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal
32 |
33 | # Should handle user parentheses when fixing.
|
= help: Replace with a single `Literal`
PYI030.py:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
28 28 | field7 = Literal[1] | Literal[2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 |-field8: Literal[1] | (Literal[2] | str) # Error
31 |+field8: Literal[1, 2] # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
PYI030.py:34:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 |-field9: Literal[1] | (Literal[2] | str) # Error
34 |+field9: Literal[1, 2] # Error
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
PYI030.py:35:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -99,12 +209,31 @@ PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single litera
36 |
37 | # Should emit for union in generic parent type.
|
= help: Replace with a single `Literal`
PYI030.py:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
35 |-field10: (Literal[1] | str) | Literal[2] # Error
35 |+field10: Literal[1, 2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
PYI030.py:38:15: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
37 | # Should emit for union in generic parent type.
38 | field11: dict[Literal[1] | Literal[2], str] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
= help: Replace with a single `Literal`
Fix
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 |-field11: dict[Literal[1] | Literal[2], str] # Error
38 |+field11: dict[Literal[1, 2], str] # Error

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
PYI030.pyi:9:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
8 | # Should emit for duplicate field types.
9 | field2: Literal[1] | Literal[2] # Error
@@ -9,24 +9,57 @@ PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal
10 |
11 | # Should emit for union types in arguments.
|
= help: Replace with a single `Literal`
PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
6 6 | field1: Literal[1] # OK
7 7 |
8 8 | # Should emit for duplicate field types.
9 |-field2: Literal[1] | Literal[2] # Error
9 |+field2: Literal[1, 2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 12 | def func1(arg1: Literal[1] | Literal[2]): # Error
PYI030.pyi:12:17: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
11 | # Should emit for union types in arguments.
12 | def func1(arg1: Literal[1] | Literal[2]): # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
13 | print(arg1)
|
= help: Replace with a single `Literal`
PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
9 9 | field2: Literal[1] | Literal[2] # Error
10 10 |
11 11 | # Should emit for union types in arguments.
12 |-def func1(arg1: Literal[1] | Literal[2]): # Error
12 |+def func1(arg1: Literal[1, 2]): # Error
13 13 | print(arg1)
14 14 |
15 15 |
PYI030.pyi:17:16: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
16 | # Should emit for unions in return types.
17 | def func2() -> Literal[1] | Literal[2]: # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
18 | return "my Literal[1]ing"
|
= help: Replace with a single `Literal`
PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
14 14 |
15 15 |
16 16 | # Should emit for unions in return types.
17 |-def func2() -> Literal[1] | Literal[2]: # Error
17 |+def func2() -> Literal[1, 2]: # Error
18 18 | return "my Literal[1]ing"
19 19 |
20 20 |
PYI030.pyi:22:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -34,8 +67,19 @@ PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single litera
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
19 19 |
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 |-field3: Literal[1] | Literal[2] | str # Error
22 |+field3: Literal[1, 2] # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
PYI030.pyi:23:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
21 | # Should emit in longer unions, even if not directly adjacent.
22 | field3: Literal[1] | Literal[2] | str # Error
@@ -44,8 +88,19 @@ PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single litera
24 | field5: Literal[1] | str | Literal[2] # Error
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
20 20 |
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 |-field4: str | Literal[1] | Literal[2] # Error
23 |+field4: Literal[1, 2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
PYI030.pyi:24:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
22 | field3: Literal[1] | Literal[2] | str # Error
23 | field4: str | Literal[1] | Literal[2] # Error
@@ -53,8 +108,19 @@ PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single litera
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
21 21 | # Should emit in longer unions, even if not directly adjacent.
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 |-field5: Literal[1] | str | Literal[2] # Error
24 |+field5: Literal[1, 2] # Error
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
PYI030.pyi:25:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
@@ -63,8 +129,19 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera
26 |
27 | # Should emit for non-type unions.
|
= help: Replace with a single `Literal`
PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
22 22 | field3: Literal[1] | Literal[2] | str # Error
23 23 | field4: str | Literal[1] | Literal[2] # Error
24 24 | field5: Literal[1] | str | Literal[2] # Error
25 |-field6: Literal[1] | bool | Literal[2] | str # Error
25 |+field6: Literal[1, 2] # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 28 | field7 = Literal[1] | Literal[2] # Error
PYI030.pyi:28:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
27 | # Should emit for non-type unions.
28 | field7 = Literal[1] | Literal[2] # Error
@@ -72,8 +149,19 @@ PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single liter
29 |
30 | # Should emit for parenthesized unions.
|
= help: Replace with a single `Literal`
PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
25 25 | field6: Literal[1] | bool | Literal[2] | str # Error
26 26 |
27 27 | # Should emit for non-type unions.
28 |-field7 = Literal[1] | Literal[2] # Error
28 |+field7 = Literal[1, 2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
PYI030.pyi:31:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
30 | # Should emit for parenthesized unions.
31 | field8: Literal[1] | (Literal[2] | str) # Error
@@ -81,16 +169,38 @@ PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single litera
32 |
33 | # Should handle user parentheses when fixing.
|
= help: Replace with a single `Literal`
PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
28 28 | field7 = Literal[1] | Literal[2] # Error
29 29 |
30 30 | # Should emit for parenthesized unions.
31 |-field8: Literal[1] | (Literal[2] | str) # Error
31 |+field8: Literal[1, 2] # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
PYI030.pyi:34:9: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
31 31 | field8: Literal[1] | (Literal[2] | str) # Error
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 |-field9: Literal[1] | (Literal[2] | str) # Error
34 |+field9: Literal[1, 2] # Error
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
PYI030.pyi:35:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
33 | # Should handle user parentheses when fixing.
34 | field9: Literal[1] | (Literal[2] | str) # Error
@@ -99,8 +209,19 @@ PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single liter
36 |
37 | # Should emit for union in generic parent type.
|
= help: Replace with a single `Literal`
PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
32 32 |
33 33 | # Should handle user parentheses when fixing.
34 34 | field9: Literal[1] | (Literal[2] | str) # Error
35 |-field10: (Literal[1] | str) | Literal[2] # Error
35 |+field10: Literal[1, 2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
PYI030.pyi:38:15: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
37 | # Should emit for union in generic parent type.
38 | field11: dict[Literal[1] | Literal[2], str] # Error
@@ -108,16 +229,38 @@ PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single liter
39 |
40 | # Should emit for unions with more than two cases
|
= help: Replace with a single `Literal`
PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Fix
35 35 | field10: (Literal[1] | str) | Literal[2] # Error
36 36 |
37 37 | # Should emit for union in generic parent type.
38 |-field11: dict[Literal[1] | Literal[2], str] # Error
38 |+field11: dict[Literal[1, 2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
PYI030.pyi:41:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Fix
38 38 | field11: dict[Literal[1] | Literal[2], str] # Error
39 39 |
40 40 | # Should emit for unions with more than two cases
41 |-field12: Literal[1] | Literal[2] | Literal[3] # Error
41 |+field12: Literal[1, 2, 3] # Error
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI030.pyi:42:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
@@ -126,8 +269,19 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter
43 |
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
= help: Replace with a single `Literal`
PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
Fix
39 39 |
40 40 | # Should emit for unions with more than two cases
41 41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
42 |-field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
42 |+field13: Literal[1, 2, 3, 4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
PYI030.pyi:45:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
@@ -135,8 +289,19 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter
46 |
47 | # Should emit for unions with mixed literal internal types
|
= help: Replace with a single `Literal`
PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
Fix
42 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
43 43 |
44 44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 |-field14: Literal[1] | Literal[2] | str | Literal[3] # Error
45 |+field14: Literal[1, 2, 3] # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
PYI030.pyi:48:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
47 | # Should emit for unions with mixed literal internal types
48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
@@ -144,8 +309,19 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter
49 |
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
= help: Replace with a single `Literal`
PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
Fix
45 45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
46 46 |
47 47 | # Should emit for unions with mixed literal internal types
48 |-field15: Literal[1] | Literal["foo"] | Literal[True] # Error
48 |+field15: Literal[1, "foo", True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 51 | field16: Literal[1] | Literal[1] # OK
PYI030.pyi:51:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 | field16: Literal[1] | Literal[1] # OK
@@ -153,8 +329,19 @@ PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single liter
52 |
53 | # Shouldn't emit if in new parent type
|
= help: Replace with a single `Literal`
PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
48 48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
49 49 |
50 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 |-field16: Literal[1] | Literal[1] # OK
51 |+field16: Literal[1, 1] # OK
52 52 |
53 53 | # Shouldn't emit if in new parent type
54 54 | field17: Literal[1] | dict[Literal[2], str] # OK
PYI030.pyi:60:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
59 | # Should respect name of literal type used
60 | field19: typing.Literal[1] | typing.Literal[2] # Error
@@ -162,8 +349,19 @@ PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single liter
61 |
62 | # Should emit in cases with newlines
|
= help: Replace with a single `Literal`
PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
57 57 | field18: dict[Literal[1], Literal[2]] # OK
58 58 |
59 59 | # Should respect name of literal type used
60 |-field19: typing.Literal[1] | typing.Literal[2] # Error
60 |+field19: Literal[1, 2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 63 | field20: typing.Union[
PYI030.pyi:63:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
62 | # Should emit in cases with newlines
63 | field20: typing.Union[
@@ -177,8 +375,24 @@ PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single liter
69 |
70 | # Should handle multiple unions with multiple members
|
= help: Replace with a single `Literal`
PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Fix
60 60 | field19: typing.Literal[1] | typing.Literal[2] # Error
61 61 |
62 62 | # Should emit in cases with newlines
63 |-field20: typing.Union[
64 |- Literal[
65 |- 1 # test
66 |- ],
67 |- Literal[2],
68 |-] # Error, newline and comment will not be emitted in message
63 |+field20: Literal[1, 2] # Error, newline and comment will not be emitted in message
69 64 |
70 65 | # Should handle multiple unions with multiple members
71 66 | field21: Literal[1, 2] | Literal[3, 4] # Error
PYI030.pyi:71:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
70 | # Should handle multiple unions with multiple members
71 | field21: Literal[1, 2] | Literal[3, 4] # Error
@@ -186,8 +400,19 @@ PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single liter
72 |
73 | # Should emit in cases with `typing.Union` instead of `|`
|
= help: Replace with a single `Literal`
PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
68 68 | ] # Error, newline and comment will not be emitted in message
69 69 |
70 70 | # Should handle multiple unions with multiple members
71 |-field21: Literal[1, 2] | Literal[3, 4] # Error
71 |+field21: Literal[1, 2, 3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
PYI030.pyi:74:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
73 | # Should emit in cases with `typing.Union` instead of `|`
74 | field22: typing.Union[Literal[1], Literal[2]] # Error
@@ -195,8 +420,19 @@ PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single liter
75 |
76 | # Should emit in cases with `typing_extensions.Literal`
|
= help: Replace with a single `Literal`
PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
71 71 | field21: Literal[1, 2] | Literal[3, 4] # Error
72 72 |
73 73 | # Should emit in cases with `typing.Union` instead of `|`
74 |-field22: typing.Union[Literal[1], Literal[2]] # Error
74 |+field22: Literal[1, 2] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
PYI030.pyi:77:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
76 | # Should emit in cases with `typing_extensions.Literal`
77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
@@ -204,8 +440,19 @@ PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single liter
78 |
79 | # Should emit in cases with nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
74 74 | field22: typing.Union[Literal[1], Literal[2]] # Error
75 75 |
76 76 | # Should emit in cases with `typing_extensions.Literal`
77 |-field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
77 |+field23: Literal[1, 2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
PYI030.pyi:80:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
79 | # Should emit in cases with nested `typing.Union`
80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
@@ -213,8 +460,19 @@ PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single liter
81 |
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
= help: Replace with a single `Literal`
PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
Fix
77 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
78 78 |
79 79 | # Should emit in cases with nested `typing.Union`
80 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
80 |+field24: Literal[1, 2] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
PYI030.pyi:83:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
@@ -222,12 +480,31 @@ PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single liter
84 |
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
Fix
80 80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
81 81 |
82 82 | # Should emit in cases with mixed `typing.Union` and `|`
83 |-field25: typing.Union[Literal[1], Literal[2] | str] # Error
83 |+field25: Literal[1, 2] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
PYI030.pyi:86:10: PYI030 [*] Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
= help: Replace with a single `Literal`
Fix
83 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
84 84 |
85 85 | # Should emit only once in cases with multiple nested `typing.Union`
86 |-field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
86 |+field24: Literal[1, 2, 3, 4] # Error

View File

@@ -5,8 +5,8 @@ use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::node::AstNode;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::AstNode;
use ruff_python_ast::{self as ast, Arguments, Constant, Decorator, Expr, ExprContext};
use ruff_python_codegen::Generator;
use ruff_python_trivia::CommentRanges;

View File

@@ -6,7 +6,7 @@ use log::error;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{self as ast, whitespace, Constant, ElifElseClause, Expr, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};

View File

@@ -1,8 +1,8 @@
use ruff_diagnostics::Edit;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};

View File

@@ -1,5 +1,5 @@
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_ast::helpers::map_callable;
use ruff_python_semantic::{Binding, BindingKind, ScopeKind, SemanticModel};
pub(crate) fn is_valid_runtime_import(binding: &Binding, semantic: &SemanticModel) -> bool {
@@ -40,13 +40,11 @@ fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticMode
};
class_def.bases().iter().any(|base| {
semantic
.resolve_call_path(map_subscript(base))
.is_some_and(|call_path| {
base_classes
.iter()
.any(|base_class| from_qualified_name(base_class) == call_path)
})
semantic.resolve_call_path(base).is_some_and(|call_path| {
base_classes
.iter()
.any(|base_class| from_qualified_name(base_class) == call_path)
})
})
}

View File

@@ -22,7 +22,6 @@ mod tests {
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_14.pyi"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_15.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_2.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_3.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_4.py"))]

View File

@@ -87,7 +87,6 @@ mod tests {
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D.py"))]
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D301.py"))]
#[test_case(Rule::TripleSingleQuotes, Path::new("D.py"))]
#[test_case(Rule::TripleSingleQuotes, Path::new("D300.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,6 +1,6 @@
use memchr::memchr_iter;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
@@ -46,21 +46,18 @@ use crate::docstrings::Docstring;
#[violation]
pub struct EscapeSequenceInDocstring;
impl AlwaysFixableViolation for EscapeSequenceInDocstring {
impl Violation for EscapeSequenceInDocstring {
#[derive_message_formats]
fn message(&self) -> String {
format!(r#"Use `r"""` if any backslashes in a docstring"#)
}
fn fix_title(&self) -> String {
format!(r#"Add `r` prefix"#)
}
}
/// D301
pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
// Docstring is already raw.
if docstring.leading_quote().contains(['r', 'R']) {
let contents = docstring.contents;
if contents.starts_with('r') || contents.starts_with("ur") {
return;
}
@@ -70,15 +67,11 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
if memchr_iter(b'\\', bytes).any(|position| {
let escaped_char = bytes.get(position.saturating_add(1));
// Allow continuations (backslashes followed by newlines) and Unicode escapes.
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'U' | b'N'))
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'N'))
}) {
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"r".to_owned() + docstring.contents,
checker.diagnostics.push(Diagnostic::new(
EscapeSequenceInDocstring,
docstring.range(),
)));
checker.diagnostics.push(diagnostic);
));
}
}

View File

@@ -1,4 +1,4 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_codegen::Quote;
use ruff_text_size::Ranged;
@@ -37,8 +37,6 @@ pub struct TripleSingleQuotes {
}
impl Violation for TripleSingleQuotes {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let TripleSingleQuotes { expected_quote } = self;
@@ -47,25 +45,12 @@ impl Violation for TripleSingleQuotes {
Quote::Single => format!(r#"Use triple single quotes `'''`"#),
}
}
fn fix_title(&self) -> Option<String> {
let TripleSingleQuotes { expected_quote } = self;
Some(match expected_quote {
Quote::Double => format!("Convert to triple double quotes"),
Quote::Single => format!("Convert to triple single quotes"),
})
}
}
/// D300
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let leading_quote = docstring.leading_quote();
let prefixes = docstring
.leading_quote()
.trim_end_matches(|c| c == '\'' || c == '"')
.to_owned();
let expected_quote = if docstring.body().contains("\"\"\"") {
Quote::Single
} else {
@@ -75,34 +60,18 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
match expected_quote {
Quote::Single => {
if !leading_quote.ends_with("'''") {
let mut diagnostic =
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
let body = docstring.body().as_str();
if !body.ends_with('\'') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}'''{body}'''"),
docstring.range(),
)));
}
checker.diagnostics.push(diagnostic);
checker.diagnostics.push(Diagnostic::new(
TripleSingleQuotes { expected_quote },
docstring.range(),
));
}
}
Quote::Double => {
if !leading_quote.ends_with("\"\"\"") {
let mut diagnostic =
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
let body = docstring.body().as_str();
if !body.ends_with('"') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}\"\"\"{body}\"\"\""),
docstring.range(),
)));
}
checker.diagnostics.push(diagnostic);
checker.diagnostics.push(Diagnostic::new(
TripleSingleQuotes { expected_quote },
docstring.range(),
));
}
}
}

View File

@@ -1,102 +1,47 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:307:5: D300 [*] Use triple double quotes `"""`
D.py:307:5: D300 Use triple double quotes `"""`
|
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 | def triple_single_quotes_raw():
307 | r'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
304 304 |
305 305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 306 | def triple_single_quotes_raw():
307 |- r'''Summary.'''
307 |+ r"""Summary."""
308 308 |
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
D.py:312:5: D300 [*] Use triple double quotes `"""`
D.py:312:5: D300 Use triple double quotes `"""`
|
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 | def triple_single_quotes_raw_uppercase():
312 | R'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 311 | def triple_single_quotes_raw_uppercase():
312 |- R'''Summary.'''
312 |+ R"""Summary."""
313 313 |
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:317:5: D300 [*] Use triple double quotes `"""`
D.py:317:5: D300 Use triple double quotes `"""`
|
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 | def single_quotes_raw():
317 | r'Summary.'
| ^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 316 | def single_quotes_raw():
317 |- r'Summary.'
317 |+ r"""Summary."""
318 318 |
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:322:5: D300 [*] Use triple double quotes `"""`
D.py:322:5: D300 Use triple double quotes `"""`
|
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 | def single_quotes_raw_uppercase():
322 | R'Summary.'
| ^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 321 | def single_quotes_raw_uppercase():
322 |- R'Summary.'
322 |+ R"""Summary."""
323 323 |
324 324 |
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:328:5: D300 [*] Use triple double quotes `"""`
D.py:328:5: D300 Use triple double quotes `"""`
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
328 | R'Sum\mary.'
| ^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
326 326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 327 | def single_quotes_raw_uppercase_backslash():
328 |- R'Sum\mary.'
328 |+ R"""Sum\mary."""
329 329 |
330 330 |
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
D.py:645:5: D300 [*] Use triple double quotes `"""`
D.py:645:5: D300 Use triple double quotes `"""`
|
644 | def single_line_docstring_with_an_escaped_backslash():
645 | "\
@@ -106,21 +51,8 @@ D.py:645:5: D300 [*] Use triple double quotes `"""`
647 |
648 | class StatementOnSameLineAsDocstring:
|
= help: Convert to triple double quotes
Fix
642 642 |
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
645 |- "\
646 |- "
645 |+ """\
646 |+ """
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
D.py:649:5: D300 [*] Use triple double quotes `"""`
D.py:649:5: D300 Use triple double quotes `"""`
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
@@ -128,37 +60,15 @@ D.py:649:5: D300 [*] Use triple double quotes `"""`
650 | def sort_services(self):
651 | pass
|
= help: Convert to triple double quotes
Fix
646 646 | "
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
649 |+ """After this docstring there's another statement on the same line separated by a semicolon.""" ; priorities=1
650 650 | def sort_services(self):
651 651 | pass
652 652 |
D.py:654:5: D300 [*] Use triple double quotes `"""`
D.py:654:5: D300 Use triple double quotes `"""`
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
651 651 | pass
652 652 |
653 653 | class StatementOnSameLineAsDocstring:
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
654 |+ """After this docstring there's another statement on the same line separated by a semicolon."""; priorities=1
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
D.py:658:5: D300 [*] Use triple double quotes `"""`
D.py:658:5: D300 Use triple double quotes `"""`
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
@@ -166,19 +76,8 @@ D.py:658:5: D300 [*] Use triple double quotes `"""`
659 | def sort_services(self):
660 | pass
|
= help: Convert to triple double quotes
Fix
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
658 |- "After this docstring there's a comment." # priorities=1
658 |+ """After this docstring there's a comment.""" # priorities=1
659 659 | def sort_services(self):
660 660 | pass
661 661 |
D.py:664:5: D300 [*] Use triple double quotes `"""`
D.py:664:5: D300 Use triple double quotes `"""`
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
@@ -186,15 +85,5 @@ D.py:664:5: D300 [*] Use triple double quotes `"""`
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D300
|
= help: Convert to triple double quotes
Fix
661 661 |
662 662 |
663 663 | def newline_after_closing_quote(self):
664 |- "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
664 |+ """We enforce a newline after the closing quote for a multi-line docstring \
665 |+ but continuations shouldn't be considered multi-line"""

View File

@@ -1,27 +0,0 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D300.py:6:5: D300 Use triple double quotes `"""`
|
5 | def ends_in_quote():
6 | 'Sum\\mary."'
| ^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
D300.py:10:5: D300 [*] Use triple double quotes `"""`
|
9 | def contains_quote():
10 | 'Sum"\\mary.'
| ^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
7 7 |
8 8 |
9 9 | def contains_quote():
10 |- 'Sum"\\mary.'
10 |+ """Sum"\\mary."""

View File

@@ -1,23 +1,28 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:333:5: D301 [*] Use `r"""` if any backslashes in a docstring
D.py:328:5: D301 Use `r"""` if any backslashes in a docstring
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
328 | R'Sum\mary.'
| ^^^^^^^^^^^^ D301
|
D.py:333:5: D301 Use `r"""` if any backslashes in a docstring
|
331 | @expect('D301: Use r""" if any backslashes in a docstring')
332 | def double_quotes_backslash():
333 | """Sum\\mary."""
| ^^^^^^^^^^^^^^^^ D301
|
= help: Add `r` prefix
Suggested fix
330 330 |
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
332 332 | def double_quotes_backslash():
333 |- """Sum\\mary."""
333 |+ r"""Sum\\mary."""
334 334 |
335 335 |
336 336 | @expect('D301: Use r""" if any backslashes in a docstring')
D.py:338:5: D301 Use `r"""` if any backslashes in a docstring
|
336 | @expect('D301: Use r""" if any backslashes in a docstring')
337 | def double_quotes_backslash_uppercase():
338 | R"""Sum\\mary."""
| ^^^^^^^^^^^^^^^^^ D301
|

View File

@@ -1,20 +1,18 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring
D301.py:2:5: D301 Use `r"""` if any backslashes in a docstring
|
1 | def double_quotes_backslash():
2 | """Sum\\mary."""
| ^^^^^^^^^^^^^^^^ D301
|
= help: Add `r` prefix
Suggested fix
1 1 | def double_quotes_backslash():
2 |- """Sum\\mary."""
2 |+ r"""Sum\\mary."""
3 3 |
4 4 |
5 5 | def double_quotes_backslash_raw():
D301.py:10:5: D301 Use `r"""` if any backslashes in a docstring
|
9 | def double_quotes_backslash_uppercase():
10 | R"""Sum\\mary."""
| ^^^^^^^^^^^^^^^^^ D301
|

View File

@@ -1,15 +1,10 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
bom.py:1:1: D300 [*] Use triple double quotes `"""`
bom.py:1:1: D300 Use triple double quotes `"""`
|
1 | ''' SAM macro definitions '''
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
1 |-''' SAM macro definitions '''
1 |+""" SAM macro definitions """

View File

@@ -50,7 +50,6 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_16.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_17.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_18.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_19.py"))]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
#[test_case(Rule::LateFutureImport, Path::new("F404.py"))]
@@ -136,7 +135,6 @@ mod tests {
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_18.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_19.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_20.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---

View File

@@ -1,14 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F821_20.py:3:24: F821 Undefined name `Record`
|
1 | """Test lazy evaluation of type alias values."""
2 |
3 | type RecordCallback[R: Record] = Callable[[R], None]
| ^^^^^^ F821
4 |
5 | from collections.abc import Callable
|

View File

@@ -22,11 +22,7 @@ pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
}
}
pub(super) fn in_dunder_method(
dunder_name: &str,
semantic: &SemanticModel,
settings: &LinterSettings,
) -> bool {
pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings) -> bool {
let scope = semantic.current_scope();
let ScopeKind::Function(ast::StmtFunctionDef {
name,
@@ -36,7 +32,7 @@ pub(super) fn in_dunder_method(
else {
return false;
};
if name != dunder_name {
if name != "__init__" {
return false;
}
let Some(parent) = semantic.first_non_type_parent_scope(scope) else {

View File

@@ -133,11 +133,8 @@ mod tests {
Rule::SubprocessRunWithoutCheck,
Path::new("subprocess_run_without_check.py")
)]
#[test_case(Rule::UnspecifiedEncoding, Path::new("unspecified_encoding.py"))]
#[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))]
#[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))]
#[test_case(Rule::MisplacedBareRaise, Path::new("misplaced_bare_raise.py"))]
#[test_case(Rule::LiteralMembership, Path::new("literal_membership.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
@@ -231,22 +228,6 @@ mod tests {
Ok(())
}
#[test]
fn max_boolean_expressions() -> Result<()> {
let diagnostics = test_path(
Path::new("pylint/too_many_boolean_expressions.py"),
&LinterSettings {
pylint: pylint::settings::Settings {
max_bool_expr: 5,
..pylint::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::TooManyBooleanExpressions)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn max_statements() -> Result<()> {
let diagnostics = test_path(

View File

@@ -54,7 +54,7 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt])
.iter()
.filter_map(ruff_python_ast::Stmt::as_function_def_stmt)
.filter(|method| {
if is_known_dunder_method(&method.name) || matches!(method.name.as_str(), "_") {
if is_known_dunder_method(&method.name) {
return false;
}
method.name.starts_with('_') && method.name.ends_with('_')
@@ -196,13 +196,5 @@ fn is_known_dunder_method(method: &str) -> bool {
| "__trunc__"
| "__weakref__"
| "__xor__"
// Overridable sunder names from the `Enum` class.
// See: https://docs.python.org/3/library/enum.html#supported-sunder-names
| "_name_"
| "_value_"
| "_missing_"
| "_ignore_"
| "_order_"
| "_generate_next_value_"
)
}

View File

@@ -1,7 +1,9 @@
use ast::ExprContext;
use ruff_python_ast::{self as ast, Expr};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
@@ -36,7 +38,7 @@ impl AlwaysFixableViolation for IterationOverSet {
}
fn fix_title(&self) -> String {
format!("Convert to `tuple`")
format!("Use a sequence type instead of a `set` when iterating over values")
}
}
@@ -52,14 +54,15 @@ pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range());
let tuple = if let [elt] = elts.as_slice() {
let elt = checker.locator().slice(elt);
format!("({elt},)")
} else {
let set = checker.locator().slice(expr);
format!("({})", &set[1..set.len() - 1])
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(tuple, expr.range())));
let tuple = checker.generator().expr(&Expr::Tuple(ast::ExprTuple {
elts: elts.clone(),
ctx: ExprContext::Store,
range: TextRange::default(),
}));
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("({tuple})"),
expr.range(),
)));
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,65 +0,0 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, CmpOp, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for membership tests on `list` and `tuple` literals.
///
/// ## Why is this bad?
/// When testing for membership in a static sequence, prefer a `set` literal
/// over a `list` or `tuple`, as Python optimizes `set` membership tests.
///
/// ## Example
/// ```python
/// 1 in [1, 2, 3]
/// ```
///
/// Use instead:
/// ```python
/// 1 in {1, 2, 3}
/// ```
/// ## References
/// - [Whats New In Python 3.2](https://docs.python.org/3/whatsnew/3.2.html#optimizations)
#[violation]
pub struct LiteralMembership;
impl AlwaysFixableViolation for LiteralMembership {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use a `set` literal when testing for membership")
}
fn fix_title(&self) -> String {
format!("Convert to `set`")
}
}
/// PLR6201
pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompare) {
let [op] = compare.ops.as_slice() else {
return;
};
if !matches!(op, CmpOp::In | CmpOp::NotIn) {
return;
}
let [right] = compare.comparators.as_slice() else {
return;
};
if !matches!(right, Expr::List(_) | Expr::Tuple(_)) {
return;
}
let mut diagnostic = Diagnostic::new(LiteralMembership, right.range());
let literal = checker.locator().slice(right);
let set = format!("{{{}}}", &literal[1..literal.len() - 1]);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(set, right.range())));
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,70 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::pylint::helpers::in_dunder_method;
/// ## What it does
/// Checks for bare `raise` statements outside of exception handlers.
///
/// ## Why is this bad?
/// A bare `raise` statement without an exception object will re-raise the last
/// exception that was active in the current scope, and is typically used
/// within an exception handler to re-raise the caught exception.
///
/// If a bare `raise` is used outside of an exception handler, it will generate
/// an error due to the lack of an active exception.
///
/// Note that a bare `raise` within a `finally` block will work in some cases
/// (namely, when the exception is raised within the `try` block), but should
/// be avoided as it can lead to confusing behavior.
///
/// ## Example
/// ```python
/// from typing import Any
///
///
/// def is_some(obj: Any) -> bool:
/// if obj is None:
/// raise
/// ```
///
/// Use instead:
/// ```python
/// from typing import Any
///
///
/// def is_some(obj: Any) -> bool:
/// if obj is None:
/// raise ValueError("`obj` cannot be `None`")
/// ```
#[violation]
pub struct MisplacedBareRaise;
impl Violation for MisplacedBareRaise {
#[derive_message_formats]
fn message(&self) -> String {
format!("Bare `raise` statement is not inside an exception handler")
}
}
/// PLE0704
pub(crate) fn misplaced_bare_raise(checker: &mut Checker, raise: &ast::StmtRaise) {
if raise.exc.is_some() {
return;
}
if checker.semantic().in_exception_handler() {
return;
}
if in_dunder_method("__exit__", checker.semantic(), checker.settings) {
return;
}
checker
.diagnostics
.push(Diagnostic::new(MisplacedBareRaise, raise.range()));
}

View File

@@ -24,12 +24,10 @@ pub(crate) use invalid_envvar_value::*;
pub(crate) use invalid_str_return::*;
pub(crate) use invalid_string_characters::*;
pub(crate) use iteration_over_set::*;
pub(crate) use literal_membership::*;
pub(crate) use load_before_global_declaration::*;
pub(crate) use logging::*;
pub(crate) use magic_value_comparison::*;
pub(crate) use manual_import_from::*;
pub(crate) use misplaced_bare_raise::*;
pub(crate) use named_expr_without_context::*;
pub(crate) use nested_min_max::*;
pub(crate) use no_self_use::*;
@@ -45,7 +43,6 @@ pub(crate) use subprocess_popen_preexec_fn::*;
pub(crate) use subprocess_run_without_check::*;
pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*;
pub(crate) use too_many_boolean_expressions::*;
pub(crate) use too_many_branches::*;
pub(crate) use too_many_public_methods::*;
pub(crate) use too_many_return_statements::*;
@@ -55,7 +52,6 @@ pub(crate) use type_name_incorrect_variance::*;
pub(crate) use type_param_name_mismatch::*;
pub(crate) use unexpected_special_method_signature::*;
pub(crate) use unnecessary_direct_lambda_call::*;
pub(crate) use unspecified_encoding::*;
pub(crate) use useless_else_on_loop::*;
pub(crate) use useless_import_alias::*;
pub(crate) use useless_return::*;
@@ -88,12 +84,10 @@ mod invalid_envvar_value;
mod invalid_str_return;
mod invalid_string_characters;
mod iteration_over_set;
mod literal_membership;
mod load_before_global_declaration;
mod logging;
mod magic_value_comparison;
mod manual_import_from;
mod misplaced_bare_raise;
mod named_expr_without_context;
mod nested_min_max;
mod no_self_use;
@@ -109,7 +103,6 @@ mod subprocess_popen_preexec_fn;
mod subprocess_run_without_check;
mod sys_exit_alias;
mod too_many_arguments;
mod too_many_boolean_expressions;
mod too_many_branches;
mod too_many_public_methods;
mod too_many_return_statements;
@@ -119,7 +112,6 @@ mod type_name_incorrect_variance;
mod type_param_name_mismatch;
mod unexpected_special_method_signature;
mod unnecessary_direct_lambda_call;
mod unspecified_encoding;
mod useless_else_on_loop;
mod useless_import_alias;
mod useless_return;

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::call_path::{from_qualified_name, CallPath};
use ruff_python_ast::{self as ast, ParameterWithDefault};
use ruff_python_semantic::{
analyze::{function_type, visibility},
Scope, ScopeId, ScopeKind,
Scope, ScopeKind,
};
use ruff_text_size::Ranged;
@@ -45,12 +45,7 @@ impl Violation for NoSelfUse {
}
/// PLR6301
pub(crate) fn no_self_use(
checker: &Checker,
scope_id: ScopeId,
scope: &Scope,
diagnostics: &mut Vec<Diagnostic>,
) {
pub(crate) fn no_self_use(checker: &Checker, scope: &Scope, diagnostics: &mut Vec<Diagnostic>) {
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
return;
};
@@ -110,28 +105,11 @@ pub(crate) fn no_self_use(
return;
};
if parameter.name.as_str() != "self" {
return;
}
// If the method contains a `super` reference, then it should be considered to use self
// implicitly.
if let Some(binding_id) = checker.semantic().global_scope().get("super") {
let binding = checker.semantic().binding(binding_id);
if binding.kind.is_builtin() {
if binding
.references()
.any(|id| checker.semantic().reference(id).scope_id() == scope_id)
{
return;
}
}
}
if scope
.get("self")
.map(|binding_id| checker.semantic().binding(binding_id))
.is_some_and(|binding| binding.kind.is_argument() && !binding.is_used())
if parameter.name.as_str() == "self"
&& scope
.get("self")
.map(|binding_id| checker.semantic().binding(binding_id))
.is_some_and(|binding| binding.kind.is_argument() && !binding.is_used())
{
diagnostics.push(Diagnostic::new(
NoSelfUse {

View File

@@ -6,7 +6,7 @@ use ruff_python_ast::helpers::is_const_none;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::pylint::helpers::in_dunder_method;
use crate::rules::pylint::helpers::in_dunder_init;
/// ## What it does
/// Checks for `__init__` methods that return values.
@@ -58,7 +58,7 @@ pub(crate) fn return_in_init(checker: &mut Checker, stmt: &Stmt) {
}
}
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
if in_dunder_init(checker.semantic(), checker.settings) {
checker
.diagnostics
.push(Diagnostic::new(ReturnInInit, stmt.range()));

View File

@@ -1,89 +0,0 @@
use ast::{Expr, StmtIf};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for too many Boolean expressions in an `if` statement.
///
/// By default, this rule allows up to 5 expressions. This can be configured
/// using the [`pylint.max-bool-expr`] option.
///
/// ## Why is this bad?
/// `if` statements with many Boolean expressions are harder to understand
/// and maintain. Consider assigning the result of the Boolean expression,
/// or any of its sub-expressions, to a variable.
///
/// ## Example
/// ```python
/// if a and b and c and d and e and f and g and h:
/// ...
/// ```
///
/// ## Options
/// - `pylint.max-bool-expr`
#[violation]
pub struct TooManyBooleanExpressions {
expressions: usize,
max_expressions: usize,
}
impl Violation for TooManyBooleanExpressions {
#[derive_message_formats]
fn message(&self) -> String {
let TooManyBooleanExpressions {
expressions,
max_expressions,
} = self;
format!("Too many Boolean expressions ({expressions} > {max_expressions})")
}
}
/// PLR0916
pub(crate) fn too_many_boolean_expressions(checker: &mut Checker, stmt: &StmtIf) {
if let Some(bool_op) = stmt.test.as_bool_op_expr() {
let expressions = count_bools(bool_op);
if expressions > checker.settings.pylint.max_bool_expr {
checker.diagnostics.push(Diagnostic::new(
TooManyBooleanExpressions {
expressions,
max_expressions: checker.settings.pylint.max_bool_expr,
},
bool_op.range(),
));
}
}
for elif in &stmt.elif_else_clauses {
if let Some(bool_op) = elif.test.as_ref().and_then(Expr::as_bool_op_expr) {
let expressions = count_bools(bool_op);
if expressions > checker.settings.pylint.max_bool_expr {
checker.diagnostics.push(Diagnostic::new(
TooManyBooleanExpressions {
expressions,
max_expressions: checker.settings.pylint.max_bool_expr,
},
bool_op.range(),
));
}
}
}
}
/// Count the number of Boolean expressions in a `bool_op` expression.
fn count_bools(bool_op: &ast::ExprBoolOp) -> usize {
bool_op
.values
.iter()
.map(|expr| {
if let Expr::BoolOp(bool_op) = expr {
count_bools(bool_op)
} else {
1
}
})
.sum::<usize>()
}

View File

@@ -1,157 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_ast::call_path::{format_call_path, CallPath};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for uses of `open` and related calls without an explicit `encoding`
/// argument.
///
/// ## Why is this bad?
/// Using `open` in text mode without an explicit encoding can lead to
/// non-portable code, with differing behavior across platforms.
///
/// Instead, consider using the `encoding` parameter to enforce a specific
/// encoding.
///
/// ## Example
/// ```python
/// open("file.txt")
/// ```
///
/// Use instead:
/// ```python
/// open("file.txt", encoding="utf-8")
/// ```
///
/// ## References
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
#[violation]
pub struct UnspecifiedEncoding {
function_name: String,
mode: Mode,
}
impl Violation for UnspecifiedEncoding {
#[derive_message_formats]
fn message(&self) -> String {
let UnspecifiedEncoding {
function_name,
mode,
} = self;
match mode {
Mode::Supported => {
format!("`{function_name}` in text mode without explicit `encoding` argument")
}
Mode::Unsupported => {
format!("`{function_name}` without explicit `encoding` argument")
}
}
}
}
/// PLW1514
pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) {
let Some((function_name, mode)) = checker
.semantic()
.resolve_call_path(&call.func)
.filter(|call_path| is_violation(call, call_path))
.map(|call_path| {
(
format_call_path(call_path.as_slice()),
Mode::from(&call_path),
)
})
else {
return;
};
checker.diagnostics.push(Diagnostic::new(
UnspecifiedEncoding {
function_name,
mode,
},
call.func.range(),
));
}
/// Returns `true` if the given expression is a string literal containing a `b` character.
fn is_binary_mode(expr: &ast::Expr) -> Option<bool> {
Some(expr.as_constant_expr()?.value.as_str()?.value.contains('b'))
}
/// Returns `true` if the given call lacks an explicit `encoding`.
fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool {
// If we have something like `*args`, which might contain the encoding argument, abort.
if call
.arguments
.args
.iter()
.any(ruff_python_ast::Expr::is_starred_expr)
{
return false;
}
// If we have something like `**kwargs`, which might contain the encoding argument, abort.
if call
.arguments
.keywords
.iter()
.any(|keyword| keyword.arg.is_none())
{
return false;
}
match call_path.as_slice() {
["" | "codecs" | "_io", "open"] => {
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
if is_binary_mode(mode_arg).unwrap_or(true) {
// binary mode or unknown mode is no violation
return false;
}
}
// else mode not specified, defaults to text mode
call.arguments.find_argument("encoding", 3).is_none()
}
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
let mode_pos = usize::from(call_path[1] == "SpooledTemporaryFile");
if let Some(mode_arg) = call.arguments.find_argument("mode", mode_pos) {
if is_binary_mode(mode_arg).unwrap_or(true) {
// binary mode or unknown mode is no violation
return false;
}
} else {
// defaults to binary mode
return false;
}
call.arguments
.find_argument("encoding", mode_pos + 2)
.is_none()
}
["io" | "_io", "TextIOWrapper"] => call.arguments.find_argument("encoding", 1).is_none(),
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
/// The call supports a `mode` argument.
Supported,
/// The call does not support a `mode` argument.
Unsupported,
}
impl From<&CallPath<'_>> for Mode {
fn from(value: &CallPath<'_>) -> Self {
match value.as_slice() {
["" | "codecs" | "_io", "open"] => Mode::Supported,
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
Mode::Supported
}
["io" | "_io", "TextIOWrapper"] => Mode::Unsupported,
_ => Mode::Unsupported,
}
}
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::pylint::helpers::in_dunder_method;
use crate::rules::pylint::helpers::in_dunder_init;
/// ## What it does
/// Checks for `__init__` methods that are turned into generators by the
@@ -40,7 +40,7 @@ impl Violation for YieldInInit {
/// PLE0100
pub(crate) fn yield_in_init(checker: &mut Checker, expr: &Expr) {
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
if in_dunder_init(checker.semantic(), checker.settings) {
checker
.diagnostics
.push(Diagnostic::new(YieldInInit, expr.range()));

View File

@@ -40,7 +40,6 @@ pub struct Settings {
pub allow_magic_value_types: Vec<ConstantType>,
pub max_args: usize,
pub max_returns: usize,
pub max_bool_expr: usize,
pub max_branches: usize,
pub max_statements: usize,
pub max_public_methods: usize,
@@ -52,7 +51,6 @@ impl Default for Settings {
allow_magic_value_types: vec![ConstantType::Str, ConstantType::Bytes],
max_args: 5,
max_returns: 6,
max_bool_expr: 5,
max_branches: 12,
max_statements: 50,
max_public_methods: 20,

View File

@@ -9,7 +9,7 @@ iteration_over_set.py:3:13: PLC0208 [*] Use a sequence type instead of a `set` w
| ^^^ PLC0208
4 | print(f"I can count to {item}!")
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
1 1 | # Errors
@@ -28,7 +28,7 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
7 | print(f"I like {item}.")
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
3 3 | for item in {1}:
@@ -38,136 +38,90 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
6 |+for item in ("apples", "lemons", "water"): # flags in-line set literals
7 7 | print(f"I like {item}.")
8 8 |
9 9 | for item in {1,}:
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
iteration_over_set.py:9:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
iteration_over_set.py:9:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
7 | print(f"I like {item}.")
8 |
9 | for item in {1,}:
| ^^^^ PLC0208
10 | print(f"I can count to {item}!")
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
| ^^^^^^^^^ PLC0208
10 |
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
6 6 | for item in {"apples", "lemons", "water"}: # flags in-line set literals
7 7 | print(f"I like {item}.")
8 8 |
9 |-for item in {1,}:
9 |+for item in (1,):
10 10 | print(f"I can count to {item}!")
11 11 |
12 12 | for item in {
9 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
9 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
10 10 |
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
12 12 |
iteration_over_set.py:12:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
iteration_over_set.py:11:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
10 | print(f"I can count to {item}!")
11 |
12 | for item in {
| _____________^
13 | | "apples", "lemons", "water"
14 | | }: # flags in-line set literals
| |_^ PLC0208
15 | print(f"I like {item}.")
|
= help: Convert to `tuple`
Fix
9 9 | for item in {1,}:
10 10 | print(f"I can count to {item}!")
11 11 |
12 |-for item in {
12 |+for item in (
13 13 | "apples", "lemons", "water"
14 |-}: # flags in-line set literals
14 |+): # flags in-line set literals
15 15 | print(f"I like {item}.")
16 16 |
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
iteration_over_set.py:17:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
15 | print(f"I like {item}.")
16 |
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
| ^^^^^^^^^ PLC0208
18 |
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
= help: Convert to `tuple`
Fix
14 14 | }: # flags in-line set literals
15 15 | print(f"I like {item}.")
16 16 |
17 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
17 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
18 18 |
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
20 20 |
iteration_over_set.py:19:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
18 |
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
10 |
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
| ^^^^^^^^^ PLC0208
20 |
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
12 |
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
16 16 |
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
18 18 |
19 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
19 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
20 20 |
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
22 22 |
8 8 |
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
10 10 |
11 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
11 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
12 12 |
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
14 14 |
iteration_over_set.py:21:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
iteration_over_set.py:13:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
20 |
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
12 |
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
| ^^^^^^^^^ PLC0208
22 |
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
14 |
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
18 18 |
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
20 20 |
21 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
21 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
22 22 |
23 23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
24 24 |
10 10 |
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
12 12 |
13 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
13 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
14 14 |
15 15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
16 16 |
iteration_over_set.py:23:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
iteration_over_set.py:15:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
22 |
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
14 |
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
| ^^^^^^^^^ PLC0208
24 |
25 | # Non-errors
16 |
17 | # Non-errors
|
= help: Convert to `tuple`
= help: Use a sequence type instead of a `set` when iterating over values
Fix
20 20 |
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
22 22 |
23 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
23 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
24 24 |
25 25 | # Non-errors
26 26 |
12 12 |
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
14 14 |
15 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
15 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
16 16 |
17 17 | # Non-errors
18 18 |

View File

@@ -1,90 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
misplaced_bare_raise.py:30:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
29 | try:
30 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
31 | except Exception:
32 | pass
|
misplaced_bare_raise.py:36:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
34 | def f():
35 | try:
36 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
37 | except Exception:
38 | pass
|
misplaced_bare_raise.py:41:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
40 | def g():
41 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
42 |
43 | def h():
|
misplaced_bare_raise.py:47:17: PLE0704 Bare `raise` statement is not inside an exception handler
|
45 | if True:
46 | def i():
47 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
48 | except Exception:
49 | pass
|
misplaced_bare_raise.py:50:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
48 | except Exception:
49 | pass
50 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
51 |
52 | raise # [misplaced-bare-raise]
|
misplaced_bare_raise.py:52:1: PLE0704 Bare `raise` statement is not inside an exception handler
|
50 | raise # [misplaced-bare-raise]
51 |
52 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
53 |
54 | try:
|
misplaced_bare_raise.py:58:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
56 | except:
57 | def i():
58 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
59 |
60 | try:
|
misplaced_bare_raise.py:64:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
62 | except:
63 | class C:
64 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
65 |
66 | try:
|
misplaced_bare_raise.py:71:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
69 | pass
70 | finally:
71 | raise # [misplaced-bare-raise]
| ^^^^^ PLE0704
|

View File

@@ -1,69 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
literal_membership.py:2:6: PLR6201 [*] Use a `set` literal when testing for membership
|
1 | # Errors
2 | 1 in [1, 2, 3]
| ^^^^^^^^^ PLR6201
3 | 1 in (1, 2, 3)
4 | 1 in (
|
= help: Convert to `set`
Fix
1 1 | # Errors
2 |-1 in [1, 2, 3]
2 |+1 in {1, 2, 3}
3 3 | 1 in (1, 2, 3)
4 4 | 1 in (
5 5 | 1, 2, 3
literal_membership.py:3:6: PLR6201 [*] Use a `set` literal when testing for membership
|
1 | # Errors
2 | 1 in [1, 2, 3]
3 | 1 in (1, 2, 3)
| ^^^^^^^^^ PLR6201
4 | 1 in (
5 | 1, 2, 3
|
= help: Convert to `set`
Fix
1 1 | # Errors
2 2 | 1 in [1, 2, 3]
3 |-1 in (1, 2, 3)
3 |+1 in {1, 2, 3}
4 4 | 1 in (
5 5 | 1, 2, 3
6 6 | )
literal_membership.py:4:6: PLR6201 [*] Use a `set` literal when testing for membership
|
2 | 1 in [1, 2, 3]
3 | 1 in (1, 2, 3)
4 | 1 in (
| ______^
5 | | 1, 2, 3
6 | | )
| |_^ PLR6201
7 |
8 | # OK
|
= help: Convert to `set`
Fix
1 1 | # Errors
2 2 | 1 in [1, 2, 3]
3 3 | 1 in (1, 2, 3)
4 |-1 in (
4 |+1 in {
5 5 | 1, 2, 3
6 |-)
6 |+}
7 7 |
8 8 | # OK
9 9 | fruits = ["cherry", "grapes"]

View File

@@ -1,30 +1,39 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
no_self_use.py:7:28: PLR6301 Method `developer_greeting` could be a function or static method
no_self_use.py:5:28: PLR6301 Method `developer_greeting` could be a function or static method
|
6 | class Person:
7 | def developer_greeting(self, name): # [no-self-use]
4 | class Person:
5 | def developer_greeting(self, name): # [no-self-use]
| ^^^^ PLR6301
8 | print(f"Greetings {name}!")
6 | print(f"Greetings {name}!")
|
no_self_use.py:10:20: PLR6301 Method `greeting_1` could be a function or static method
no_self_use.py:8:20: PLR6301 Method `greeting_1` could be a function or static method
|
6 | print(f"Greetings {name}!")
7 |
8 | def greeting_1(self): # [no-self-use]
| ^^^^ PLR6301
9 | print("Hello!")
|
no_self_use.py:11:20: PLR6301 Method `greeting_2` could be a function or static method
|
8 | print(f"Greetings {name}!")
9 |
10 | def greeting_1(self): # [no-self-use]
9 | print("Hello!")
10 |
11 | def greeting_2(self): # [no-self-use]
| ^^^^ PLR6301
11 | print("Hello!")
12 | print("Hi!")
|
no_self_use.py:13:20: PLR6301 Method `greeting_2` could be a function or static method
no_self_use.py:55:25: PLR6301 Method `abstract_method` could be a function or static method
|
11 | print("Hello!")
12 |
13 | def greeting_2(self): # [no-self-use]
| ^^^^ PLR6301
14 | print("Hi!")
53 | class Sub(Base):
54 | @override
55 | def abstract_method(self):
| ^^^^ PLR6301
56 | print("concrete method")
|

View File

@@ -1,72 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encoding` argument
|
7 | # Errors.
8 | open("test.txt")
| ^^^^ PLW1514
9 | io.TextIOWrapper(io.FileIO("test.txt"))
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
7 | # Errors.
8 | open("test.txt")
9 | io.TextIOWrapper(io.FileIO("test.txt"))
| ^^^^^^^^^^^^^^^^ PLW1514
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
11 | tempfile.NamedTemporaryFile("w")
|
unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
8 | open("test.txt")
9 | io.TextIOWrapper(io.FileIO("test.txt"))
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
| ^^^^^^^^^^^^^^^^^^ PLW1514
11 | tempfile.NamedTemporaryFile("w")
12 | tempfile.TemporaryFile("w")
|
unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
9 | io.TextIOWrapper(io.FileIO("test.txt"))
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
11 | tempfile.NamedTemporaryFile("w")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
12 | tempfile.TemporaryFile("w")
13 | codecs.open("test.txt")
|
unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
11 | tempfile.NamedTemporaryFile("w")
12 | tempfile.TemporaryFile("w")
| ^^^^^^^^^^^^^^^^^^^^^^ PLW1514
13 | codecs.open("test.txt")
14 | tempfile.SpooledTemporaryFile(0, "w")
|
unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explicit `encoding` argument
|
11 | tempfile.NamedTemporaryFile("w")
12 | tempfile.TemporaryFile("w")
13 | codecs.open("test.txt")
| ^^^^^^^^^^^ PLW1514
14 | tempfile.SpooledTemporaryFile(0, "w")
|
unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
12 | tempfile.TemporaryFile("w")
13 | codecs.open("test.txt")
14 | tempfile.SpooledTemporaryFile(0, "w")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
15 |
16 | # Non-errors.
|

View File

@@ -1,214 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
too_many_boolean_expressions.py:11:6: PLR0916 Too many Boolean expressions (6 > 5)
|
9 | elif (a and b) and c and d and e:
10 | ...
11 | elif (a and b) and c and d and e and f:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
12 | ...
13 | elif (a and b) and c and d and e and f and g:
|
too_many_boolean_expressions.py:13:6: PLR0916 Too many Boolean expressions (7 > 5)
|
11 | elif (a and b) and c and d and e and f:
12 | ...
13 | elif (a and b) and c and d and e and f and g:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
14 | ...
15 | elif (a and b) and c and d and e and f and g and h:
|
too_many_boolean_expressions.py:15:6: PLR0916 Too many Boolean expressions (8 > 5)
|
13 | elif (a and b) and c and d and e and f and g:
14 | ...
15 | elif (a and b) and c and d and e and f and g and h:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
16 | ...
17 | elif (a and b) and c and d and e and f and g and h and i:
|
too_many_boolean_expressions.py:17:6: PLR0916 Too many Boolean expressions (9 > 5)
|
15 | elif (a and b) and c and d and e and f and g and h:
16 | ...
17 | elif (a and b) and c and d and e and f and g and h and i:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
18 | ...
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
too_many_boolean_expressions.py:19:6: PLR0916 Too many Boolean expressions (10 > 5)
|
17 | elif (a and b) and c and d and e and f and g and h and i:
18 | ...
19 | elif (a and b) and c and d and e and f and g and h and i and j:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
20 | ...
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
too_many_boolean_expressions.py:21:6: PLR0916 Too many Boolean expressions (11 > 5)
|
19 | elif (a and b) and c and d and e and f and g and h and i and j:
20 | ...
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
22 | ...
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
too_many_boolean_expressions.py:23:6: PLR0916 Too many Boolean expressions (12 > 5)
|
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
22 | ...
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
24 | ...
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
too_many_boolean_expressions.py:25:6: PLR0916 Too many Boolean expressions (13 > 5)
|
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
24 | ...
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
26 | ...
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
too_many_boolean_expressions.py:27:6: PLR0916 Too many Boolean expressions (14 > 5)
|
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
26 | ...
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
28 | ...
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
too_many_boolean_expressions.py:29:6: PLR0916 Too many Boolean expressions (15 > 5)
|
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
28 | ...
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
30 | ...
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
too_many_boolean_expressions.py:31:6: PLR0916 Too many Boolean expressions (16 > 5)
|
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
30 | ...
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
32 | ...
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
too_many_boolean_expressions.py:33:6: PLR0916 Too many Boolean expressions (17 > 5)
|
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
32 | ...
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
34 | ...
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
too_many_boolean_expressions.py:35:6: PLR0916 Too many Boolean expressions (18 > 5)
|
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
34 | ...
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
36 | ...
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
too_many_boolean_expressions.py:37:6: PLR0916 Too many Boolean expressions (19 > 5)
|
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
36 | ...
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
38 | ...
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
too_many_boolean_expressions.py:39:6: PLR0916 Too many Boolean expressions (20 > 5)
|
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
38 | ...
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
40 | ...
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
too_many_boolean_expressions.py:41:6: PLR0916 Too many Boolean expressions (21 > 5)
|
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
40 | ...
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
42 | ...
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
too_many_boolean_expressions.py:43:6: PLR0916 Too many Boolean expressions (22 > 5)
|
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
42 | ...
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
44 | ...
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
too_many_boolean_expressions.py:45:6: PLR0916 Too many Boolean expressions (23 > 5)
|
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
44 | ...
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
46 | ...
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
too_many_boolean_expressions.py:47:6: PLR0916 Too many Boolean expressions (24 > 5)
|
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
46 | ...
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
48 | ...
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
too_many_boolean_expressions.py:49:6: PLR0916 Too many Boolean expressions (25 > 5)
|
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
48 | ...
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
50 | ...
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
too_many_boolean_expressions.py:51:6: PLR0916 Too many Boolean expressions (26 > 5)
|
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
50 | ...
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
52 | ...
53 | else:
|

View File

@@ -1,21 +1,22 @@
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use std::borrow::Cow;
static CURLY_BRACES: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\\N\{[^}]+})|([{}])").unwrap());
pub(super) fn curly_escape(text: &str) -> Cow<'_, str> {
pub(super) fn curly_escape(text: &str) -> String {
// Match all curly braces. This will include named unicode escapes (like
// \N{SNOWMAN}), which we _don't_ want to escape, so take care to preserve them.
CURLY_BRACES.replace_all(text, |caps: &Captures| {
if let Some(match_) = caps.get(1) {
match_.as_str().to_string()
} else {
if &caps[2] == "{" {
"{{".to_string()
CURLY_BRACES
.replace_all(text, |caps: &Captures| {
if let Some(match_) = caps.get(1) {
match_.as_str().to_string()
} else {
"}}".to_string()
if &caps[2] == "{" {
"{{".to_string()
} else {
"}}".to_string()
}
}
}
})
})
.to_string()
}

View File

@@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::str::FromStr;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
@@ -106,7 +105,7 @@ fn simplify_conversion_flag(flags: CConversionFlags) -> String {
}
/// Convert a [`PercentFormat`] struct into a `String`.
fn handle_part(part: &CFormatPart<String>) -> Cow<'_, str> {
fn handle_part(part: &CFormatPart<String>) -> String {
match part {
CFormatPart::Literal(item) => curly_escape(item),
CFormatPart::Spec(spec) => {
@@ -115,7 +114,7 @@ fn handle_part(part: &CFormatPart<String>) -> Cow<'_, str> {
// TODO(charlie): What case is this?
if spec.format_char == '%' {
format_string.push('%');
return Cow::Owned(format_string);
return format_string;
}
format_string.push('{');
@@ -172,25 +171,26 @@ fn handle_part(part: &CFormatPart<String>) -> Cow<'_, str> {
format_string.push(spec.format_char);
}
format_string.push('}');
Cow::Owned(format_string)
format_string
}
}
}
/// Convert a [`CFormatString`] into a `String`.
fn percent_to_format(format_string: &CFormatString) -> String {
format_string
.iter()
.map(|(_, part)| handle_part(part))
.collect()
let mut contents = String::new();
for (.., format_part) in format_string.iter() {
contents.push_str(&handle_part(format_part));
}
contents
}
/// If a tuple has one argument, remove the comma; otherwise, return it as-is.
fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> {
fn clean_params_tuple(right: &Expr, locator: &Locator) -> String {
let mut contents = locator.slice(right).to_string();
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &right {
if elts.len() == 1 {
if !locator.contains_line_break(right.range()) {
let mut contents = locator.slice(right).to_string();
for (i, character) in contents.chars().rev().enumerate() {
if character == ',' {
let correct_index = contents.len() - i - 1;
@@ -198,12 +198,10 @@ fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> {
break;
}
}
return Cow::Owned(contents);
}
}
}
Cow::Borrowed(locator.slice(right))
contents
}
/// Converts a dictionary to a function call while preserving as much styling as
@@ -421,16 +419,16 @@ pub(crate) fn printf_string_formatting(checker: &mut Checker, expr: &Expr, right
// Parse the parameters.
let params_string = match right {
Expr::Constant(_) | Expr::FString(_) => {
Cow::Owned(format!("({})", checker.locator().slice(right)))
format!("({})", checker.locator().slice(right))
}
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_) => {
if num_keyword_arguments > 0 {
// If we have _any_ named fields, assume the right-hand side is a mapping.
Cow::Owned(format!("(**{})", checker.locator().slice(right)))
format!("(**{})", checker.locator().slice(right))
} else if num_positional_arguments > 1 {
// If we have multiple fields, but no named fields, assume the right-hand side is a
// tuple.
Cow::Owned(format!("(*{})", checker.locator().slice(right)))
format!("(*{})", checker.locator().slice(right))
} else {
// Otherwise, if we have a single field, we can't make any assumptions about the
// right-hand side. It _could_ be a tuple, but it could also be a single value,
@@ -446,12 +444,13 @@ pub(crate) fn printf_string_formatting(checker: &mut Checker, expr: &Expr, right
}
Expr::Tuple(_) => clean_params_tuple(right, checker.locator()),
Expr::Dict(_) => {
let Some(params_string) =
if let Some(params_string) =
clean_params_dictionary(right, checker.locator(), checker.stylist())
else {
{
params_string
} else {
return;
};
Cow::Owned(params_string)
}
}
_ => return,
};

View File

@@ -3,8 +3,8 @@ use itertools::Itertools;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::node::AstNode;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::AstNode;
use ruff_python_ast::{self as ast, Arguments, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;

View File

@@ -23,7 +23,7 @@ use crate::rules::{
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade,
};
use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion};
use crate::settings::types::{PerFileIgnore, PythonVersion};
use crate::{codes, RuleSelector};
use super::line_width::{LineLength, TabSize};
@@ -38,7 +38,6 @@ pub mod types;
#[derive(Debug, CacheKey)]
pub struct LinterSettings {
pub exclude: FilePatternSet,
pub project_root: PathBuf,
pub rules: RuleTable,
@@ -132,7 +131,6 @@ impl LinterSettings {
pub fn new(project_root: &Path) -> Self {
Self {
exclude: FilePatternSet::default(),
target_version: PythonVersion::default(),
project_root: project_root.to_path_buf(),
rules: DEFAULT_SELECTORS

View File

@@ -16,7 +16,6 @@ doctest = false
[dependencies]
ruff_python_trivia = { path = "../ruff_python_trivia" }
serde_derive_internals = "0.29.0"
proc-macro2 = { workspace = true }
quote = { workspace = true }

View File

@@ -1,11 +1,11 @@
use proc_macro2::TokenTree;
use quote::{quote, quote_spanned};
use serde_derive_internals::Ctxt;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field,
Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Token, Type, TypePath,
};
use ruff_python_trivia::textwrap::dedent;
@@ -38,14 +38,25 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
.any(|attr| attr.path().is_ident("option_group"))
{
output.push(handle_option_group(field)?);
} else if let Type::Path(ty) = &field.ty {
let serde_field = serde_field_metadata(field)?;
} else if let Some(serde) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("serde"))
{
// If a field has the `serde(flatten)` attribute, flatten the options into the parent
// by calling `Type::record` instead of `visitor.visit_set`
if serde_field.flatten() {
let ty_name = ty.path.require_ident()?;
output.push(quote_spanned!(ident.span() => (#ty_name::record(visit))));
if let (Type::Path(ty), Meta::List(list)) = (&field.ty, &serde.meta) {
for token in list.tokens.clone() {
if let TokenTree::Ident(ident) = token {
if ident == "flatten" {
let ty_name = ty.path.require_ident()?;
output.push(quote_spanned!(
ident.span() => (#ty_name::record(visit))
));
break;
}
}
}
}
}
}
@@ -182,10 +193,6 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
let serde_field = serde_field_metadata(field)?;
let attributed_aliases = serde_field.aliases();
let aliases = quote!(BTreeSet::from_iter([#(#attributed_aliases),*]));
Ok(quote_spanned!(
ident.span() => {
visit.record_field(#kebab_name, crate::options_base::OptionField{
@@ -193,7 +200,6 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
default: &#default,
value_type: &#value_type,
example: &#example,
aliases: #aliases
})
}
))
@@ -242,17 +248,3 @@ fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
}
}
fn serde_field_metadata(field: &Field) -> syn::Result<serde_derive_internals::attr::Field> {
let context = Ctxt::new();
let field = serde_derive_internals::attr::Field::from_ast(
&context,
0,
field,
None,
&serde_derive_internals::attr::Default::Default,
);
context.check()?;
Ok(field)
}

View File

@@ -58,7 +58,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
};
// Map from: linter (e.g., `Flake8Bugbear`) to rule code (e.g.,`"002"`) to rule data (e.g.,
// `(Rule::UnaryPrefixIncrement, RuleGroup::Stable, vec![])`).
// `(Rule::UnaryPrefixIncrement, RuleGroup::Unspecified, vec![])`).
let mut linter_to_rules: BTreeMap<Ident, BTreeMap<String, Rule>> = BTreeMap::new();
for arm in arms {

View File

@@ -1,6 +1,6 @@
use ruff_text_size::{Ranged, TextRange};
use crate::AnyNodeRef;
use crate::node::AnyNodeRef;
use crate::{self as ast, Expr};
/// Unowned pendant to [`ast::Expr`] that stores a reference instead of a owned value.

View File

@@ -8,9 +8,9 @@ use smallvec::SmallVec;
use ruff_text_size::{Ranged, TextRange};
use crate::call_path::CallPath;
use crate::node::AnyNodeRef;
use crate::parenthesize::parenthesized_range;
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
use crate::AnyNodeRef;
use crate::{
self as ast, Arguments, CmpOp, Constant, ExceptHandler, Expr, MatchCase, Pattern, Stmt,
TypeParam,

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