Compare commits
34 Commits
Remove_emp
...
zanie/ecos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
105fb1c682 | ||
|
|
88c0106421 | ||
|
|
f60aa85471 | ||
|
|
d942a777d7 | ||
|
|
8a529925b3 | ||
|
|
dc6b4ad2b4 | ||
|
|
21ea290d6a | ||
|
|
5da0f9111e | ||
|
|
cb6d74c27b | ||
|
|
73049df3ed | ||
|
|
bf0e5788ef | ||
|
|
4113d65836 | ||
|
|
4c2c9bf7e0 | ||
|
|
172ac2c9a2 | ||
|
|
cac9754455 | ||
|
|
134def0119 | ||
|
|
1fabaca5de | ||
|
|
523f542dbd | ||
|
|
ee7575eb5a | ||
|
|
84f7391cc5 | ||
|
|
7da4e28a98 | ||
|
|
5718df638f | ||
|
|
4bb4cd3b37 | ||
|
|
620426de7a | ||
|
|
84ec66a22c | ||
|
|
e58ffa9a7a | ||
|
|
aa6846c78c | ||
|
|
3d03e75a9d | ||
|
|
b6e75e58c9 | ||
|
|
8061894af6 | ||
|
|
e261eb7461 | ||
|
|
bd06cbe0c5 | ||
|
|
ddffadb4b0 | ||
|
|
8255e4ed6c |
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -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.2.0
|
||||
uses: cloudflare/wrangler-action@v3.3.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -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.2.0
|
||||
uses: cloudflare/wrangler-action@v3.3.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
## 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
|
||||
|
||||
115
CHANGELOG.md
Normal file
115
CHANGELOG.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# 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))
|
||||
@@ -170,7 +170,8 @@ 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`).
|
||||
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. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||
|
||||
|
||||
63
Cargo.lock
generated
63
Cargo.lock
generated
@@ -28,9 +28,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1084,9 +1084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
|
||||
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1925,14 +1925,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.6"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.9",
|
||||
"regex-syntax 0.7.5",
|
||||
"regex-automata 0.4.3",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1949,10 +1949,16 @@ 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.7.5",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1967,6 +1973,12 @@ 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"
|
||||
@@ -2039,7 +2051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2176,7 +2188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
@@ -2646,9 +2658,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@@ -3092,11 +3104,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -3105,9 +3116,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3116,9 +3127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -3248,8 +3259,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "1.1.0"
|
||||
source = "git+https://github.com/konstin/unicode_names2?rev=e2ee8155795a13afbea5caa4dbce8d1f93bc26eb#e2ee8155795a13afbea5caa4dbce8d1f93bc26eb"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5506ae2c3c1ccbdf468e52fc5ef536c2ccd981f01273a4cb81aa61021f3a5f"
|
||||
dependencies = [
|
||||
"phf",
|
||||
"unicode_names2_generator",
|
||||
@@ -3257,8 +3269,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2_generator"
|
||||
version = "1.1.0"
|
||||
source = "git+https://github.com/konstin/unicode_names2?rev=e2ee8155795a13afbea5caa4dbce8d1f93bc26eb#e2ee8155795a13afbea5caa4dbce8d1f93bc26eb"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6dfc680313e95bc6637fa278cd7a22390c3c2cd7b8b2bd28755bc6c0fc811e7"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"log",
|
||||
|
||||
@@ -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.33.0", feature = ["filters", "glob"] }
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
@@ -31,7 +31,7 @@ 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.9.6" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
@@ -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.37" }
|
||||
tracing = { version = "0.1.39" }
|
||||
tracing-indicatif = { version = "0.3.4" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode_names2 = { git = "https://github.com/konstin/unicode_names2", rev = "e2ee8155795a13afbea5caa4dbce8d1f93bc26eb" }
|
||||
unicode_names2 = { version = "1.2.0" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
@@ -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.0.292
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -117,16 +117,6 @@ 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>,
|
||||
@@ -507,7 +497,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.or(self.format),
|
||||
output_format: self.output_format,
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -177,14 +177,6 @@ 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`
|
||||
|
||||
@@ -161,7 +161,7 @@ impl Printer {
|
||||
"es"
|
||||
};
|
||||
writeln!(writer,
|
||||
"{} hidden fix{es} can be enabled with the `--unsafe-fixes` option.",
|
||||
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||
fixables.unapplicable_unsafe
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ if condition:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that the legacy `format` option continues to work but emits a warning.
|
||||
/// Since 0.1.0 the legacy format option is no longer supported
|
||||
#[test]
|
||||
fn legacy_format_option() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -156,53 +156,29 @@ format = "json"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
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.
|
||||
"###);
|
||||
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
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
1 hidden fix can be enabled with the `--unsafe-fixes` option.
|
||||
No fixes available (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).
|
||||
1 hidden fix can be enabled with the `--unsafe-fixes` option.
|
||||
No fixes available (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.
|
||||
1 hidden fix can be enabled with the `--unsafe-fixes` option.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
2 hidden fixes can be enabled with the `--unsafe-fixes` option.
|
||||
No fixes available (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.
|
||||
2 hidden fixes can be enabled with the `--unsafe-fixes` option.
|
||||
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
@@ -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(name = "Printer::print", skip_all)]
|
||||
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
|
||||
pub fn print_with_indent(
|
||||
mut self,
|
||||
document: &'a Document,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
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.1" }
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
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.19" }
|
||||
semver = { version = "1.0.20" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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:
|
||||
...
|
||||
@@ -22,3 +22,10 @@ class C:
|
||||
|
||||
class D(C):
|
||||
x: UUID
|
||||
|
||||
|
||||
import collections
|
||||
|
||||
|
||||
class E(BaseModel[int]):
|
||||
x: collections.Awaitable
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def with_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
def ends_in_quote():
|
||||
'Sum\\mary."'
|
||||
|
||||
|
||||
def contains_quote():
|
||||
'Sum"\\mary.'
|
||||
@@ -63,6 +63,15 @@ 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
|
||||
...
|
||||
|
||||
@@ -6,6 +6,14 @@ 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
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Errors
|
||||
1 in [1, 2, 3]
|
||||
1 in (1, 2, 3)
|
||||
1 in (
|
||||
1, 2, 3
|
||||
)
|
||||
|
||||
# OK
|
||||
fruits = ["cherry", "grapes"]
|
||||
"cherry" in fruits
|
||||
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# 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]
|
||||
@@ -1,5 +1,7 @@
|
||||
import abc
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
class Person:
|
||||
def developer_greeting(self, name): # [no-self-use]
|
||||
@@ -60,3 +62,24 @@ 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():
|
||||
...
|
||||
|
||||
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
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:
|
||||
...
|
||||
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
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, )
|
||||
@@ -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, &mut diagnostics);
|
||||
pylint::rules::no_self_use(checker, scope_id, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,6 +786,9 @@ 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,
|
||||
@@ -1194,6 +1197,9 @@ 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);
|
||||
}
|
||||
|
||||
@@ -964,7 +964,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Raise(ast::StmtRaise { exc, .. }) => {
|
||||
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
|
||||
if checker.enabled(Rule::RaiseNotImplemented) {
|
||||
if let Some(expr) = exc {
|
||||
pyflakes::rules::raise_not_implemented(checker, expr);
|
||||
@@ -1004,6 +1004,9 @@ 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) {
|
||||
@@ -1067,6 +1070,9 @@ 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,
|
||||
|
||||
@@ -524,6 +524,7 @@ 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());
|
||||
|
||||
@@ -562,6 +563,7 @@ 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) {
|
||||
@@ -580,7 +582,9 @@ where
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
self.visit_expr(value);
|
||||
// The value in a `type` alias has annotation semantics, in that it's never
|
||||
// evaluated at runtime.
|
||||
self.visit_annotation(value);
|
||||
self.semantic.pop_scope();
|
||||
self.visit_expr(name);
|
||||
}
|
||||
@@ -1766,7 +1770,7 @@ impl<'a> Checker<'a> {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.visit_expr(bound);
|
||||
self.visit_annotation(bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
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
|
||||
@@ -32,7 +31,7 @@ pub struct UnnecessaryLiteralUnion {
|
||||
members: Vec<String>,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for UnnecessaryLiteralUnion {
|
||||
impl Violation for UnnecessaryLiteralUnion {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
@@ -40,17 +39,13 @@ impl AlwaysFixableViolation 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") {
|
||||
@@ -59,28 +54,21 @@ 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 literal_members: Vec<String> = literal_exprs
|
||||
.into_iter()
|
||||
.map(|expr| checker.locator().slice(expr.as_ref()).to_string())
|
||||
.collect();
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
let diagnostic = Diagnostic::new(
|
||||
UnnecessaryLiteralUnion {
|
||||
members: literal_members.clone(),
|
||||
members: literal_exprs
|
||||
.into_iter()
|
||||
.map(|expr| checker.locator().slice(expr.as_ref()).to_string())
|
||||
.collect(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("Literal[{}]", literal_members.join(", ")),
|
||||
expr.range(),
|
||||
)));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,57 +9,24 @@ PYI030.py:9:9: PYI030 [*] Multiple literal members in a union. Use a single lite
|
||||
10 |
|
||||
11 | # Should emit for union types in arguments.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -67,19 +34,8 @@ PYI030.py:22:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -88,19 +44,8 @@ PYI030.py:23:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -108,19 +53,8 @@ PYI030.py:24:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -129,19 +63,8 @@ PYI030.py:25:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
26 |
|
||||
27 | # Should emit for non-type unions.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -149,19 +72,8 @@ PYI030.py:28:10: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
29 |
|
||||
30 | # Should emit for parenthesized unions.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -169,38 +81,16 @@ PYI030.py:31:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
32 |
|
||||
33 | # Should handle user parentheses when fixing.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -209,31 +99,12 @@ PYI030.py:35:10: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
36 |
|
||||
37 | # Should emit for union in generic parent type.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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,57 +9,24 @@ PYI030.pyi:9:9: PYI030 [*] Multiple literal members in a union. Use a single lit
|
||||
10 |
|
||||
11 | # Should emit for union types in arguments.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -67,19 +34,8 @@ PYI030.pyi:22:9: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
23 | field4: str | Literal[1] | Literal[2] # Error
|
||||
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -88,19 +44,8 @@ PYI030.pyi:23:9: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
24 | field5: Literal[1] | str | Literal[2] # Error
|
||||
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -108,19 +53,8 @@ PYI030.pyi:24:9: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
||||
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -129,19 +63,8 @@ PYI030.pyi:25:9: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
26 |
|
||||
27 | # Should emit for non-type unions.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -149,19 +72,8 @@ PYI030.pyi:28:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
29 |
|
||||
30 | # Should emit for parenthesized unions.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -169,38 +81,16 @@ PYI030.pyi:31:9: PYI030 [*] Multiple literal members in a union. Use a single li
|
||||
32 |
|
||||
33 | # Should handle user parentheses when fixing.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -209,19 +99,8 @@ PYI030.pyi:35:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
36 |
|
||||
37 | # Should emit for union in generic parent type.
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -229,38 +108,16 @@ PYI030.pyi:38:15: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
39 |
|
||||
40 | # Should emit for unions with more than two cases
|
||||
|
|
||||
= 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
|
||||
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]`
|
||||
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`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -269,19 +126,8 @@ PYI030.pyi:42:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
43 |
|
||||
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -289,19 +135,8 @@ PYI030.pyi:45:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
46 |
|
||||
47 | # Should emit for unions with mixed literal internal types
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -309,19 +144,8 @@ PYI030.pyi:48:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
49 |
|
||||
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -329,19 +153,8 @@ PYI030.pyi:51:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
52 |
|
||||
53 | # Shouldn't emit if in new parent type
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -349,19 +162,8 @@ PYI030.pyi:60:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
61 |
|
||||
62 | # Should emit in cases with newlines
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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[
|
||||
@@ -375,24 +177,8 @@ PYI030.pyi:63:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
69 |
|
||||
70 | # Should handle multiple unions with multiple members
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -400,19 +186,8 @@ PYI030.pyi:71:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
72 |
|
||||
73 | # Should emit in cases with `typing.Union` instead of `|`
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -420,19 +195,8 @@ PYI030.pyi:74:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
75 |
|
||||
76 | # Should emit in cases with `typing_extensions.Literal`
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -440,19 +204,8 @@ PYI030.pyi:77:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
78 |
|
||||
79 | # Should emit in cases with nested `typing.Union`
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -460,19 +213,8 @@ PYI030.pyi:80:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
81 |
|
||||
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
@@ -480,31 +222,12 @@ PYI030.pyi:83:10: PYI030 [*] Multiple literal members in a union. Use a single l
|
||||
84 |
|
||||
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
|
|
||||
= help: Replace with a single `Literal`
|
||||
|
||||
ℹ 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]`
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_python_ast::call_path::from_qualified_name;
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::helpers::{map_callable, map_subscript};
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind, SemanticModel};
|
||||
|
||||
pub(crate) fn is_valid_runtime_import(binding: &Binding, semantic: &SemanticModel) -> bool {
|
||||
@@ -40,11 +40,13 @@ fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticMode
|
||||
};
|
||||
|
||||
class_def.bases().iter().any(|base| {
|
||||
semantic.resolve_call_path(base).is_some_and(|call_path| {
|
||||
base_classes
|
||||
.iter()
|
||||
.any(|base_class| from_qualified_name(base_class) == call_path)
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ 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"))]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
|
||||
@@ -87,6 +87,7 @@ 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(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_codegen::Quote;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -37,6 +37,8 @@ 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;
|
||||
@@ -45,12 +47,25 @@ 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 {
|
||||
@@ -60,18 +75,34 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
|
||||
match expected_quote {
|
||||
Quote::Single => {
|
||||
if !leading_quote.ends_with("'''") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
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);
|
||||
}
|
||||
}
|
||||
Quote::Double => {
|
||||
if !leading_quote.ends_with("\"\"\"") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,102 @@
|
||||
---
|
||||
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
|
||||
|
||||
D.py:312:5: D300 Use 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 `"""`
|
||||
|
|
||||
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
|
||||
|
||||
D.py:317:5: D300 Use 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 `"""`
|
||||
|
|
||||
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
316 | def single_quotes_raw():
|
||||
317 | r'Summary.'
|
||||
| ^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:322:5: D300 Use 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 `"""`
|
||||
|
|
||||
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
|
||||
|
||||
D.py:328:5: D300 Use 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 `"""`
|
||||
|
|
||||
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
|
||||
|
||||
D.py:645:5: D300 Use 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 `"""`
|
||||
|
|
||||
644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
645 | "\
|
||||
@@ -51,8 +106,21 @@ D.py:645:5: D300 Use triple double quotes `"""`
|
||||
647 |
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:649:5: D300 Use 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 `"""`
|
||||
|
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
@@ -60,15 +128,37 @@ D.py:649:5: D300 Use triple double quotes `"""`
|
||||
650 | def sort_services(self):
|
||||
651 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:654:5: D300 Use 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 `"""`
|
||||
|
|
||||
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
|
||||
|
||||
D.py:658:5: D300 Use 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 `"""`
|
||||
|
|
||||
657 | class CommentAfterDocstring:
|
||||
658 | "After this docstring there's a comment." # priorities=1
|
||||
@@ -76,8 +166,19 @@ D.py:658:5: D300 Use triple double quotes `"""`
|
||||
659 | def sort_services(self):
|
||||
660 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:664:5: D300 Use 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 `"""`
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
@@ -85,5 +186,15 @@ 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"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
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."""
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
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 """
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings) -> bool {
|
||||
pub(super) fn in_dunder_method(
|
||||
dunder_name: &str,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
let scope = semantic.current_scope();
|
||||
let ScopeKind::Function(ast::StmtFunctionDef {
|
||||
name,
|
||||
@@ -32,7 +36,7 @@ pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if name != "__init__" {
|
||||
if name != dunder_name {
|
||||
return false;
|
||||
}
|
||||
let Some(parent) = semantic.first_non_type_parent_scope(scope) else {
|
||||
|
||||
@@ -133,8 +133,11 @@ 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(
|
||||
@@ -228,6 +231,22 @@ 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(
|
||||
|
||||
@@ -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) {
|
||||
if is_known_dunder_method(&method.name) || matches!(method.name.as_str(), "_") {
|
||||
return false;
|
||||
}
|
||||
method.name.starts_with('_') && method.name.ends_with('_')
|
||||
@@ -196,5 +196,13 @@ 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_"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,7 +36,7 @@ impl AlwaysFixableViolation for IterationOverSet {
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Use a sequence type instead of a `set` when iterating over values")
|
||||
format!("Convert to `tuple`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +52,14 @@ pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
|
||||
|
||||
let mut diagnostic = Diagnostic::new(IterationOverSet, 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(),
|
||||
)));
|
||||
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())));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
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
|
||||
/// - [What’s 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);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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()));
|
||||
}
|
||||
@@ -24,10 +24,12 @@ 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::*;
|
||||
@@ -43,6 +45,7 @@ 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::*;
|
||||
@@ -52,6 +55,7 @@ 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::*;
|
||||
@@ -84,10 +88,12 @@ 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;
|
||||
@@ -103,6 +109,7 @@ 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;
|
||||
@@ -112,6 +119,7 @@ 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;
|
||||
|
||||
@@ -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, ScopeKind,
|
||||
Scope, ScopeId, ScopeKind,
|
||||
};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -45,7 +45,12 @@ impl Violation for NoSelfUse {
|
||||
}
|
||||
|
||||
/// PLR6301
|
||||
pub(crate) fn no_self_use(checker: &Checker, scope: &Scope, diagnostics: &mut Vec<Diagnostic>) {
|
||||
pub(crate) fn no_self_use(
|
||||
checker: &Checker,
|
||||
scope_id: ScopeId,
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
@@ -105,11 +110,28 @@ pub(crate) fn no_self_use(checker: &Checker, scope: &Scope, diagnostics: &mut Ve
|
||||
return;
|
||||
};
|
||||
|
||||
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())
|
||||
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())
|
||||
{
|
||||
diagnostics.push(Diagnostic::new(
|
||||
NoSelfUse {
|
||||
|
||||
@@ -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_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## 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_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ReturnInInit, stmt.range()));
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
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>()
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## 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_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(YieldInInit, expr.range()));
|
||||
|
||||
@@ -40,6 +40,7 @@ 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,
|
||||
@@ -51,6 +52,7 @@ 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,
|
||||
|
||||
@@ -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: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ 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: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
3 3 | for item in {1}:
|
||||
@@ -38,90 +38,136 @@ 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 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 9 | for item in {1,}:
|
||||
|
||||
iteration_over_set.py:9:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:9:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
7 | print(f"I like {item}.")
|
||||
8 |
|
||||
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
|
||||
9 | for item in {1,}:
|
||||
| ^^^^ PLC0208
|
||||
10 | print(f"I can count to {item}!")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
6 6 | for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
7 7 | print(f"I like {item}.")
|
||||
8 8 |
|
||||
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 |
|
||||
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 {
|
||||
|
||||
iteration_over_set.py:11:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:12:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
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
|
||||
12 |
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
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: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
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:13:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
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
|
||||
14 |
|
||||
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
|
||||
ℹ Fix
|
||||
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
|
||||
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:15:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:17:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
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
|
||||
15 | print(f"I like {item}.")
|
||||
16 |
|
||||
17 | # Non-errors
|
||||
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: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
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
|
||||
14 14 | }: # flags in-line set literals
|
||||
15 15 | print(f"I like {item}.")
|
||||
16 16 |
|
||||
17 17 | # Non-errors
|
||||
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
|
||||
| ^^^^^^^^^ PLC0208
|
||||
20 |
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ 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 |
|
||||
|
||||
iteration_over_set.py:21: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
|
||||
| ^^^^^^^^^ PLC0208
|
||||
22 |
|
||||
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ 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 |
|
||||
|
||||
iteration_over_set.py:23: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
|
||||
| ^^^^^^^^^ PLC0208
|
||||
24 |
|
||||
25 | # Non-errors
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ 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 |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
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
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
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"]
|
||||
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
no_self_use.py:5:28: PLR6301 Method `developer_greeting` could be a function or static method
|
||||
no_self_use.py:7:28: PLR6301 Method `developer_greeting` could be a function or static method
|
||||
|
|
||||
4 | class Person:
|
||||
5 | def developer_greeting(self, name): # [no-self-use]
|
||||
6 | class Person:
|
||||
7 | def developer_greeting(self, name): # [no-self-use]
|
||||
| ^^^^ PLR6301
|
||||
6 | print(f"Greetings {name}!")
|
||||
8 | print(f"Greetings {name}!")
|
||||
|
|
||||
|
||||
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
|
||||
no_self_use.py:10:20: PLR6301 Method `greeting_1` could be a function or static method
|
||||
|
|
||||
9 | print("Hello!")
|
||||
10 |
|
||||
11 | def greeting_2(self): # [no-self-use]
|
||||
8 | print(f"Greetings {name}!")
|
||||
9 |
|
||||
10 | def greeting_1(self): # [no-self-use]
|
||||
| ^^^^ PLR6301
|
||||
12 | print("Hi!")
|
||||
11 | print("Hello!")
|
||||
|
|
||||
|
||||
no_self_use.py:55:25: PLR6301 Method `abstract_method` could be a function or static method
|
||||
no_self_use.py:13:20: PLR6301 Method `greeting_2` could be a function or static method
|
||||
|
|
||||
53 | class Sub(Base):
|
||||
54 | @override
|
||||
55 | def abstract_method(self):
|
||||
| ^^^^ PLR6301
|
||||
56 | print("concrete method")
|
||||
11 | print("Hello!")
|
||||
12 |
|
||||
13 | def greeting_2(self): # [no-self-use]
|
||||
| ^^^^ PLR6301
|
||||
14 | print("Hi!")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
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.
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
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:
|
||||
|
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
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) -> String {
|
||||
pub(super) fn curly_escape(text: &str) -> Cow<'_, str> {
|
||||
// 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()
|
||||
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()
|
||||
} else {
|
||||
if &caps[2] == "{" {
|
||||
"{{".to_string()
|
||||
} else {
|
||||
"}}".to_string()
|
||||
}
|
||||
"}}".to_string()
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
@@ -105,7 +106,7 @@ fn simplify_conversion_flag(flags: CConversionFlags) -> String {
|
||||
}
|
||||
|
||||
/// Convert a [`PercentFormat`] struct into a `String`.
|
||||
fn handle_part(part: &CFormatPart<String>) -> String {
|
||||
fn handle_part(part: &CFormatPart<String>) -> Cow<'_, str> {
|
||||
match part {
|
||||
CFormatPart::Literal(item) => curly_escape(item),
|
||||
CFormatPart::Spec(spec) => {
|
||||
@@ -114,7 +115,7 @@ fn handle_part(part: &CFormatPart<String>) -> String {
|
||||
// TODO(charlie): What case is this?
|
||||
if spec.format_char == '%' {
|
||||
format_string.push('%');
|
||||
return format_string;
|
||||
return Cow::Owned(format_string);
|
||||
}
|
||||
|
||||
format_string.push('{');
|
||||
@@ -171,26 +172,25 @@ fn handle_part(part: &CFormatPart<String>) -> String {
|
||||
format_string.push(spec.format_char);
|
||||
}
|
||||
format_string.push('}');
|
||||
format_string
|
||||
Cow::Owned(format_string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`CFormatString`] into a `String`.
|
||||
fn percent_to_format(format_string: &CFormatString) -> String {
|
||||
let mut contents = String::new();
|
||||
for (.., format_part) in format_string.iter() {
|
||||
contents.push_str(&handle_part(format_part));
|
||||
}
|
||||
contents
|
||||
format_string
|
||||
.iter()
|
||||
.map(|(_, part)| handle_part(part))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// If a tuple has one argument, remove the comma; otherwise, return it as-is.
|
||||
fn clean_params_tuple(right: &Expr, locator: &Locator) -> String {
|
||||
let mut contents = locator.slice(right).to_string();
|
||||
fn clean_params_tuple<'a>(right: &Expr, locator: &Locator<'a>) -> Cow<'a, str> {
|
||||
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,10 +198,12 @@ fn clean_params_tuple(right: &Expr, locator: &Locator) -> String {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Cow::Owned(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
contents
|
||||
|
||||
Cow::Borrowed(locator.slice(right))
|
||||
}
|
||||
|
||||
/// Converts a dictionary to a function call while preserving as much styling as
|
||||
@@ -419,16 +421,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(_) => {
|
||||
format!("({})", checker.locator().slice(right))
|
||||
Cow::Owned(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.
|
||||
format!("(**{})", checker.locator().slice(right))
|
||||
Cow::Owned(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.
|
||||
format!("(*{})", checker.locator().slice(right))
|
||||
Cow::Owned(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,
|
||||
@@ -444,13 +446,12 @@ pub(crate) fn printf_string_formatting(checker: &mut Checker, expr: &Expr, right
|
||||
}
|
||||
Expr::Tuple(_) => clean_params_tuple(right, checker.locator()),
|
||||
Expr::Dict(_) => {
|
||||
if let Some(params_string) =
|
||||
let Some(params_string) =
|
||||
clean_params_dictionary(right, checker.locator(), checker.stylist())
|
||||
{
|
||||
params_string
|
||||
} else {
|
||||
else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
Cow::Owned(params_string)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -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::Unspecified, vec![])`).
|
||||
// `(Rule::UnaryPrefixIncrement, RuleGroup::Stable, vec![])`).
|
||||
let mut linter_to_rules: BTreeMap<Ident, BTreeMap<String, Rule>> = BTreeMap::new();
|
||||
|
||||
for arm in arms {
|
||||
|
||||
7
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/number.py
vendored
Normal file
7
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/number.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.1
|
||||
1.
|
||||
1E+1
|
||||
1E-1
|
||||
1.E+1
|
||||
1.0E+1
|
||||
1.1E+1
|
||||
@@ -4,7 +4,7 @@ use ruff_python_ast::UnaryOp;
|
||||
|
||||
use crate::comments::{trailing_comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -57,7 +57,14 @@ impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
|
||||
operand.format().fmt(f)
|
||||
if operand
|
||||
.as_bin_op_expr()
|
||||
.is_some_and(|bin_op| bin_op.op.is_pow())
|
||||
{
|
||||
operand.format().with_options(Parentheses::Always).fmt(f)
|
||||
} else {
|
||||
operand.format().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
|
||||
@@ -142,7 +142,7 @@ fn normalize_floating_number(input: &str) -> Cow<str> {
|
||||
|
||||
let mut chars = input.char_indices();
|
||||
|
||||
let fraction_ends_with_dot = if let Some((index, '.')) = chars.next() {
|
||||
let mut prev_char_is_dot = if let Some((index, '.')) = chars.next() {
|
||||
// Add a leading `0` if `input` starts with `.`.
|
||||
output.push('0');
|
||||
output.push('.');
|
||||
@@ -155,8 +155,8 @@ fn normalize_floating_number(input: &str) -> Cow<str> {
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some((index, c @ ('e' | 'E'))) => {
|
||||
if fraction_ends_with_dot {
|
||||
// Add `0` if fraction part ends with `.`.
|
||||
if prev_char_is_dot {
|
||||
// Add `0` if the `e` immediately follows a `.` (e.g., `1.e1`).
|
||||
output.push_str(&input[last_index..index]);
|
||||
output.push('0');
|
||||
last_index = index;
|
||||
@@ -177,9 +177,12 @@ fn normalize_floating_number(input: &str) -> Cow<str> {
|
||||
|
||||
break;
|
||||
}
|
||||
Some(_) => continue,
|
||||
Some((_index, c)) => {
|
||||
prev_char_is_dot = c == '.';
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
if input.ends_with('.') {
|
||||
if prev_char_is_dot {
|
||||
// Add `0` if fraction part ends with `.`.
|
||||
output.push_str(&input[last_index..]);
|
||||
output.push('0');
|
||||
|
||||
@@ -266,15 +266,6 @@ last_call()
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -31,7 +31,7 @@
|
||||
-1
|
||||
~int and not v1 ^ 123 + v2 | True
|
||||
(~int) and (not ((v1 ^ (123 + v2)) | True))
|
||||
-+(really ** -(confusing ** ~(operator**-precedence)))
|
||||
++really ** -confusing ** ~operator**-precedence
|
||||
flags & ~select.EPOLLIN and waiters.write_task is not None
|
||||
lambda arg: None
|
||||
lambda a=True: a
|
||||
@@ -115,7 +115,7 @@
|
||||
arg,
|
||||
another,
|
||||
@@ -322,7 +313,7 @@ not great
|
||||
-1
|
||||
~int and not v1 ^ 123 + v2 | True
|
||||
(~int) and (not ((v1 ^ (123 + v2)) | True))
|
||||
+really ** -confusing ** ~operator**-precedence
|
||||
+(really ** -(confusing ** ~(operator**-precedence)))
|
||||
flags & ~select.EPOLLIN and waiters.write_task is not None
|
||||
lambda arg: None
|
||||
lambda a=True: a
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/number.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
.1
|
||||
1.
|
||||
1E+1
|
||||
1E-1
|
||||
1.E+1
|
||||
1.0E+1
|
||||
1.1E+1
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
0.1
|
||||
1.0
|
||||
1e1
|
||||
1e-1
|
||||
1.0e1
|
||||
1.0e1
|
||||
1.1e1
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{Locator, SourceLocation};
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_workspace::configuration::Configuration;
|
||||
use ruff_workspace::options::{FormatOptions, FormatOrOutputFormat, LintOptions, Options};
|
||||
use ruff_workspace::options::{FormatOptions, LintOptions, Options};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
@@ -140,11 +140,11 @@ impl Workspace {
|
||||
|
||||
..LintOptions::default()
|
||||
}),
|
||||
format: Some(FormatOrOutputFormat::Format(FormatOptions {
|
||||
format: Some(FormatOptions {
|
||||
indent_style: Some(IndentStyle::Space),
|
||||
quote_style: Some(QuoteStyle::Double),
|
||||
..FormatOptions::default()
|
||||
})),
|
||||
}),
|
||||
..Options::default()
|
||||
})
|
||||
.map_err(into_error)
|
||||
|
||||
@@ -39,9 +39,9 @@ use crate::options::{
|
||||
Flake8ComprehensionsOptions, Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions,
|
||||
Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions,
|
||||
Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions,
|
||||
Flake8UnusedArgumentsOptions, FormatOptions, FormatOrOutputFormat, IsortOptions, LintOptions,
|
||||
McCabeOptions, Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions,
|
||||
PydocstyleOptions, PyflakesOptions, PylintOptions,
|
||||
Flake8UnusedArgumentsOptions, FormatOptions, IsortOptions, LintOptions, McCabeOptions, Options,
|
||||
Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions, PyflakesOptions,
|
||||
PylintOptions,
|
||||
};
|
||||
use crate::settings::{
|
||||
FileResolverSettings, FormatterSettings, LineEnding, Settings, EXCLUDE, INCLUDE,
|
||||
@@ -377,7 +377,7 @@ impl Configuration {
|
||||
.cache_dir
|
||||
.map(|dir| {
|
||||
let dir = shellexpand::full(&dir);
|
||||
dir.map(|dir| PathBuf::from(dir.as_ref()))
|
||||
dir.map(|dir| fs::normalize_path_to(dir.as_ref(), project_root))
|
||||
})
|
||||
.transpose()
|
||||
.map_err(|e| anyhow!("Invalid `cache-dir` value: {e}"))?,
|
||||
@@ -435,12 +435,7 @@ impl Configuration {
|
||||
fix: options.fix,
|
||||
fix_only: options.fix_only,
|
||||
unsafe_fixes: options.unsafe_fixes.map(UnsafeFixes::from),
|
||||
output_format: options.output_format.or_else(|| {
|
||||
options
|
||||
.format
|
||||
.as_ref()
|
||||
.and_then(FormatOrOutputFormat::as_output_format)
|
||||
}),
|
||||
output_format: options.output_format,
|
||||
force_exclude: options.force_exclude,
|
||||
line_length: options.line_length,
|
||||
tab_size: options.tab_size,
|
||||
@@ -460,11 +455,7 @@ impl Configuration {
|
||||
target_version: options.target_version,
|
||||
|
||||
lint: LintConfiguration::from_options(lint, project_root)?,
|
||||
format: if let Some(FormatOrOutputFormat::Format(format)) = options.format {
|
||||
FormatConfiguration::from_options(format)?
|
||||
} else {
|
||||
FormatConfiguration::default()
|
||||
},
|
||||
format: FormatConfiguration::from_options(options.format.unwrap_or_default())?,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ use ruff_linter::{warn_user_once, RuleSelector};
|
||||
use ruff_macros::{CombineOptions, OptionsMetadata};
|
||||
use ruff_python_formatter::QuoteStyle;
|
||||
|
||||
use crate::options_base::{OptionsMetadata, Visit};
|
||||
use crate::settings::LineEnding;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)]
|
||||
@@ -380,19 +379,9 @@ pub struct Options {
|
||||
#[serde(flatten)]
|
||||
pub lint_top_level: LintOptions,
|
||||
|
||||
/// Options to configure the code formatting.
|
||||
///
|
||||
/// Previously:
|
||||
/// The style in which violation messages should be formatted: `"text"`
|
||||
/// (default), `"grouped"` (group messages by file), `"json"`
|
||||
/// (machine-readable), `"junit"` (machine-readable XML), `"github"` (GitHub
|
||||
/// Actions annotations), `"gitlab"` (GitLab CI code quality report),
|
||||
/// `"pylint"` (Pylint text format) or `"azure"` (Azure Pipeline logging commands).
|
||||
///
|
||||
/// This option has been **deprecated** in favor of `output-format`
|
||||
/// to avoid ambiguity with Ruff's upcoming formatter.
|
||||
/// Options to configure code formatting.
|
||||
#[option_group]
|
||||
pub format: Option<FormatOrOutputFormat>,
|
||||
pub format: Option<FormatOptions>,
|
||||
}
|
||||
|
||||
/// Experimental section to configure Ruff's linting. This new section will eventually
|
||||
@@ -2390,6 +2379,11 @@ pub struct PylintOptions {
|
||||
example = r"max-public-methods = 20"
|
||||
)]
|
||||
pub max_public_methods: Option<usize>,
|
||||
|
||||
/// Maximum number of Boolean expressions allowed within a single `if` statement
|
||||
/// (see: `PLR0916`).
|
||||
#[option(default = r"5", value_type = "int", example = r"max-bool-expr = 5")]
|
||||
pub max_bool_expr: Option<usize>,
|
||||
}
|
||||
|
||||
impl PylintOptions {
|
||||
@@ -2400,6 +2394,7 @@ impl PylintOptions {
|
||||
.allow_magic_value_types
|
||||
.unwrap_or(defaults.allow_magic_value_types),
|
||||
max_args: self.max_args.unwrap_or(defaults.max_args),
|
||||
max_bool_expr: self.max_bool_expr.unwrap_or(defaults.max_bool_expr),
|
||||
max_returns: self.max_returns.unwrap_or(defaults.max_returns),
|
||||
max_branches: self.max_branches.unwrap_or(defaults.max_branches),
|
||||
max_statements: self.max_statements.unwrap_or(defaults.max_statements),
|
||||
@@ -2465,38 +2460,11 @@ impl PyUpgradeOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum FormatOrOutputFormat {
|
||||
Format(FormatOptions),
|
||||
OutputFormat(SerializationFormat),
|
||||
}
|
||||
|
||||
impl FormatOrOutputFormat {
|
||||
pub const fn as_output_format(&self) -> Option<SerializationFormat> {
|
||||
match self {
|
||||
FormatOrOutputFormat::Format(_) => None,
|
||||
FormatOrOutputFormat::OutputFormat(format) => Some(*format),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsMetadata for FormatOrOutputFormat {
|
||||
fn record(visit: &mut dyn Visit) {
|
||||
FormatOptions::record(visit);
|
||||
}
|
||||
|
||||
fn documentation() -> Option<&'static str> {
|
||||
FormatOptions::documentation()
|
||||
}
|
||||
}
|
||||
|
||||
/// Experimental: Configures how `ruff format` formats your code.
|
||||
///
|
||||
/// Please provide feedback in [this discussion](https://github.com/astral-sh/ruff/discussions/7310).
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions,
|
||||
Debug, PartialEq, Eq, Default, Deserialize, Serialize, OptionsMetadata, CombineOptions,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
@@ -2541,7 +2509,7 @@ pub struct FormatOptions {
|
||||
/// ```
|
||||
///
|
||||
/// Ruff will change `a` to use single quotes when using `quote-style = "single"`. However,
|
||||
/// `a` will be unchanged, as converting to single quotes would require the inner `'` to be
|
||||
/// `b` will be unchanged, as converting to single quotes would require the inner `'` to be
|
||||
/// escaped, which leads to less readable code: `'It\'s monday morning'`.
|
||||
#[option(
|
||||
default = r#"double"#,
|
||||
|
||||
@@ -13,11 +13,10 @@ use log::debug;
|
||||
use path_absolutize::path_dedot;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::packaging::is_package;
|
||||
use ruff_linter::{fs, warn_user_once};
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::options::FormatOrOutputFormat;
|
||||
use crate::pyproject;
|
||||
use crate::pyproject::settings_toml;
|
||||
use crate::settings::Settings;
|
||||
@@ -221,10 +220,6 @@ fn resolve_configuration(
|
||||
let options = pyproject::load_options(&path)
|
||||
.map_err(|err| anyhow!("Failed to parse `{}`: {}", path.display(), err))?;
|
||||
|
||||
if matches!(options.format, Some(FormatOrOutputFormat::OutputFormat(_))) {
|
||||
warn_user_once!("The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.");
|
||||
}
|
||||
|
||||
let project_root = relativity.resolve(&path);
|
||||
let configuration = Configuration::from_options(options, &project_root)?;
|
||||
|
||||
|
||||
@@ -109,8 +109,6 @@ If you're wondering how to configure Ruff, here are some **recommended guideline
|
||||
- Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example,
|
||||
you might consider expanding to `select = ["E", "F", "B"]` to enable the popular flake8-bugbear
|
||||
extension.
|
||||
- By default, Ruff's fixes are aggressive. If you find that it's too aggressive for your liking,
|
||||
consider turning off fixes for specific rules or categories (see [_FAQ_](faq.md#ruff-tried-to-fix-something--but-it-broke-my-code)).
|
||||
|
||||
## Using `ruff.toml`
|
||||
|
||||
@@ -430,7 +428,7 @@ Ruff only enables safe fixes by default. Unsafe fixes can be enabled by settings
|
||||
ruff check . --unsafe-fixes
|
||||
|
||||
# Apply unsafe fixes
|
||||
ruff check . --fix --unsafe-fixes
|
||||
ruff check . --fix --unsafe-fixes
|
||||
```
|
||||
|
||||
The safety of fixes can be adjusted per rule using the [`extend-safe-fixes`](settings.md#extend-safe-fixes) and [`extend-unsafe-fixes`](settings.md#extend-unsafe-fixes) settings.
|
||||
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
# Include `--format=github` to enable automatic inline annotations.
|
||||
# Update output format to enable automatic inline annotations.
|
||||
- name: Run Ruff
|
||||
run: ruff check --format=github .
|
||||
run: ruff check --output-format=github .
|
||||
```
|
||||
|
||||
21
docs/faq.md
21
docs/faq.md
@@ -436,22 +436,13 @@ For more, see the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) c
|
||||
|
||||
## Ruff tried to fix something — but it broke my code?
|
||||
|
||||
Ruff's fixes are a best-effort mechanism. Given the dynamic nature of Python, it's difficult to
|
||||
have _complete_ certainty when making changes to code, even for the seemingly trivial fixes.
|
||||
Ruff labels fixes as "safe" and "unsafe". By default, Ruff will fix all violations for which safe
|
||||
fixes are available, while unsafe fixes can be enabled via the [`unsafe-fixes`](settings.md#unsafe-fixes)
|
||||
setting, or passing the `--unsafe-fixes` flag to `ruff check`. For more, see [the fix documentation](configuration.md#fixes).
|
||||
|
||||
In the future, Ruff will support enabling fix behavior based on the safety of the patch.
|
||||
|
||||
In the meantime, if you find that the fixes are too aggressive, you can disable it on a per-rule or
|
||||
per-category basis using the [`unfixable`](settings.md#unfixable) mechanic.
|
||||
For example, to disable the fix for some possibly-unsafe rules, you could add the following to your
|
||||
`pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
unfixable = ["B", "SIM", "TRY", "RUF"]
|
||||
```
|
||||
|
||||
If you find a case where Ruff's fix breaks your code, please file an Issue!
|
||||
Even still, given the dynamic nature of Python, it's difficult to have _complete_ certainty when
|
||||
making changes to code, even for seemingly trivial fixes. If a "safe" fix breaks your code, please
|
||||
[file an Issue](https://github.com/astral-sh/ruff/issues/new).
|
||||
|
||||
## How can I disable Ruff's color output?
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.292
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -23,7 +23,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.0.292
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -33,7 +33,7 @@ Or, to enable fixes:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.292
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [ --fix, --exit-non-zero-on-fix ]
|
||||
@@ -44,7 +44,7 @@ Or, to run the hook on Jupyter Notebooks too:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.292
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
types_or: [python, pyi, jupyter]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
|
||||
readme = "README.md"
|
||||
|
||||
45
python/ruff-ecosystem/README.md
Normal file
45
python/ruff-ecosystem/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ruff-ecosystem
|
||||
|
||||
Ruff ecosystem checks.
|
||||
|
||||
## Installation
|
||||
|
||||
From the Ruff project root, install with `pip`:
|
||||
|
||||
```shell
|
||||
pip install -e ./python/ruff-ecosystem
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
ruff-ecosystem <check | format> <baseline executable> <comparison executable>
|
||||
```
|
||||
|
||||
Note executable paths must be absolute or relative to the current working directory.
|
||||
|
||||
Run `ruff check` ecosystem checks comparing your debug build to your system Ruff:
|
||||
|
||||
```shell
|
||||
ruff-ecosystem check "$(which ruff)" "./target/debug/ruff"
|
||||
```
|
||||
|
||||
Run `ruff format` ecosystem checks comparing your debug build to your system Ruff:
|
||||
|
||||
```shell
|
||||
ruff-ecosystem format "$(which ruff)" "./target/debug/ruff"
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
When developing, it can be useful to set the `--pdb` flag to drop into a debugger on failure:
|
||||
|
||||
```shell
|
||||
ruff-ecosystem check "$(which ruff)" "./target/debug/ruff" --pdb
|
||||
```
|
||||
|
||||
You can also provide a path to cache checkouts to speed up repeated runs:
|
||||
|
||||
```shell
|
||||
ruff-ecosystem check "$(which ruff)" "./target/debug/ruff" --cache ./repos
|
||||
```
|
||||
10
python/ruff-ecosystem/pyproject.toml
Normal file
10
python/ruff-ecosystem/pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ruff-ecosystem"
|
||||
version = "0.0.0"
|
||||
|
||||
[project.scripts]
|
||||
ruff-ecosystem = "ruff_ecosystem.cli:entrypoint"
|
||||
3
python/ruff-ecosystem/ruff_ecosystem/__init__.py
Normal file
3
python/ruff-ecosystem/ruff_ecosystem/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("ruff-ecosystem")
|
||||
8
python/ruff-ecosystem/ruff_ecosystem/__main__.py
Normal file
8
python/ruff-ecosystem/ruff_ecosystem/__main__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Enables usage with `python -m ruff_ecosystem`
|
||||
"""
|
||||
|
||||
from ruff_ecosystem.cli import entrypoint
|
||||
|
||||
if __name__ == "__main__":
|
||||
entrypoint()
|
||||
116
python/ruff-ecosystem/ruff_ecosystem/cli.py
Normal file
116
python/ruff-ecosystem/ruff_ecosystem/cli.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from contextlib import nullcontext
|
||||
from ruff_ecosystem.models import RuffCommand
|
||||
from ruff_ecosystem.emitters import EmitterType
|
||||
from ruff_ecosystem.defaults import DEFAULT_TARGETS
|
||||
from ruff_ecosystem.main import main
|
||||
from signal import SIGINT, SIGTERM
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def excepthook(type, value, tb):
|
||||
if hasattr(sys, "ps1") or not sys.stderr.isatty():
|
||||
# we are in interactive mode or we don't have a tty so call the default
|
||||
sys.__excepthook__(type, value, tb)
|
||||
else:
|
||||
import traceback, pdb
|
||||
|
||||
traceback.print_exception(type, value, tb)
|
||||
print()
|
||||
pdb.post_mortem(tb)
|
||||
|
||||
|
||||
def entrypoint():
|
||||
args = parse_args()
|
||||
|
||||
if args.pdb:
|
||||
sys.excepthook = excepthook
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Use a temporary directory for caching if no cache is specified
|
||||
cache_context = (
|
||||
tempfile.TemporaryDirectory() if not args.cache else nullcontext(args.cache)
|
||||
)
|
||||
|
||||
with cache_context as cache:
|
||||
loop = asyncio.get_event_loop()
|
||||
main_task = asyncio.ensure_future(
|
||||
main(
|
||||
command=RuffCommand(args.ruff_command),
|
||||
ruff_baseline_executable=args.ruff_baseline,
|
||||
ruff_comparison_executable=args.ruff_comparison,
|
||||
targets=DEFAULT_TARGETS,
|
||||
emitter=EmitterType(args.output_format).to_emitter(),
|
||||
cache=Path(cache),
|
||||
raise_on_failure=args.pdb,
|
||||
)
|
||||
)
|
||||
# https://stackoverflow.com/a/58840987/3549270
|
||||
for signal in [SIGINT, SIGTERM]:
|
||||
loop.add_signal_handler(signal, main_task.cancel)
|
||||
try:
|
||||
loop.run_until_complete(main_task)
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check two versions of ruff against a corpus of open-source code.",
|
||||
)
|
||||
|
||||
# TODO: Support non-default `--targets`
|
||||
# parser.add_argument(
|
||||
# "--targets",
|
||||
# type=Path,
|
||||
# help=(
|
||||
# "Optional JSON files to use over the default repositories. "
|
||||
# "Supports both github_search_*.jsonl and known-github-tomls.jsonl."
|
||||
# ),
|
||||
# )
|
||||
parser.add_argument(
|
||||
"--cache",
|
||||
type=Path,
|
||||
help="Location for caching cloned repositories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-format",
|
||||
choices=[option.name for option in EmitterType],
|
||||
default="json",
|
||||
help="Location for caching cloned repositories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable debug logging",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pdb",
|
||||
action="store_true",
|
||||
help="Enable debugging on failure",
|
||||
)
|
||||
parser.add_argument(
|
||||
"ruff_command",
|
||||
choices=[option.name for option in RuffCommand],
|
||||
help="The Ruff command to test",
|
||||
)
|
||||
parser.add_argument(
|
||||
"ruff_baseline",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"ruff_comparison",
|
||||
type=Path,
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
66
python/ruff-ecosystem/ruff_ecosystem/defaults.py
Normal file
66
python/ruff-ecosystem/ruff_ecosystem/defaults.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from .models import Repository, CheckOptions, Target
|
||||
|
||||
# TODO: Consider exporting this as JSON instead for consistent setup
|
||||
DEFAULT_TARGETS = [
|
||||
# Target(repo=Repository(owner="DisnakeDev", name="disnake", branch="master")),
|
||||
# Target(repo=Repository(owner="PostHog", name="HouseWatch", branch="main")),
|
||||
# Target(repo=Repository(owner="RasaHQ", name="rasa", branch="main")),
|
||||
# Target(repo=Repository(owner="Snowflake-Labs", name="snowcli", branch="main")),
|
||||
# Target(repo=Repository(owner="aiven", name="aiven-client", branch="main")),
|
||||
# Target(repo=Repository(owner="alteryx", name="featuretools", branch="main")),
|
||||
# Target(
|
||||
# repo=Repository(owner="apache", name="airflow", branch="main"),
|
||||
# check_options=CheckOptions(select="ALL"),
|
||||
# ),
|
||||
# Target(repo=Repository(owner="aws", name="aws-sam-cli", branch="develop")),
|
||||
# Target(repo=Repository(owner="bloomberg", name="pytest-memray", branch="main")),
|
||||
# Target(
|
||||
# repo=Repository(owner="bokeh", name="bokeh", branch="branch-3.3"),
|
||||
# check_options=CheckOptions(select="ALL"),
|
||||
# ),
|
||||
# Target(repo=Repository(owner="commaai", name="openpilot", branch="master")),
|
||||
# Target(repo=Repository(owner="demisto", name="content", branch="master")),
|
||||
# Target(repo=Repository(owner="docker", name="docker-py", branch="main")),
|
||||
# Target(
|
||||
# repo=Repository(owner="freedomofpress", name="securedrop", branch="develop")
|
||||
# ),
|
||||
# Target(repo=Repository(owner="fronzbot", name="blinkpy", branch="dev")),
|
||||
# Target(repo=Repository(owner="ibis-project", name="ibis", branch="master")),
|
||||
# Target(repo=Repository(owner="ing-bank", name="probatus", branch="main")),
|
||||
# Target(repo=Repository(owner="jrnl-org", name="jrnl", branch="develop")),
|
||||
# Target(repo=Repository(owner="latchbio", name="latch", branch="main")),
|
||||
# Target(repo=Repository(owner="lnbits", name="lnbits", branch="main")),
|
||||
# Target(repo=Repository(owner="milvus-io", name="pymilvus", branch="master")),
|
||||
# Target(repo=Repository(owner="mlflow", name="mlflow", branch="master")),
|
||||
# Target(repo=Repository(owner="model-bakers", name="model_bakery", branch="main")),
|
||||
# Target(repo=Repository(owner="pandas-dev", name="pandas", branch="main")),
|
||||
# Target(repo=Repository(owner="prefecthq", name="prefect", branch="main")),
|
||||
# Target(repo=Repository(owner="pypa", name="build", branch="main")),
|
||||
# Target(repo=Repository(owner="pypa", name="cibuildwheel", branch="main")),
|
||||
# Target(repo=Repository(owner="pypa", name="pip", branch="main")),
|
||||
# Target(repo=Repository(owner="pypa", name="setuptools", branch="main")),
|
||||
# Target(repo=Repository(owner="python", name="mypy", branch="master")),
|
||||
# Target(
|
||||
# repo=Repository(
|
||||
# owner="python",
|
||||
# name="typeshed",
|
||||
# branch="main",
|
||||
# ),
|
||||
# check_options=CheckOptions(select="PYI"),
|
||||
# ),
|
||||
# Target(repo=Repository(owner="python-poetry", name="poetry", branch="master")),
|
||||
# Target(repo=Repository(owner="reflex-dev", name="reflex", branch="main")),
|
||||
# Target(repo=Repository(owner="rotki", name="rotki", branch="develop")),
|
||||
# Target(repo=Repository(owner="scikit-build", name="scikit-build", branch="main")),
|
||||
# Target(
|
||||
# repo=Repository(owner="scikit-build", name="scikit-build-core", branch="main")
|
||||
# ),
|
||||
# Target(repo=Repository(owner="sphinx-doc", name="sphinx", branch="master")),
|
||||
# Target(repo=Repository(owner="spruceid", name="siwe-py", branch="main")),
|
||||
# Target(repo=Repository(owner="tiangolo", name="fastapi", branch="master")),
|
||||
# Target(repo=Repository(owner="yandex", name="ch-backup", branch="main")),
|
||||
Target(
|
||||
repo=Repository(owner="zulip", name="zulip", branch="main"),
|
||||
check_options=CheckOptions(select="ALL"),
|
||||
),
|
||||
]
|
||||
99
python/ruff-ecosystem/ruff_ecosystem/emitters.py
Normal file
99
python/ruff-ecosystem/ruff_ecosystem/emitters.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from enum import Enum
|
||||
import abc
|
||||
from ruff_ecosystem.models import Target, Diff, ClonedRepository, Result
|
||||
from ruff_ecosystem.ruff import CHECK_DIFF_LINE_RE
|
||||
import traceback
|
||||
import json
|
||||
from pathlib import Path
|
||||
import dataclasses
|
||||
|
||||
|
||||
class Emitter(abc.ABC):
|
||||
@abc.abstractclassmethod
|
||||
def emit_error(cls, target: Target, exc: Exception):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def emit_diff(cls, target: Target, diff: Diff, cloned_repo: ClonedRepository):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def emit_result(cls, result: Result):
|
||||
pass
|
||||
|
||||
|
||||
class DebugEmitter(Emitter):
|
||||
def emit_error(cls, target: Target, exc: Exception):
|
||||
print(f"Error in {target.repo.fullname}")
|
||||
traceback.print_exception(exc)
|
||||
|
||||
def emit_diff(cls, target: Target, diff: Diff, cloned_repo: ClonedRepository):
|
||||
pass
|
||||
|
||||
|
||||
class JSONEmitter(Emitter):
|
||||
class DataclassJSONEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if dataclasses.is_dataclass(o):
|
||||
return dataclasses.asdict(o)
|
||||
if isinstance(o, set):
|
||||
return tuple(o)
|
||||
if isinstance(o, Path):
|
||||
return str(o)
|
||||
return super().default(o)
|
||||
|
||||
def emit_error(cls, target: Target, exc: Exception):
|
||||
pass
|
||||
|
||||
def emit_diff(cls, target: Target, diff: Diff, cloned_repo: ClonedRepository):
|
||||
pass
|
||||
|
||||
def emit_result(cls, result: Result):
|
||||
print(json.dumps(result, indent=4, cls=cls.DataclassJSONEncoder))
|
||||
|
||||
|
||||
class MarkdownEmitter(Emitter):
|
||||
def emit_error(cls, target: Target, exc: Exception):
|
||||
cls._print(title="error", content=f"```\n{exc}\n```", target=target)
|
||||
|
||||
def emit_diff(cls, target: Target, diff: Diff, cloned_repo: ClonedRepository):
|
||||
changes = f"+{len(diff.added)}, -{len(diff.removed)}"
|
||||
|
||||
content = ""
|
||||
for line in list(diff):
|
||||
match = CHECK_DIFF_LINE_RE.match(line)
|
||||
if match is None:
|
||||
content += line + "\n"
|
||||
continue
|
||||
|
||||
pre, inner, path, lnum, post = match.groups()
|
||||
url = cloned_repo.url_for(path, int(lnum))
|
||||
content += f"{pre} <a href='{url}'>{inner}</a> {post}" + "\n"
|
||||
|
||||
cls._print(title=changes, content=f"<pre>\n{content}\n</pre>", target=target)
|
||||
|
||||
def _print(cls, title: str, content: str, target: Target):
|
||||
print(f"<details><summary>{target.repo.fullname} ({title})</summary>")
|
||||
print(target.repo.url, target.check_options.summary())
|
||||
print("<p>")
|
||||
print()
|
||||
|
||||
print(content)
|
||||
|
||||
print()
|
||||
print("</p>")
|
||||
print("</details>")
|
||||
|
||||
|
||||
class EmitterType(Enum):
|
||||
markdown = "markdown"
|
||||
json = "json"
|
||||
|
||||
def to_emitter(self) -> Emitter:
|
||||
match self:
|
||||
case self.markdown:
|
||||
return MarkdownEmitter()
|
||||
case self.json:
|
||||
return JSONEmitter()
|
||||
case _:
|
||||
raise ValueError("Unknown emitter type {self}")
|
||||
72
python/ruff-ecosystem/ruff_ecosystem/git.py
Normal file
72
python/ruff-ecosystem/ruff_ecosystem/git.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from ruff_ecosystem.models import Repository, ClonedRepository
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator
|
||||
from asyncio import create_subprocess_exec
|
||||
from subprocess import PIPE
|
||||
from ruff_ecosystem import logger
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def clone(
|
||||
repo: Repository, checkout_dir: Path
|
||||
) -> AsyncGenerator[ClonedRepository, None]:
|
||||
"""Shallow clone this repository to a temporary directory."""
|
||||
if checkout_dir.exists():
|
||||
logger.debug(f"Reusing {repo.owner}:{repo.name}")
|
||||
yield await _cloned_repository(repo, checkout_dir)
|
||||
return
|
||||
|
||||
logger.debug(f"Cloning {repo.owner}:{repo.name} to {checkout_dir}")
|
||||
command = [
|
||||
"git",
|
||||
"clone",
|
||||
"--config",
|
||||
"advice.detachedHead=false",
|
||||
"--quiet",
|
||||
"--depth",
|
||||
"1",
|
||||
"--no-tags",
|
||||
]
|
||||
if repo.branch:
|
||||
command.extend(["--branch", repo.branch])
|
||||
|
||||
command.extend(
|
||||
[
|
||||
f"https://github.com/{repo.owner}/{repo.name}",
|
||||
checkout_dir,
|
||||
],
|
||||
)
|
||||
|
||||
process = await create_subprocess_exec(*command, env={"GIT_TERMINAL_PROMPT": "0"})
|
||||
|
||||
status_code = await process.wait()
|
||||
|
||||
logger.debug(
|
||||
f"Finished cloning {repo.fullname} with status {status_code}",
|
||||
)
|
||||
yield await _cloned_repository(repo, checkout_dir)
|
||||
|
||||
|
||||
async def _cloned_repository(repo: Repository, checkout_dir: Path) -> ClonedRepository:
|
||||
return ClonedRepository(
|
||||
name=repo.name,
|
||||
owner=repo.owner,
|
||||
branch=repo.branch,
|
||||
path=checkout_dir,
|
||||
commit_hash=await _get_commit_hash(checkout_dir),
|
||||
)
|
||||
|
||||
|
||||
async def _get_commit_hash(checkout_dir: Path) -> str:
|
||||
"""
|
||||
Return the commit sha for the repository in the checkout directory.
|
||||
"""
|
||||
process = await create_subprocess_exec(
|
||||
*["git", "rev-parse", "HEAD"],
|
||||
cwd=checkout_dir,
|
||||
stdout=PIPE,
|
||||
)
|
||||
stdout, _ = await process.communicate()
|
||||
assert await process.wait() == 0, f"Failed to retrieve commit sha at {checkout_dir}"
|
||||
return stdout.decode().strip()
|
||||
235
python/ruff-ecosystem/ruff_ecosystem/main.py
Normal file
235
python/ruff-ecosystem/ruff_ecosystem/main.py
Normal file
@@ -0,0 +1,235 @@
|
||||
from ruff_ecosystem.models import (
|
||||
RuffCommand,
|
||||
Target,
|
||||
Diff,
|
||||
ClonedRepository,
|
||||
RuleChanges,
|
||||
CheckComparison,
|
||||
Result,
|
||||
)
|
||||
from pathlib import Path
|
||||
from ruff_ecosystem import logger
|
||||
import asyncio
|
||||
from ruff_ecosystem.git import clone
|
||||
from ruff_ecosystem.ruff import ruff_check, ruff_format
|
||||
from ruff_ecosystem.emitters import Emitter
|
||||
import difflib
|
||||
from typing import TypeVar
|
||||
import re
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
async def main(
|
||||
command: RuffCommand,
|
||||
ruff_baseline_executable: Path,
|
||||
ruff_comparison_executable: Path,
|
||||
targets: list[Target],
|
||||
cache: Path | None,
|
||||
emitter: Emitter,
|
||||
max_parallelism: int = 50,
|
||||
raise_on_failure: bool = False,
|
||||
) -> None:
|
||||
logger.debug("Using command %s", command.value)
|
||||
logger.debug("Using baseline executable at %s", ruff_baseline_executable)
|
||||
logger.debug("Using comparison executable at %s", ruff_comparison_executable)
|
||||
logger.debug("Using cache directory %s", cache)
|
||||
logger.debug("Checking %s targets", len(targets))
|
||||
|
||||
semaphore = asyncio.Semaphore(max_parallelism)
|
||||
|
||||
async def limited_parallelism(coroutine: T) -> T:
|
||||
async with semaphore:
|
||||
return await coroutine
|
||||
|
||||
comparisons: list[Exception | CheckComparison] = await asyncio.gather(
|
||||
*[
|
||||
limited_parallelism(
|
||||
clone_and_compare(
|
||||
command,
|
||||
ruff_baseline_executable,
|
||||
ruff_comparison_executable,
|
||||
target,
|
||||
cache,
|
||||
)
|
||||
)
|
||||
for target in targets
|
||||
],
|
||||
return_exceptions=not raise_on_failure,
|
||||
)
|
||||
comparisons_by_target = dict(zip(targets, comparisons, strict=True))
|
||||
|
||||
# Calculate totals
|
||||
total_removed = total_added = errors = 0
|
||||
total_rule_changes = RuleChanges()
|
||||
for comparison in comparisons_by_target.values():
|
||||
if isinstance(comparison, Exception):
|
||||
errors += 1
|
||||
else:
|
||||
total_removed += len(comparison.diff.removed)
|
||||
total_added += len(comparison.diff.added)
|
||||
total_rule_changes += comparison.rule_changes
|
||||
|
||||
errors = []
|
||||
comparisons = []
|
||||
for target, comparison in comparisons_by_target.items():
|
||||
if isinstance(comparison, Exception):
|
||||
errors.append((target, comparison))
|
||||
continue
|
||||
|
||||
if comparison.diff:
|
||||
comparisons.append((target, comparison))
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
result = Result(
|
||||
total_added=total_added,
|
||||
total_removed=total_removed,
|
||||
total_rule_changes=total_rule_changes,
|
||||
comparisons=comparisons,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
emitter.emit_result(result)
|
||||
return
|
||||
|
||||
if total_removed == 0 and total_added == 0 and errors == 0:
|
||||
print("\u2705 ecosystem check detected no changes.")
|
||||
return
|
||||
|
||||
s = "s" if errors != 1 else ""
|
||||
changes = f"(+{total_added}, -{total_removed}, {errors} error{s})"
|
||||
|
||||
print(f"\u2139\ufe0f ecosystem check **detected changes**. {changes}")
|
||||
print()
|
||||
|
||||
for target, comparison in comparisons_by_target.items():
|
||||
if isinstance(comparison, Exception):
|
||||
emitter.emit_error(target, comparison)
|
||||
continue
|
||||
|
||||
if comparison.diff:
|
||||
emitter.emit_diff(target, comparison.diff, comparison.repo)
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
if len(total_rule_changes.rule_codes()) > 0:
|
||||
print(f"Rules changed: {len(total_rule_changes.rule_codes())}")
|
||||
print()
|
||||
print("| Rule | Changes | Additions | Removals |")
|
||||
print("| ---- | ------- | --------- | -------- |")
|
||||
for rule, (additions, removals) in sorted(
|
||||
total_rule_changes.items(),
|
||||
key=lambda x: (x[1][0] + x[1][1]),
|
||||
reverse=True,
|
||||
):
|
||||
print(f"| {rule} | {additions + removals} | {additions} | {removals} |")
|
||||
|
||||
|
||||
async def clone_and_compare(
|
||||
command: RuffCommand,
|
||||
ruff_baseline_executable: Path,
|
||||
ruff_comparison_executable: Path,
|
||||
target: Target,
|
||||
cache: Path,
|
||||
) -> CheckComparison:
|
||||
"""Check a specific repository against two versions of ruff."""
|
||||
assert ":" not in target.repo.owner
|
||||
assert ":" not in target.repo.name
|
||||
|
||||
match command:
|
||||
case RuffCommand.check:
|
||||
ruff_task, create_comparison, options = (
|
||||
ruff_check,
|
||||
create_check_comparison,
|
||||
target.check_options,
|
||||
)
|
||||
case RuffCommand.format:
|
||||
ruff_task, create_comparison, options = (
|
||||
ruff_format,
|
||||
create_format_comparison,
|
||||
target.format_options,
|
||||
)
|
||||
case _:
|
||||
raise ValueError(f"Unknowm target Ruff command {command}")
|
||||
|
||||
checkout_dir = cache.joinpath(f"{target.repo.owner}:{target.repo.name}")
|
||||
async with clone(target.repo, checkout_dir) as cloned_repo:
|
||||
try:
|
||||
async with asyncio.TaskGroup() as tg:
|
||||
baseline_task = tg.create_task(
|
||||
ruff_task(
|
||||
executable=ruff_baseline_executable.resolve(),
|
||||
path=cloned_repo.path,
|
||||
name=cloned_repo.fullname,
|
||||
options=options,
|
||||
),
|
||||
)
|
||||
comparison_task = tg.create_task(
|
||||
ruff_task(
|
||||
executable=ruff_comparison_executable.resolve(),
|
||||
path=cloned_repo.path,
|
||||
name=cloned_repo.fullname,
|
||||
options=options,
|
||||
),
|
||||
)
|
||||
except ExceptionGroup as e:
|
||||
raise e.exceptions[0] from e
|
||||
|
||||
return create_comparison(
|
||||
cloned_repo, baseline_task.result(), comparison_task.result()
|
||||
)
|
||||
|
||||
|
||||
def create_check_comparison(
|
||||
repo: ClonedRepository, baseline_output: str, comparison_output: str
|
||||
) -> CheckComparison:
|
||||
removed, added = set(), set()
|
||||
|
||||
for line in difflib.ndiff(baseline_output, comparison_output):
|
||||
if line.startswith("- "):
|
||||
removed.add(line[2:])
|
||||
elif line.startswith("+ "):
|
||||
added.add(line[2:])
|
||||
|
||||
diff = Diff(removed=removed, added=added)
|
||||
|
||||
return CheckComparison(
|
||||
diff=diff, repo=repo, rule_changes=rule_changes_from_diff(diff)
|
||||
)
|
||||
|
||||
|
||||
def rule_changes_from_diff(diff: Diff) -> RuleChanges:
|
||||
"""
|
||||
Parse a diff from `ruff check` to determine the additions and removals for each rule.
|
||||
"""
|
||||
rule_changes = RuleChanges()
|
||||
|
||||
# Count rule changes
|
||||
for line in diff.lines():
|
||||
# Find rule change for current line or construction
|
||||
# + <rule>/<path>:<line>:<column>: <rule_code> <message>
|
||||
matches = re.search(r": ([A-Z]{1,4}[0-9]{3,4})", line)
|
||||
|
||||
if matches is None:
|
||||
# Handle case where there are no regex matches e.g.
|
||||
# + "?application=AIRFLOW&authenticator=TEST_AUTH&role=TEST_ROLE&warehouse=TEST_WAREHOUSE" # noqa: E501, ERA001
|
||||
# Which was found in local testing
|
||||
continue
|
||||
|
||||
rule_code = matches.group(1)
|
||||
|
||||
# Get current additions and removals for this rule
|
||||
current_changes = rule_changes[rule_code]
|
||||
|
||||
# Check if addition or removal depending on the first character
|
||||
if line[0] == "+":
|
||||
current_changes = (current_changes[0] + 1, current_changes[1])
|
||||
elif line[0] == "-":
|
||||
current_changes = (current_changes[0], current_changes[1] + 1)
|
||||
|
||||
rule_changes[rule_code] = current_changes
|
||||
|
||||
return rule_changes
|
||||
160
python/ruff-ecosystem/ruff_ecosystem/models.py
Normal file
160
python/ruff-ecosystem/ruff_ecosystem/models.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Self, Iterator
|
||||
import heapq
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RuffCommand(Enum):
|
||||
check = "check"
|
||||
format = "format"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Repository:
|
||||
"""
|
||||
A remote GitHub repository
|
||||
"""
|
||||
|
||||
owner: str
|
||||
name: str
|
||||
branch: str | None
|
||||
|
||||
@property
|
||||
def fullname(self) -> str:
|
||||
return f"{self.owner}/{self.name}"
|
||||
|
||||
@property
|
||||
def url(self: Self) -> str:
|
||||
return f"https://github.com/{self.owner}/{self.name}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ClonedRepository(Repository):
|
||||
"""
|
||||
A cloned GitHub repository, which includes the hash of the cloned commit.
|
||||
"""
|
||||
|
||||
commit_hash: str
|
||||
path: Path
|
||||
|
||||
def url_for(self: Self, path: str, line_number: int | None = None) -> str:
|
||||
"""
|
||||
Return the remote GitHub URL for the given path in this repository.
|
||||
"""
|
||||
# Default to main branch
|
||||
url = f"https://github.com/{self.owner}/{self.name}/blob/{self.commit_hash}/{path}"
|
||||
if line_number:
|
||||
url += f"#L{line_number}"
|
||||
return url
|
||||
|
||||
@property
|
||||
def url(self: Self) -> str:
|
||||
return f"https://github.com/{self.owner}/{self.name}@{self.commit_hash}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Diff:
|
||||
"""A diff between two runs of ruff."""
|
||||
|
||||
removed: set[str]
|
||||
added: set[str]
|
||||
|
||||
def __bool__(self: Self) -> bool:
|
||||
"""Return true if this diff is non-empty."""
|
||||
return bool(self.removed or self.added)
|
||||
|
||||
def lines(self: Self) -> Iterator[str]:
|
||||
"""Iterate through the changed lines in diff format."""
|
||||
for line in heapq.merge(sorted(self.removed), sorted(self.added)):
|
||||
if line in self.removed:
|
||||
yield f"- {line}"
|
||||
else:
|
||||
yield f"+ {line}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuleChanges:
|
||||
changes: dict[str, tuple[int, int]] = field(default_factory=dict)
|
||||
|
||||
def rule_codes(self) -> list[str]:
|
||||
return list(self.changes.keys())
|
||||
|
||||
def items(self) -> Iterator[tuple[str, tuple[int, int]]]:
|
||||
return self.changes.items()
|
||||
|
||||
def __setitem__(self, key: str, value: tuple[int, int]) -> None:
|
||||
self.changes[key] = value
|
||||
|
||||
def __getitem__(self, key: str) -> tuple[int, int]:
|
||||
return self.changes.get(key, (0, 0))
|
||||
|
||||
def __add__(self, other: Self) -> Self:
|
||||
if not isinstance(other, type(self)):
|
||||
return NotImplemented
|
||||
|
||||
result = self.changes.copy()
|
||||
for rule_code, (added, removed) in other.changes.items():
|
||||
if rule_code in result:
|
||||
result[rule_code] = (
|
||||
result[rule_code][0] + added,
|
||||
result[rule_code][1] + removed,
|
||||
)
|
||||
else:
|
||||
result[rule_code] = (added, removed)
|
||||
|
||||
return RuleChanges(changes=result)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CheckComparison:
|
||||
diff: Diff
|
||||
repo: ClonedRepository
|
||||
rule_changes: RuleChanges
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CheckOptions:
|
||||
"""
|
||||
Ruff check options
|
||||
"""
|
||||
|
||||
select: str = ""
|
||||
ignore: str = ""
|
||||
exclude: str = ""
|
||||
|
||||
# Generating fixes is slow and verbose
|
||||
show_fixes: bool = False
|
||||
|
||||
def summary(self) -> str:
|
||||
return f"select {self.select} ignore {self.ignore} exclude {self.exclude}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FormatOptions:
|
||||
"""
|
||||
Ruff format options
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Target:
|
||||
"""
|
||||
An ecosystem target
|
||||
"""
|
||||
|
||||
repo: Repository
|
||||
check_options: CheckOptions = field(default_factory=CheckOptions)
|
||||
format_options: FormatOptions = field(default_factory=FormatOptions)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Result:
|
||||
total_added: int
|
||||
total_removed: int
|
||||
total_rule_changes: RuleChanges
|
||||
|
||||
comparisons: tuple[Target, CheckComparison]
|
||||
errors: tuple[Target, Exception]
|
||||
90
python/ruff-ecosystem/ruff_ecosystem/ruff.py
Normal file
90
python/ruff-ecosystem/ruff_ecosystem/ruff.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from pathlib import Path
|
||||
from ruff_ecosystem import logger
|
||||
from ruff_ecosystem.models import CheckOptions, FormatOptions
|
||||
import time
|
||||
from asyncio import create_subprocess_exec
|
||||
from subprocess import PIPE
|
||||
from typing import Sequence
|
||||
import re
|
||||
|
||||
CHECK_SUMMARY_LINE_RE = re.compile(
|
||||
r"^(Found \d+ error.*)|(.*potentially fixable with.*)$"
|
||||
)
|
||||
|
||||
|
||||
CHECK_DIFF_LINE_RE = re.compile(
|
||||
r"^(?P<pre>[+-]) (?P<inner>(?P<path>[^:]+):(?P<lnum>\d+):\d+:) (?P<post>.*)$",
|
||||
)
|
||||
|
||||
|
||||
class RuffError(Exception):
|
||||
"""An error reported by ruff."""
|
||||
|
||||
|
||||
async def ruff_check(
|
||||
*, executable: Path, path: Path, name: str, options: CheckOptions
|
||||
) -> Sequence[str]:
|
||||
"""Run the given ruff binary against the specified path."""
|
||||
logger.debug(f"Checking {name} with {executable}")
|
||||
ruff_args = ["check", "--no-cache", "--exit-zero"]
|
||||
if options.select:
|
||||
ruff_args.extend(["--select", options.select])
|
||||
if options.ignore:
|
||||
ruff_args.extend(["--ignore", options.ignore])
|
||||
if options.exclude:
|
||||
ruff_args.extend(["--exclude", options.exclude])
|
||||
if options.show_fixes:
|
||||
ruff_args.extend(["--show-fixes", "--ecosystem-ci"])
|
||||
|
||||
start = time.time()
|
||||
proc = await create_subprocess_exec(
|
||||
executable.absolute(),
|
||||
*ruff_args,
|
||||
".",
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
cwd=path,
|
||||
)
|
||||
result, err = await proc.communicate()
|
||||
end = time.time()
|
||||
|
||||
logger.debug(f"Finished checking {name} with {executable} in {end - start:.2f}")
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise RuffError(err.decode("utf8"))
|
||||
|
||||
lines = [
|
||||
line
|
||||
for line in result.decode("utf8").splitlines()
|
||||
if not CHECK_SUMMARY_LINE_RE.match(line)
|
||||
]
|
||||
|
||||
return sorted(lines)
|
||||
|
||||
|
||||
async def ruff_format(
|
||||
*, executable: Path, path: Path, name: str, options: FormatOptions
|
||||
) -> Sequence[str]:
|
||||
"""Run the given ruff binary against the specified path."""
|
||||
logger.debug(f"Checking {name} with {executable}")
|
||||
ruff_args = ["format", "--no-cache", "--exit-zero"]
|
||||
|
||||
start = time.time()
|
||||
proc = await create_subprocess_exec(
|
||||
executable.absolute(),
|
||||
*ruff_args,
|
||||
".",
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
cwd=path,
|
||||
)
|
||||
result, err = await proc.communicate()
|
||||
end = time.time()
|
||||
|
||||
logger.debug(f"Finished formatting {name} with {executable} in {end - start:.2f}")
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise RuffError(err.decode("utf8"))
|
||||
|
||||
lines = result.decode("utf8").splitlines()
|
||||
return lines
|
||||
33
ruff.schema.json
generated
33
ruff.schema.json
generated
@@ -355,10 +355,10 @@
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"description": "Options to configure the code formatting.\n\nPreviously: The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations), `\"gitlab\"` (GitLab CI code quality report), `\"pylint\"` (Pylint text format) or `\"azure\"` (Azure Pipeline logging commands).\n\nThis option has been **deprecated** in favor of `output-format` to avoid ambiguity with Ruff's upcoming formatter.",
|
||||
"description": "Options to configure code formatting.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FormatOrOutputFormat"
|
||||
"$ref": "#/definitions/FormatOptions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -1239,7 +1239,7 @@
|
||||
]
|
||||
},
|
||||
"quote-style": {
|
||||
"description": "Whether to prefer single `'` or double `\"` quotes for strings. Defaults to double quotes.\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for multiline strings and docstrings, regardless of the configured quote style.\n\nRuff may also deviate from this option if using the configured quotes would require escaping quote characters within the string. For example, given:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change `a` to use single quotes when using `quote-style = \"single\"`. However, `a` will be unchanged, as converting to single quotes would require the inner `'` to be escaped, which leads to less readable code: `'It\\'s monday morning'`.",
|
||||
"description": "Whether to prefer single `'` or double `\"` quotes for strings. Defaults to double quotes.\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for multiline strings and docstrings, regardless of the configured quote style.\n\nRuff may also deviate from this option if using the configured quotes would require escaping quote characters within the string. For example, given:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change `a` to use single quotes when using `quote-style = \"single\"`. However, `b` will be unchanged, as converting to single quotes would require the inner `'` to be escaped, which leads to less readable code: `'It\\'s monday morning'`.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/QuoteStyle"
|
||||
@@ -1259,16 +1259,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FormatOrOutputFormat": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FormatOptions"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializationFormat"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportSection": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2224,6 +2214,15 @@
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"max-bool-expr": {
|
||||
"description": "Maximum number of Boolean expressions allowed within a single `if` statement (see: `PLR0916`).",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"max-branches": {
|
||||
"description": "Maximum number of branches allowed for a function or method body (see: `PLR0912`).",
|
||||
"type": [
|
||||
@@ -2905,6 +2904,9 @@
|
||||
"PLE060",
|
||||
"PLE0604",
|
||||
"PLE0605",
|
||||
"PLE07",
|
||||
"PLE070",
|
||||
"PLE0704",
|
||||
"PLE1",
|
||||
"PLE11",
|
||||
"PLE114",
|
||||
@@ -2956,6 +2958,7 @@
|
||||
"PLR0912",
|
||||
"PLR0913",
|
||||
"PLR0915",
|
||||
"PLR0916",
|
||||
"PLR1",
|
||||
"PLR17",
|
||||
"PLR170",
|
||||
@@ -2975,6 +2978,9 @@
|
||||
"PLR550",
|
||||
"PLR5501",
|
||||
"PLR6",
|
||||
"PLR62",
|
||||
"PLR620",
|
||||
"PLR6201",
|
||||
"PLR63",
|
||||
"PLR630",
|
||||
"PLR6301",
|
||||
@@ -3004,6 +3010,7 @@
|
||||
"PLW1509",
|
||||
"PLW151",
|
||||
"PLW1510",
|
||||
"PLW1514",
|
||||
"PLW16",
|
||||
"PLW164",
|
||||
"PLW1641",
|
||||
|
||||
@@ -140,8 +140,7 @@ pub(crate) fn {rule_name_snake}(checker: &mut Checker) {{}}
|
||||
variant = pascal_case(linter)
|
||||
rule = f"""rules::{linter.split(" ")[0]}::rules::{name}"""
|
||||
lines.append(
|
||||
" " * 8
|
||||
+ f"""({variant}, "{code}") => (RuleGroup::Unspecified, {rule}),\n""",
|
||||
" " * 8 + f"""({variant}, "{code}") => (RuleGroup::Stable, {rule}),\n""",
|
||||
)
|
||||
lines.sort()
|
||||
text += "".join(lines)
|
||||
|
||||
@@ -68,6 +68,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
|
||||
"surrounding-whitespace",
|
||||
"tab-indentation",
|
||||
"too-few-spaces-before-inline-comment",
|
||||
"too-many-boolean-expressions",
|
||||
"trailing-comma-on-bare-tuple",
|
||||
"triple-single-quotes",
|
||||
"under-indentation",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# The pinned revisions are the latest of this writing, update freely.
|
||||
|
||||
set -ex
|
||||
set -e
|
||||
|
||||
target=$(git rev-parse --show-toplevel)/target
|
||||
dir="$target/progress_projects"
|
||||
@@ -20,43 +20,55 @@ mkdir -p "$dir"
|
||||
if [ ! -d "$dir/twine/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/pypa/twine "$dir/twine"
|
||||
fi
|
||||
git -C "$dir/twine" checkout 0bb428c410b8df64c04dc881ac1db37d932f3066
|
||||
git -C "$dir/twine" checkout -q afc37f8b26ed06ccd104f6724f293f657b9b7f15
|
||||
|
||||
# web framework that implements a lot of magic
|
||||
if [ ! -d "$dir/django/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/django/django "$dir/django"
|
||||
fi
|
||||
git -C "$dir/django" checkout 48a1929ca050f1333927860ff561f6371706968a
|
||||
git -C "$dir/django" checkout -q 20b7aac7ca60b0352d926340622e618bcbee54a8
|
||||
|
||||
# an ML project
|
||||
if [ ! -d "$dir/transformers/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/huggingface/transformers "$dir/transformers"
|
||||
fi
|
||||
git -C "$dir/transformers" checkout 62396cff46854dc53023236cfeb785993fa70067
|
||||
git -C "$dir/transformers" checkout -q 5c081e29930466ecf9a478727039d980131076d9
|
||||
|
||||
# type annotations
|
||||
if [ ! -d "$dir/typeshed/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/python/typeshed "$dir/typeshed"
|
||||
fi
|
||||
git -C "$dir/typeshed" checkout 2c15a8e7906e19f49bb765e2807dd0079fe9c04b
|
||||
git -C "$dir/typeshed" checkout -q cb688d2577520d98c09853acc20de099300b4e48
|
||||
|
||||
# python 3.11, typing and 100% test coverage
|
||||
if [ ! -d "$dir/warehouse/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/pypi/warehouse "$dir/warehouse"
|
||||
fi
|
||||
git -C "$dir/warehouse" checkout 6be6bccf07dace18784ea8aeac7906903fdbcf3a
|
||||
git -C "$dir/warehouse" checkout -q c6d9dd32b7c85d3a5f4240c95267874417e5b965
|
||||
|
||||
# zulip, a django user
|
||||
if [ ! -d "$dir/zulip/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/zulip/zulip "$dir/zulip"
|
||||
fi
|
||||
git -C "$dir/zulip" checkout 328cdde24331b82baa4c9b1bf1cb7b2015799826
|
||||
git -C "$dir/zulip" checkout -q b605042312c763c9a1e458f0ca6a003799682546
|
||||
|
||||
# home-assistant, home automation with 1ok files
|
||||
if [ ! -d "$dir/home-assistant/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/home-assistant/core "$dir/home-assistant"
|
||||
fi
|
||||
git -C "$dir/home-assistant" checkout -q 88296c1998fd1943576e0167ab190d25af175257
|
||||
|
||||
# poetry, a package manager that uses black preview style
|
||||
if [ ! -d "$dir/poetry/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/python-poetry/poetry "$dir/poetry"
|
||||
fi
|
||||
git -C "$dir/poetry" checkout -q f5cb9f0fb19063cf280faf5e39c82d5691da9939
|
||||
|
||||
# cpython itself
|
||||
if [ ! -d "$dir/cpython/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/python/cpython "$dir/cpython"
|
||||
fi
|
||||
git -C "$dir/cpython" checkout 1a1bfc28912a39b500c578e9f10a8a222638d411
|
||||
git -C "$dir/cpython" checkout -q b75186f69edcf54615910a5cd707996144163ef7
|
||||
|
||||
# Uncomment if you want to update the hashes
|
||||
#for i in "$dir"/*/; do git -C "$i" switch main && git -C "$i" pull; done
|
||||
@@ -64,7 +76,7 @@ git -C "$dir/cpython" checkout 1a1bfc28912a39b500c578e9f10a8a222638d411
|
||||
|
||||
time cargo run --bin ruff_dev -- format-dev --stability-check \
|
||||
--error-file "$target/progress_projects_errors.txt" --log-file "$target/progress_projects_log.txt" --stats-file "$target/progress_projects_stats.txt" \
|
||||
--files-with-errors 15 --multi-project "$dir" || (
|
||||
--files-with-errors 14 --multi-project "$dir" || (
|
||||
echo "Ecosystem check failed"
|
||||
cat "$target/progress_projects_log.txt"
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user