Compare commits

..

11 Commits

Author SHA1 Message Date
Zanie
6eeadba9f5 Remove Applicability::Unspecified and associated helpers 2023-10-03 14:39:23 -05:00
Zanie
ee903bf168 Update snapshots to reflect changes in fix messages 2023-10-03 13:02:58 -05:00
Zanie
9949fa0daf Use different symbols for fix messages depending on applicability
Refactors `violation_string` as well
2023-10-03 13:02:58 -05:00
Zanie
96b8ce18c8 Refactor fix_file implementation to use Fix.applies
https://github.com/astral-sh/ruff/pull/5119#discussion_r1265224831
2023-10-03 13:02:58 -05:00
Zanie
d004ddc674 Cleanup internal commentary on the fix rules 2023-10-03 13:02:58 -05:00
Zanie
6577833435 Remove stale todo from test 2023-10-03 13:02:58 -05:00
Zanie
87def58605 Revert change to notebook cell iteration 2023-10-03 13:02:58 -05:00
Zanie
7c05fb5d66 Use self.flags.intersects consistently 2023-10-03 13:02:58 -05:00
Zanie
85b5efe141 Update CLI documentation for fixes 2023-10-03 13:02:58 -05:00
Zanie
74acdf5a22 Fixups for CI 2023-10-03 13:02:58 -05:00
Evan Rittenhouse
d01969c974 Update CLI to respect fix applicability 2023-10-03 13:02:56 -05:00
619 changed files with 22412 additions and 27693 deletions

View File

@@ -77,8 +77,6 @@ jobs:
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Clippy"
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- name: "Clippy (wasm)"
@@ -99,8 +97,6 @@ jobs:
with:
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: cargo insta test --all --all-features --unreferenced reject
@@ -150,8 +146,6 @@ jobs:
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@v0.4.0
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Run wasm-pack"
run: |
cd crates/ruff_wasm
@@ -165,8 +159,6 @@ jobs:
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
- run: cargo check
- run: cargo fmt --all --check
@@ -235,8 +227,6 @@ jobs:
# Only pinned to make caching work, update freely
run: rustup toolchain install nightly-2023-06-08
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
@@ -252,8 +242,6 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
@@ -279,8 +267,6 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Install pre-commit"
run: pip install pre-commit
- name: "Cache pre-commit"
@@ -314,8 +300,6 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: pip install -r docs/requirements-insiders.txt
@@ -368,8 +352,6 @@ jobs:
tool: cargo-codspeed
- uses: Swatinem/rust-cache@v2
with:
shared-key: "ci"
- name: "Build benchmarks"
run: cargo codspeed build --features codspeed -p ruff_benchmark

View File

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

View File

@@ -40,9 +40,8 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.3.1
uses: cloudflare/wrangler-action@v3.1.1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
command: pages deploy playground/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
command: pages deploy playground/dist --project-name=ruff-playground --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}

View File

@@ -23,7 +23,6 @@ repos:
- id: mdformat
additional_dependencies:
- mdformat-mkdocs
- mdformat-admon
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0

View File

@@ -1,35 +1,5 @@
# Breaking Changes
## 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
fixes, but the meaning could be changed when applying unsafe fixes. Previously, unsafe fixes were always displayed
and applied when fixing was enabled. Now, unsafe fixes are hidden by default and not applied. The `--unsafe-fixes`
flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.
See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,
the stable rule set no longer includes `line-too-long` (`E501`) and `mixed-spaces-and-tabs` (`E101`). Other
excluded Pycodestyle rules include whitespace enforcement in `E1` and `E2`; these rules are currently in preview, and are already omitted by default.
This change only affects those using Ruff under its default rule set. Users that include `E` in their `select` will experience no change in behavior.
## 0.0.288
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))

View File

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

View File

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

139
Cargo.lock generated
View File

@@ -28,9 +28,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
dependencies = [
"memchr",
]
@@ -74,9 +74,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.4"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -112,9 +112,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
@@ -221,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
dependencies = [
"memchr",
"regex-automata 0.3.9",
"regex-automata 0.3.8",
"serde",
]
@@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.6"
version = "4.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3"
dependencies = [
"clap_builder",
"clap_derive",
@@ -323,9 +323,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.6"
version = "4.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab"
dependencies = [
"anstream",
"anstyle",
@@ -383,7 +383,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -608,7 +608,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"anyhow",
"clap",
@@ -1084,9 +1084,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.34.0"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
dependencies = [
"console",
"globset",
@@ -1129,7 +1129,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -1268,9 +1268,8 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libcst"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5c2ff400caac657bf794181d885491bb97cc37c376f8cb4fa3a0cc2176a053"
version = "0.1.0"
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
dependencies = [
"chic",
"libcst_derive",
@@ -1283,9 +1282,8 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7f252282b20bfec6fae65d351ab1df7e4552a6270dd7bb779ca9d6135aabe9"
version = "0.1.0"
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
dependencies = [
"quote",
"syn 1.0.109",
@@ -1707,7 +1705,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -1792,9 +1790,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
@@ -1925,14 +1923,14 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
"regex-automata 0.3.8",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1946,19 +1944,13 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.9"
version = "0.3.8"
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"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.2",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1973,12 +1965,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "result-like"
version = "0.4.6"
@@ -2051,7 +2037,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2155,7 +2141,6 @@ name = "ruff_diagnostics"
version = "0.0.0"
dependencies = [
"anyhow",
"is-macro",
"log",
"ruff_text_size",
"serde",
@@ -2188,7 +2173,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
@@ -2259,7 +2244,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -2509,7 +2494,6 @@ dependencies = [
"glob",
"globset",
"ignore",
"is-macro",
"itertools 0.11.0",
"log",
"once_cell",
@@ -2658,9 +2642,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
version = "1.0.20"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
[[package]]
name = "serde"
@@ -2690,7 +2674,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -2752,7 +2736,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -2781,9 +2765,9 @@ checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "similar"
version = "2.3.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
[[package]]
name = "siphasher"
@@ -2847,7 +2831,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -2863,9 +2847,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [
"proc-macro2",
"quote",
@@ -2952,7 +2936,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -2964,7 +2948,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
"test-case-core",
]
@@ -2985,7 +2969,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -3104,10 +3088,11 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.39"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
@@ -3116,20 +3101,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.27"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
"valuable",
@@ -3259,9 +3244,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5506ae2c3c1ccbdf468e52fc5ef536c2ccd981f01273a4cb81aa61021f3a5f"
checksum = "38b2c0942619ae1797f999a0ce7efc6c09592ad30e68e16cdbfdcd48a98c3579"
dependencies = [
"phf",
"unicode_names2_generator",
@@ -3269,9 +3254,9 @@ dependencies = [
[[package]]
name = "unicode_names2_generator"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6dfc680313e95bc6637fa278cd7a22390c3c2cd7b8b2bd28755bc6c0fc811e7"
checksum = "4d0d66ab60be9799a70f8eb227ea43da7dcc47561dd9102cbadacfe0930113f7"
dependencies = [
"getopts",
"log",
@@ -3340,7 +3325,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
]
[[package]]
@@ -3434,7 +3419,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
"wasm-bindgen-shared",
]
@@ -3468,7 +3453,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.37",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -15,46 +15,47 @@ license = "MIT"
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.6", features = ["derive"] }
clap = { version = "4.4.5", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.34.0", feature = ["filters", "glob"] }
insta = { version = "1.33.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
itertools = { version = "0.11.0" }
libcst = { version = "1.1.0", default-features = false }
log = { version = "0.4.17" }
memchr = { version = "2.6.4" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.1.1" }
proc-macro2 = { version = "1.0.69" }
proc-macro2 = { version = "1.0.67" }
quote = { version = "1.0.23" }
regex = { version = "1.10.2" }
regex = { version = "1.9.5" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
similar = { version = "2.2.1", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.2" }
syn = { version = "2.0.38" }
syn = { version = "2.0.37" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.49" }
toml = { version = "0.7.8" }
tracing = { version = "0.1.39" }
tracing = { version = "0.1.37" }
tracing-indicatif = { version = "0.3.4" }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
unicode-ident = { version = "1.0.12" }
unicode_names2 = { version = "1.2.0" }
unicode_names2 = { version = "1.1.0" }
unicode-width = { version = "0.1.11" }
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "03179b55ebe7e916f1722e18e8f0b87c01616d1f", default-features = false }
[profile.release]
lto = "fat"
codegen-units = 1

View File

@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.0
rev: v0.0.292
hooks:
- id: ruff
```
@@ -172,8 +172,8 @@ If left unspecified, the default configuration is equivalent to:
```toml
[tool.ruff]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
select = ["E4", "E7", "E9", "F"]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
@@ -239,7 +239,7 @@ Rust as a first-party feature.
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
stylistic rules made obsolete by the use of a formatter, like
stylistic rules made obsolete by the use of an autoformatter, like
[Black](https://github.com/psf/black).
If you're just getting started with Ruff, **the default rule set is a great place to start**: it

View File

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

View File

@@ -13,7 +13,6 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
use ruff_linter::rules::flake8_tidy_imports::settings::Strictness;
use ruff_linter::rules::pydocstyle::settings::Convention;
use ruff_linter::settings::types::PythonVersion;
use ruff_linter::settings::DEFAULT_SELECTORS;
use ruff_linter::warn_user;
use ruff_workspace::options::{
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
@@ -26,6 +25,11 @@ use super::external_config::ExternalConfig;
use super::plugin::Plugin;
use super::{parser, plugin};
const DEFAULT_SELECTORS: &[RuleSelector] = &[
RuleSelector::Linter(Linter::Pyflakes),
RuleSelector::Linter(Linter::Pycodestyle),
];
pub(crate) fn convert(
config: &HashMap<String, HashMap<String, Option<String>>>,
external_config: &ExternalConfig,

View File

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

View File

@@ -9,7 +9,6 @@ use ruff_linter::logging::LogLevel;
use ruff_linter::registry::Rule;
use ruff_linter::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
UnsafeFixes,
};
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
use ruff_workspace::configuration::{Configuration, RuleSelection};
@@ -77,18 +76,15 @@ pub enum Command {
pub struct CheckCommand {
/// List of files or directories to check.
pub files: Vec<PathBuf>,
/// Apply fixes to resolve lint violations.
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
/// Apply automatic fixes to resolve lint violations.
/// Use `--no-fix` to disable or `--fix-suggested` to include suggested fixes.
#[arg(long, overrides_with("no_fix"))]
fix: bool,
#[clap(long, overrides_with("fix"), hide = true)]
/// Apply automatic and suggested fixes to resolve lint violations.
#[arg(long, overrides_with_all(["fix", "no_fix"]))]
fix_suggested: bool,
#[clap(long, overrides_with_all(["fix", "fix_suggested"]), hide = true)]
no_fix: bool,
/// Include fixes that may not retain the original intent of the code.
/// Use `--no-unsafe-fixes` to disable.
#[arg(long, overrides_with("no_unsafe_fixes"))]
unsafe_fixes: bool,
#[arg(long, overrides_with("unsafe_fixes"), hide = true)]
no_unsafe_fixes: bool,
/// Show violations with source code.
/// Use `--no-show-source` to disable.
#[arg(long, overrides_with("no_show_source"))]
@@ -108,7 +104,7 @@ pub struct CheckCommand {
#[arg(short, long)]
pub watch: bool,
/// Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`.
/// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes.
/// Use `--no-fix-only` to disable or `--fix-suggested` to include suggested fixes.
#[arg(long, overrides_with("no_fix_only"))]
fix_only: bool,
#[clap(long, overrides_with("fix_only"), hide = true)]
@@ -117,6 +113,16 @@ pub struct CheckCommand {
#[arg(long)]
ignore_noqa: bool,
/// Output serialization format for violations. (Deprecated: Use `--output-format` instead).
#[arg(
long,
value_enum,
env = "RUFF_FORMAT",
conflicts_with = "output_format",
hide = true
)]
pub format: Option<SerializationFormat>,
/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
pub output_format: Option<SerializationFormat>,
@@ -494,10 +500,9 @@ impl CheckCommand {
cache_dir: self.cache_dir,
fix: resolve_bool_arg(self.fix, self.no_fix),
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
.map(UnsafeFixes::from),
fix_suggested: Some(self.fix_suggested),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
output_format: self.output_format,
output_format: self.output_format.or(self.format),
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
},
)
@@ -598,7 +603,7 @@ pub struct CliOverrides {
pub cache_dir: Option<PathBuf>,
pub fix: Option<bool>,
pub fix_only: Option<bool>,
pub unsafe_fixes: Option<UnsafeFixes>,
pub fix_suggested: Option<bool>,
pub force_exclude: Option<bool>,
pub output_format: Option<SerializationFormat>,
pub show_fixes: Option<bool>,
@@ -624,8 +629,8 @@ impl ConfigurationTransformer for CliOverrides {
if let Some(fix_only) = &self.fix_only {
config.fix_only = Some(*fix_only);
}
if self.unsafe_fixes.is_some() {
config.unsafe_fixes = self.unsafe_fixes;
if self.fix_suggested.is_some() {
config.fix_suggested = self.fix_suggested;
}
config.lint.rule_selections.push(RuleSelection {
select: self.select.clone(),

View File

@@ -338,7 +338,6 @@ pub(crate) fn init(path: &Path) -> Result<()> {
#[cfg(test)]
mod tests {
use filetime::{set_file_mtime, FileTime};
use ruff_linter::settings::types::UnsafeFixes;
use std::env::temp_dir;
use std::fs;
use std::io;
@@ -410,8 +409,7 @@ mod tests {
&settings.linter,
Some(&cache),
flags::Noqa::Enabled,
flags::FixMode::Generate,
UnsafeFixes::Enabled,
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
)
.unwrap();
if diagnostics
@@ -456,8 +454,7 @@ mod tests {
&settings.linter,
Some(&cache),
flags::Noqa::Enabled,
flags::FixMode::Generate,
UnsafeFixes::Enabled,
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
)
.unwrap();
}
@@ -714,8 +711,7 @@ mod tests {
&self.settings.linter,
Some(cache),
flags::Noqa::Enabled,
flags::FixMode::Generate,
UnsafeFixes::Enabled,
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
)
}
}

View File

@@ -11,7 +11,6 @@ use itertools::Itertools;
use log::{debug, error, warn};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ruff_linter::settings::types::UnsafeFixes;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
@@ -37,7 +36,6 @@ pub(crate) fn check(
cache: flags::Cache,
noqa: flags::Noqa,
fix_mode: flags::FixMode,
unsafe_fixes: UnsafeFixes,
) -> Result<Diagnostics> {
// Collect all the Python files to check.
let start = Instant::now();
@@ -121,16 +119,7 @@ pub(crate) fn check(
}
});
lint_path(
path,
package,
&settings.linter,
cache,
noqa,
fix_mode,
unsafe_fixes,
)
.map_err(|e| {
lint_path(path, package, &settings.linter, cache, noqa, fix_mode).map_err(|e| {
(Some(path.to_owned()), {
let mut error = e.to_string();
for cause in e.chain() {
@@ -210,10 +199,9 @@ fn lint_path(
cache: Option<&Cache>,
noqa: flags::Noqa,
fix_mode: flags::FixMode,
unsafe_fixes: UnsafeFixes,
) -> Result<Diagnostics> {
let result = catch_unwind(|| {
crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode, unsafe_fixes)
crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode)
});
match result {
@@ -245,8 +233,6 @@ mod test {
use std::os::unix::fs::OpenOptionsExt;
use anyhow::Result;
use ruff_linter::settings::types::UnsafeFixes;
use rustc_hash::FxHashMap;
use tempfile::TempDir;
@@ -298,8 +284,7 @@ mod test {
&CliOverrides::default(),
flags::Cache::Disabled,
flags::Noqa::Disabled,
flags::FixMode::Generate,
UnsafeFixes::Enabled,
flags::FixMode::Generate(flags::SuggestedFixes::Apply),
)
.unwrap();
let mut output = Vec::new();

View File

@@ -30,7 +30,7 @@ pub(crate) fn check_stdin(
let mut diagnostics = lint_stdin(
filename,
package_root,
stdin,
&stdin,
&pyproject_config.settings,
noqa,
fix_mode,

View File

@@ -66,21 +66,23 @@ pub(crate) fn format(
.filter_map(|entry| {
match entry {
Ok(entry) => {
let path = entry.into_path();
let path = entry.path();
let SourceType::Python(source_type) = SourceType::from(&path) else {
let SourceType::Python(source_type) = SourceType::from(path) else {
// Ignore any non-Python files.
return None;
};
let resolved_settings = resolver.resolve(&path, &pyproject_config);
let resolved_settings = resolver.resolve(path, &pyproject_config);
Some(
match catch_unwind(|| {
format_path(&path, &resolved_settings.formatter, source_type, mode)
format_path(path, &resolved_settings.formatter, source_type, mode)
}) {
Ok(inner) => inner.map(|result| FormatPathResult { path, result }),
Err(error) => Err(FormatCommandError::Panic(Some(path), error)),
Ok(inner) => inner,
Err(error) => {
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
}
},
)
}
@@ -104,7 +106,7 @@ pub(crate) fn format(
error!("{error}");
}
let summary = FormatSummary::new(results.as_slice(), mode);
let summary = FormatResultSummary::new(results, mode);
// Report on the formatting changes.
if log_level >= LogLevel::Default {
@@ -124,7 +126,7 @@ pub(crate) fn format(
}
FormatMode::Check => {
if errors.is_empty() {
if summary.any_formatted() {
if summary.formatted > 0 {
Ok(ExitStatus::Failure)
} else {
Ok(ExitStatus::Success)
@@ -143,11 +145,11 @@ fn format_path(
settings: &FormatterSettings,
source_type: PySourceType,
mode: FormatMode,
) -> Result<FormatResult, FormatCommandError> {
) -> Result<FormatCommandResult, FormatCommandError> {
// Extract the sources from the file.
let source_kind = match SourceKind::from_path(path, source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => return Ok(FormatResult::Unchanged),
Ok(None) => return Ok(FormatCommandResult::Unchanged),
Err(err) => {
return Err(FormatCommandError::Read(Some(path.to_path_buf()), err));
}
@@ -164,9 +166,9 @@ fn format_path(
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
}
Ok(FormatResult::Formatted)
Ok(FormatCommandResult::Formatted)
}
FormattedSource::Unchanged(_) => Ok(FormatResult::Unchanged),
FormattedSource::Unchanged(_) => Ok(FormatCommandResult::Unchanged),
}
}
@@ -178,11 +180,11 @@ pub(crate) enum FormattedSource {
Unchanged(SourceKind),
}
impl From<FormattedSource> for FormatResult {
impl From<FormattedSource> for FormatCommandResult {
fn from(value: FormattedSource) -> Self {
match value {
FormattedSource::Formatted(_) => FormatResult::Formatted,
FormattedSource::Unchanged(_) => FormatResult::Unchanged,
FormattedSource::Formatted(_) => FormatCommandResult::Formatted,
FormattedSource::Unchanged(_) => FormatCommandResult::Unchanged,
}
}
}
@@ -290,97 +292,73 @@ pub(crate) fn format_source(
}
}
/// The result of an individual formatting operation.
#[derive(Debug, Clone, Copy, is_macro::Is)]
pub(crate) enum FormatResult {
pub(crate) enum FormatCommandResult {
/// The file was formatted.
Formatted,
/// The file was unchanged, as the formatted contents matched the existing contents.
Unchanged,
}
/// The coupling of a [`FormatResult`] with the path of the file that was analyzed.
#[derive(Debug)]
struct FormatPathResult {
path: PathBuf,
result: FormatResult,
}
/// A summary of the formatting results.
#[derive(Debug)]
struct FormatSummary<'a> {
/// The individual formatting results.
results: &'a [FormatPathResult],
struct FormatResultSummary {
/// The format mode that was used.
mode: FormatMode,
/// The number of files that were formatted.
formatted: usize,
/// The number of files that were unchanged.
unchanged: usize,
}
impl<'a> FormatSummary<'a> {
fn new(results: &'a [FormatPathResult], mode: FormatMode) -> Self {
Self { results, mode }
}
/// Returns `true` if any of the files require formatting.
fn any_formatted(&self) -> bool {
self.results
.iter()
.any(|result| result.result.is_formatted())
}
}
impl Display for FormatSummary<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Compute the number of changed and unchanged files.
let mut formatted = 0u32;
let mut unchanged = 0u32;
for result in self.results {
match result.result {
FormatResult::Formatted => {
// If we're running in check mode, report on any files that would be formatted.
if self.mode.is_check() {
writeln!(
f,
"Would reformat: {}",
fs::relativize_path(&result.path).bold()
)?;
}
formatted += 1;
}
FormatResult::Unchanged => unchanged += 1,
impl FormatResultSummary {
fn new(diagnostics: Vec<FormatCommandResult>, mode: FormatMode) -> Self {
let mut summary = Self {
mode,
formatted: 0,
unchanged: 0,
};
for diagnostic in diagnostics {
match diagnostic {
FormatCommandResult::Formatted => summary.formatted += 1,
FormatCommandResult::Unchanged => summary.unchanged += 1,
}
}
summary
}
}
// Write out a summary of the formatting results.
if formatted > 0 && unchanged > 0 {
impl Display for FormatResultSummary {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.formatted > 0 && self.unchanged > 0 {
write!(
f,
"{} file{} {}, {} file{} left unchanged",
formatted,
if formatted == 1 { "" } else { "s" },
self.formatted,
if self.formatted == 1 { "" } else { "s" },
match self.mode {
FormatMode::Write => "reformatted",
FormatMode::Check => "would be reformatted",
},
unchanged,
if unchanged == 1 { "" } else { "s" },
self.unchanged,
if self.unchanged == 1 { "" } else { "s" },
)
} else if formatted > 0 {
} else if self.formatted > 0 {
write!(
f,
"{} file{} {}",
formatted,
if formatted == 1 { "" } else { "s" },
self.formatted,
if self.formatted == 1 { "" } else { "s" },
match self.mode {
FormatMode::Write => "reformatted",
FormatMode::Check => "would be reformatted",
}
)
} else if unchanged > 0 {
} else if self.unchanged > 0 {
write!(
f,
"{} file{} left unchanged",
unchanged,
if unchanged == 1 { "" } else { "s" },
self.unchanged,
if self.unchanged == 1 { "" } else { "s" },
)
} else {
Ok(())

View File

@@ -2,7 +2,7 @@ use std::io::stdout;
use std::path::Path;
use anyhow::Result;
use log::error;
use log::warn;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::{PySourceType, SourceType};
@@ -10,7 +10,7 @@ use ruff_workspace::resolver::python_file_at_path;
use ruff_workspace::FormatterSettings;
use crate::args::{CliOverrides, FormatArguments};
use crate::commands::format::{format_source, FormatCommandError, FormatMode, FormatResult};
use crate::commands::format::{format_source, FormatCommandError, FormatCommandResult, FormatMode};
use crate::resolve::resolve;
use crate::stdin::read_from_stdin;
use crate::ExitStatus;
@@ -59,7 +59,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
}
},
Err(err) => {
error!("{err}");
warn!("{err}");
Ok(ExitStatus::Error)
}
}
@@ -71,14 +71,14 @@ fn format_source_code(
settings: &FormatterSettings,
source_type: PySourceType,
mode: FormatMode,
) -> Result<FormatResult, FormatCommandError> {
) -> Result<FormatCommandResult, FormatCommandError> {
// Read the source from stdin.
let source_code = read_from_stdin()
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err.into()))?;
let source_kind = match SourceKind::from_source_code(source_code, source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => return Ok(FormatResult::Unchanged),
Ok(None) => return Ok(FormatCommandResult::Unchanged),
Err(err) => {
return Err(FormatCommandError::Read(path.map(Path::to_path_buf), err));
}
@@ -96,5 +96,5 @@ fn format_source_code(
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
}
Ok(FormatResult::from(formatted))
Ok(FormatCommandResult::from(formatted))
}

View File

@@ -35,7 +35,7 @@ impl<'a> Explanation<'a> {
message_formats: rule.message_formats(),
fix,
explanation: rule.explanation(),
preview: rule.is_preview() || rule.is_nursery(),
preview: rule.is_preview(),
}
}
}
@@ -61,7 +61,7 @@ fn format_rule_text(rule: Rule) -> String {
output.push('\n');
}
if rule.is_preview() || rule.is_nursery() {
if rule.is_preview() {
output.push_str(
r#"This rule is in preview and is not stable. The `--preview` flag is required for use."#,
);

View File

@@ -1,7 +1,8 @@
#![cfg_attr(target_family = "wasm", allow(dead_code))]
use std::fs::File;
use std::fs::{write, File};
use std::io;
use std::io::{BufWriter, Write};
use std::ops::AddAssign;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
@@ -11,8 +12,8 @@ use anyhow::{Context, Result};
use colored::Colorize;
use filetime::FileTime;
use log::{debug, error, warn};
use ruff_linter::settings::types::UnsafeFixes;
use rustc_hash::FxHashMap;
use similar::TextDiff;
use ruff_diagnostics::Diagnostic;
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
@@ -24,7 +25,7 @@ use ruff_linter::settings::{flags, LinterSettings};
use ruff_linter::source_kind::{SourceError, SourceKind};
use ruff_linter::{fs, IOError, SyntaxError};
use ruff_macros::CacheKey;
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
use ruff_notebook::{Cell, Notebook, NotebookError, NotebookIndex};
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::{SourceType, TomlSourceType};
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
@@ -169,7 +170,6 @@ pub(crate) fn lint_path(
cache: Option<&Cache>,
noqa: flags::Noqa,
fix_mode: flags::FixMode,
unsafe_fixes: UnsafeFixes,
) -> Result<Diagnostics> {
// Check the cache.
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
@@ -241,44 +241,109 @@ pub(crate) fn lint_path(
error: parse_error,
},
fixed,
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
if let Ok(FixerResult {
result,
transformed,
fixed,
}) = lint_fix(
path,
package,
noqa,
unsafe_fixes,
settings,
&source_kind,
source_type,
) {
if !fixed.is_empty() {
match fix_mode {
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
flags::FixMode::Diff => {
source_kind.diff(
transformed.as_ref(),
Some(path),
&mut io::stdout().lock(),
)?;
) = match fix_mode {
flags::FixMode::Apply(fix_suggested) | flags::FixMode::Diff(fix_suggested) => {
if let Ok(FixerResult {
result,
transformed,
fixed,
}) = lint_fix(
path,
package,
noqa,
settings,
&source_kind,
source_type,
fix_suggested,
) {
if !fixed.is_empty() {
match fix_mode {
flags::FixMode::Apply(_) => match transformed.as_ref() {
SourceKind::Python(transformed) => {
write(path, transformed.as_bytes())?;
}
SourceKind::IpyNotebook(notebook) => {
let mut writer = BufWriter::new(File::create(path)?);
notebook.write(&mut writer)?;
}
},
flags::FixMode::Diff(_) => {
match transformed.as_ref() {
SourceKind::Python(transformed) => {
let mut stdout = io::stdout().lock();
TextDiff::from_lines(source_kind.source_code(), transformed)
.unified_diff()
.header(
&fs::relativize_path(path),
&fs::relativize_path(path),
)
.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
SourceKind::IpyNotebook(dest_notebook) => {
// We need to load the notebook again, since we might've
// mutated it.
let src_notebook = source_kind.as_ipy_notebook().unwrap();
let mut stdout = io::stdout().lock();
// Cell indices are 1-based.
for ((idx, src_cell), dest_cell) in (1u32..)
.zip(src_notebook.cells().iter())
.zip(dest_notebook.cells().iter())
{
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
(src_cell, dest_cell)
else {
continue;
};
TextDiff::from_lines(
&src_code_cell.source.to_string(),
&dest_code_cell.source.to_string(),
)
.unified_diff()
// Jupyter notebook cells don't necessarily have a newline
// at the end. For example,
//
// ```python
// print("hello")
// ```
//
// For a cell containing the above code, there'll only be one line,
// and it won't have a newline at the end. If it did, there'd be
// two lines, and the second line would be empty:
//
// ```python
// print("hello")
//
// ```
.missing_newline_hint(false)
.header(
&format!("{}:cell {}", &fs::relativize_path(path), idx),
&format!("{}:cell {}", &fs::relativize_path(path), idx),
)
.to_writer(&mut stdout)?;
}
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
}
flags::FixMode::Generate(_) => {}
}
flags::FixMode::Generate => {}
}
(result, fixed)
} else {
// If we fail to fix, lint the original source code.
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
let fixed = FxHashMap::default();
(result, fixed)
}
(result, fixed)
} else {
// If we fail to fix, lint the original source code.
}
flags::FixMode::Generate(_) => {
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
let fixed = FxHashMap::default();
(result, fixed)
}
} else {
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
let fixed = FxHashMap::default();
(result, fixed)
};
let imports = imports.unwrap_or_default();
@@ -329,7 +394,7 @@ pub(crate) fn lint_path(
pub(crate) fn lint_stdin(
path: Option<&Path>,
package: Option<&Path>,
contents: String,
contents: &str,
settings: &Settings,
noqa: flags::Noqa,
fix_mode: flags::FixMode,
@@ -340,7 +405,7 @@ pub(crate) fn lint_stdin(
};
// Extract the sources from the file.
let source_kind = match SourceKind::from_source_code(contents, source_type) {
let source_kind = match SourceKind::from_source_code(contents.to_string(), source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => return Ok(Diagnostics::default()),
Err(err) => {
@@ -355,37 +420,70 @@ pub(crate) fn lint_stdin(
error: parse_error,
},
fixed,
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
if let Ok(FixerResult {
result,
transformed,
fixed,
}) = lint_fix(
path.unwrap_or_else(|| Path::new("-")),
package,
noqa,
settings.unsafe_fixes,
&settings.linter,
&source_kind,
source_type,
) {
match fix_mode {
flags::FixMode::Apply => {
// Write the contents to stdout, regardless of whether any errors were fixed.
transformed.write(&mut io::stdout().lock())?;
}
flags::FixMode::Diff => {
// But only write a diff if it's non-empty.
if !fixed.is_empty() {
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
) = match fix_mode {
flags::FixMode::Apply(fix_suggested) | flags::FixMode::Diff(fix_suggested) => {
if let Ok(FixerResult {
result,
transformed,
fixed,
}) = lint_fix(
path.unwrap_or_else(|| Path::new("-")),
package,
noqa,
&settings.linter,
&source_kind,
source_type,
fix_suggested,
) {
match fix_mode {
flags::FixMode::Apply(_) => {
// Write the contents to stdout, regardless of whether any errors were fixed.
io::stdout().write_all(transformed.source_code().as_bytes())?;
}
}
flags::FixMode::Generate => {}
}
flags::FixMode::Diff(_) => {
// But only write a diff if it's non-empty.
if !fixed.is_empty() {
let text_diff = TextDiff::from_lines(
source_kind.source_code(),
transformed.source_code(),
);
let mut unified_diff = text_diff.unified_diff();
if let Some(path) = path {
unified_diff
.header(&fs::relativize_path(path), &fs::relativize_path(path));
}
(result, fixed)
} else {
// If we fail to fix, lint the original source code.
let mut stdout = io::stdout().lock();
unified_diff.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
flags::FixMode::Generate(_) => {}
}
(result, fixed)
} else {
// If we fail to fix, lint the original source code.
let result = lint_only(
path.unwrap_or_else(|| Path::new("-")),
package,
&settings.linter,
noqa,
&source_kind,
source_type,
);
let fixed = FxHashMap::default();
// Write the contents to stdout anyway.
if fix_mode.is_apply() {
io::stdout().write_all(contents.as_bytes())?;
}
(result, fixed)
}
}
flags::FixMode::Generate(_) => {
let result = lint_only(
path.unwrap_or_else(|| Path::new("-")),
package,
@@ -395,25 +493,8 @@ pub(crate) fn lint_stdin(
source_type,
);
let fixed = FxHashMap::default();
// Write the contents to stdout anyway.
if fix_mode.is_apply() {
source_kind.write(&mut io::stdout().lock())?;
}
(result, fixed)
}
} else {
let result = lint_only(
path.unwrap_or_else(|| Path::new("-")),
package,
&settings.linter,
noqa,
&source_kind,
source_type,
);
let fixed = FxHashMap::default();
(result, fixed)
};
let imports = imports.unwrap_or_default();

View File

@@ -6,12 +6,11 @@ use std::sync::mpsc::channel;
use anyhow::Result;
use clap::CommandFactory;
use colored::Colorize;
use log::warn;
use notify::{recommended_watcher, RecursiveMode, Watcher};
use ruff_linter::logging::{set_up_logging, LogLevel};
use ruff_linter::settings::flags::FixMode;
use ruff_linter::settings::flags::{FixMode, SuggestedFixes};
use ruff_linter::settings::types::SerializationFormat;
use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings;
@@ -105,6 +104,8 @@ pub fn run(
}: Args,
) -> Result<ExitStatus> {
{
use colored::Colorize;
let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
#[allow(clippy::print_stderr)]
@@ -165,7 +166,10 @@ pub fn run(
}
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
warn_user_once!("`ruff format` is not yet stable, and subject to change in future versions.");
warn_user_once!(
"`ruff format` is a work-in-progress, subject to change at any time, and intended only for \
experimentation."
);
let (cli, overrides) = args.partition();
@@ -177,6 +181,14 @@ fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
}
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
if args.format.is_some() {
if std::env::var("RUFF_FORMAT").is_ok() {
warn_user!("The environment variable `RUFF_FORMAT` is deprecated. Use `RUFF_OUTPUT_FORMAT` instead.");
} else {
warn_user!("The argument `--format=<FORMAT>` is deprecated. Use `--output-format=<FORMAT>` instead.");
}
}
let (cli, overrides) = args.partition();
// Construct the "default" settings. These are used when no `pyproject.toml`
@@ -196,7 +208,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
}
_ => Box::new(BufWriter::new(io::stdout())),
};
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
if cli.show_settings {
commands::show_settings::show_settings(
@@ -217,7 +228,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
let Settings {
fix,
fix_only,
unsafe_fixes,
fix_suggested,
output_format,
show_fixes,
show_source,
@@ -230,14 +241,21 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
// print them to stdout, if we're reading from stdin).
// - If `--diff` or `--fix-only` are set, don't print any violations (only applicable fixes)
// - By default, applicable fixes only include [`Applicablility::Automatic`], but if
// `--unsafe-fixes` is set, then [`Applicablility::Suggested`] fixes are included.
// `--fix--suggested` is set, then [`Applicablility::Suggested`] fixes are included.
let fix_suggested = if fix_suggested {
SuggestedFixes::Apply
} else {
SuggestedFixes::Disable
};
let fix_mode = if cli.diff {
FixMode::Diff
FixMode::Diff(fix_suggested)
} else if fix || fix_only {
FixMode::Apply
FixMode::Apply(fix_suggested)
} else {
FixMode::Generate
// Always generate all fixes, regardless of [`Applicability`], in `generate` mode
FixMode::Generate(SuggestedFixes::Apply)
};
let cache = !cli.no_cache;
@@ -283,17 +301,11 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
return Ok(ExitStatus::Success);
}
let printer = Printer::new(
output_format,
log_level,
fix_mode,
unsafe_fixes,
printer_flags,
);
let printer = Printer::new(output_format, log_level, fix_mode, printer_flags);
if cli.watch {
if output_format != SerializationFormat::Text {
warn_user!("`--output-format text` is always used in watch mode.");
warn_user!("--format 'text' is used in watch mode.");
}
// Configure the file watcher.
@@ -317,7 +329,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
cache.into(),
noqa.into(),
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages)?;
@@ -350,7 +361,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
cache.into(),
noqa.into(),
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages)?;
}
@@ -377,22 +387,18 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
cache.into(),
noqa.into(),
fix_mode,
unsafe_fixes,
)?
};
// Always try to print violations (though the printer itself may suppress output)
// If we're writing fixes via stdin, the transformed source code goes to the writer
// so send the summary to stderr instead
let mut summary_writer = if is_stdin && matches!(fix_mode, FixMode::Apply | FixMode::Diff) {
stderr_writer
} else {
writer
};
if cli.statistics {
printer.write_statistics(&diagnostics, &mut summary_writer)?;
} else {
printer.write_once(&diagnostics, &mut summary_writer)?;
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(fix_mode, FixMode::Apply(_) | FixMode::Diff(_))) {
if cli.statistics {
printer.write_statistics(&diagnostics, &mut writer)?;
} else {
printer.write_once(&diagnostics, &mut writer)?;
}
}
if !cli.exit_zero {

View File

@@ -7,6 +7,7 @@ use anyhow::Result;
use bitflags::bitflags;
use colored::Colorize;
use itertools::{iterate, Itertools};
use ruff_diagnostics::Applicability;
use rustc_hash::FxHashMap;
use serde::Serialize;
@@ -19,8 +20,8 @@ use ruff_linter::message::{
};
use ruff_linter::notify_user;
use ruff_linter::registry::{AsRule, Rule};
use ruff_linter::settings::flags::{self};
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
use ruff_linter::settings::flags::{self, SuggestedFixes};
use ruff_linter::settings::types::SerializationFormat;
use crate::diagnostics::Diagnostics;
@@ -73,7 +74,6 @@ pub(crate) struct Printer {
format: SerializationFormat,
log_level: LogLevel,
fix_mode: flags::FixMode,
unsafe_fixes: UnsafeFixes,
flags: Flags,
}
@@ -82,14 +82,12 @@ impl Printer {
format: SerializationFormat,
log_level: LogLevel,
fix_mode: flags::FixMode,
unsafe_fixes: UnsafeFixes,
flags: Flags,
) -> Self {
Self {
format,
log_level,
fix_mode,
unsafe_fixes,
flags,
}
}
@@ -102,15 +100,12 @@ impl Printer {
fn write_summary_text(&self, writer: &mut dyn Write, diagnostics: &Diagnostics) -> Result<()> {
if self.log_level >= LogLevel::Default {
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
let fixed = diagnostics
.fixed
.values()
.flat_map(std::collections::HashMap::values)
.sum::<usize>();
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
let fixed = diagnostics
.fixed
.values()
.flat_map(std::collections::HashMap::values)
.sum::<usize>();
let remaining = diagnostics.messages.len();
let total = fixed + remaining;
if fixed > 0 {
@@ -124,83 +119,23 @@ impl Printer {
writeln!(writer, "Found {remaining} error{s}.")?;
}
if let Some(fixables) = fixables {
let fix_prefix = format!("[{}]", "*".cyan());
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
if self.unsafe_fixes.is_enabled() {
if fixables.applicable > 0 {
writeln!(
writer,
"{fix_prefix} {} fixable with the --fix option.",
fixables.applicable
)?;
}
} else {
if fixables.applicable > 0 && fixables.unapplicable_unsafe > 0 {
let es = if fixables.unapplicable_unsafe == 1 {
""
} else {
"es"
};
writeln!(writer,
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
fixables.applicable, fixables.unapplicable_unsafe
)?;
} else if fixables.applicable > 0 {
// Only applicable fixes
writeln!(
writer,
"{fix_prefix} {} fixable with the `--fix` option.",
fixables.applicable,
)?;
} else {
// Only unapplicable fixes
let es = if fixables.unapplicable_unsafe == 1 {
""
} else {
"es"
};
writeln!(writer,
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
fixables.unapplicable_unsafe
)?;
}
}
if !fixables.is_empty() {
writeln!(writer, "{}", fixables.violation_string())?;
}
} else {
// Check if there are unapplied fixes
let unapplied = {
if let Some(fixables) = fixables {
fixables.unapplicable_unsafe
let fixed = diagnostics
.fixed
.values()
.flat_map(std::collections::HashMap::values)
.sum::<usize>();
if fixed > 0 {
let s = if fixed == 1 { "" } else { "s" };
if self.fix_mode.is_apply() {
writeln!(writer, "Fixed {fixed} error{s}.")?;
} else {
0
}
};
if unapplied > 0 {
let es = if unapplied == 1 { "" } else { "es" };
if fixed > 0 {
let s = if fixed == 1 { "" } else { "s" };
if self.fix_mode.is_apply() {
writeln!(writer, "Fixed {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
} else {
writeln!(writer, "Would fix {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
}
} else {
if self.fix_mode.is_apply() {
writeln!(writer, "No errors fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
} else {
writeln!(writer, "No errors would be fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
}
}
} else {
if fixed > 0 {
let s = if fixed == 1 { "" } else { "s" };
if self.fix_mode.is_apply() {
writeln!(writer, "Fixed {fixed} error{s}.")?;
} else {
writeln!(writer, "Would fix {fixed} error{s}.")?;
}
writeln!(writer, "Would fix {fixed} error{s}.")?;
}
}
}
@@ -235,7 +170,7 @@ impl Printer {
}
let context = EmitterContext::new(&diagnostics.notebook_indexes);
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
match self.format {
SerializationFormat::Json => {
@@ -249,10 +184,9 @@ impl Printer {
}
SerializationFormat::Text => {
TextEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_fix_status(show_fix_status(self.fix_mode, &fixables))
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
@@ -268,8 +202,7 @@ impl Printer {
SerializationFormat::Grouped => {
GroupedEmitter::default()
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_unsafe_fixes(self.unsafe_fixes)
.with_show_fix_status(show_fix_status(self.fix_mode, &fixables))
.emit(writer, &diagnostics.messages, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
@@ -419,7 +352,7 @@ impl Printer {
);
}
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
let fixables = FixableStatistics::new(diagnostics, self.fix_mode.suggested_fixes());
if !diagnostics.messages.is_empty() {
if self.log_level >= LogLevel::Default {
@@ -428,9 +361,8 @@ impl Printer {
let context = EmitterContext::new(&diagnostics.notebook_indexes);
TextEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_fix_status(show_fix_status(self.fix_mode, &fixables))
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
}
writer.flush()?;
@@ -453,13 +385,13 @@ fn num_digits(n: usize) -> usize {
}
/// Return `true` if the [`Printer`] should indicate that a rule is fixable.
fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics>) -> bool {
fn show_fix_status(fix_mode: flags::FixMode, fixables: &FixableStatistics) -> bool {
// If we're in application mode, avoid indicating that a rule is fixable.
// If the specific violation were truly fixable, it would've been fixed in
// this pass! (We're occasionally unable to determine whether a specific
// violation is fixable without trying to fix it, so if fix is not
// enabled, we may inadvertently indicate that a rule is fixable.)
(!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes)
(!fix_mode.is_apply()) && fixables.fixes_are_applicable()
}
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
@@ -503,42 +435,70 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
Ok(())
}
/// Statistics for [applicable][ruff_diagnostics::Applicability] fixes.
#[derive(Debug)]
struct FixableStatistics {
applicable: u32,
unapplicable_unsafe: u32,
/// Contains the number of [`Applicability::Automatic`] and [`Applicability::Suggested`] fixes
struct FixableStatistics<'a> {
automatic: u32,
suggested: u32,
apply_suggested: &'a SuggestedFixes,
}
impl FixableStatistics {
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
let mut applicable = 0;
let mut unapplicable_unsafe = 0;
impl<'a> FixableStatistics<'a> {
fn new(diagnostics: &Diagnostics, apply_suggested: &'a SuggestedFixes) -> Self {
let mut automatic = 0;
let mut suggested = 0;
for message in &diagnostics.messages {
if let Some(fix) = &message.fix {
if fix.applies(unsafe_fixes.required_applicability()) {
applicable += 1;
} else {
// Do not include unapplicable fixes at other levels that do not provide an opt-in
if fix.applicability().is_unsafe() {
unapplicable_unsafe += 1;
}
if fix.applicability() == Applicability::Suggested {
suggested += 1;
} else if fix.applicability() == Applicability::Automatic {
automatic += 1;
}
}
}
if applicable == 0 && unapplicable_unsafe == 0 {
None
} else {
Some(Self {
applicable,
unapplicable_unsafe,
})
Self {
automatic,
suggested,
apply_suggested,
}
}
fn any_applicable_fixes(&self) -> bool {
self.applicable > 0
fn fixes_are_applicable(&self) -> bool {
match self.apply_suggested {
SuggestedFixes::Apply => self.automatic > 0 || self.suggested > 0,
SuggestedFixes::Disable => self.automatic > 0,
}
}
/// Returns [`true`] if there aren't any fixes to be displayed
fn is_empty(&self) -> bool {
self.automatic == 0 && self.suggested == 0
}
/// Build the displayed fix status message depending on the types of the remaining fixes.
fn violation_string(&self) -> String {
let automatic_prefix = format!("[{}]", Applicability::Automatic.symbol().cyan());
let suggested_prefix = format!("[{}]", Applicability::Suggested.symbol().cyan());
if self.automatic > 0 && self.suggested > 0 {
format!(
"{automatic_prefix} {} fixable with the --fix option.\n\
{suggested_prefix} {} potentially fixable with the --fix-suggested option.",
self.automatic, self.suggested
)
} else if self.automatic > 0 {
format!(
"{automatic_prefix} {} fixable with the --fix option.",
self.automatic,
)
} else if self.suggested > 0 {
format!(
"{suggested_prefix} {} potentially fixable with the --fix-suggested option.",
self.suggested
)
} else {
String::new()
}
}
}

View File

@@ -39,7 +39,7 @@ if condition:
print('Hy "Micha"') # Should not change quotes
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
"###);
}
@@ -83,7 +83,7 @@ if condition:
print('Should change quotes')
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
"###);
Ok(())
}
@@ -139,12 +139,12 @@ if condition:
print('Should change quotes')
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
"###);
Ok(())
}
/// Since 0.1.0 the legacy format option is no longer supported
/// Tests that the legacy `format` option continues to work but emits a warning.
#[test]
fn legacy_format_option() -> Result<()> {
let tempdir = TempDir::new()?;
@@ -156,29 +156,52 @@ format = "json"
"#,
)?;
insta::with_settings!({filters => vec![
(&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"),
]}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--select", "F401", "--no-cache", "--config"])
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to parse `[RUFF-TOML-PATH]`: TOML parse error at line 2, column 10
|
2 | format = "json"
| ^^^^^^
invalid type: string "json", expected struct FormatOptions
"###);
});
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--select", "F401", "--no-cache", "--config"])
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
import os
"#), @r###"
success: false
exit_code: 1
----- stdout -----
[
{
"code": "F401",
"end_location": {
"column": 10,
"row": 2
},
"filename": "-",
"fix": {
"applicability": "Automatic",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 3
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `os`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`os` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
}
]
----- stderr -----
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
"###);
Ok(())
}

View File

@@ -1,5 +1,6 @@
#![cfg(not(target_family = "wasm"))]
#[cfg(unix)]
use std::fs;
#[cfg(unix)]
use std::fs::Permissions;
@@ -11,13 +12,13 @@ use std::process::Command;
use std::str;
#[cfg(unix)]
use anyhow::Context;
use anyhow::Result;
use anyhow::{Context, Result};
#[cfg(unix)]
use clap::Parser;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
#[cfg(unix)]
use path_absolutize::path_dedot;
#[cfg(unix)]
use tempfile::TempDir;
#[cfg(unix)]
@@ -51,7 +52,7 @@ fn stdin_error() {
----- stdout -----
-:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
"###);
@@ -68,7 +69,7 @@ fn stdin_filename() {
----- stdout -----
F401.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
"###);
@@ -86,7 +87,7 @@ fn stdin_source_type_py() {
----- stdout -----
TCH.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
"###);
@@ -149,7 +150,6 @@ fn stdin_fix_py() {
print(sys.version)
----- stderr -----
Found 1 error (1 fixed, 0 remaining).
"###);
}
@@ -241,83 +241,10 @@ fn stdin_fix_jupyter() {
success: true
exit_code: 0
----- stdout -----
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
"metadata": {},
"source": [
"Foo"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
}
],
"source": [
"print(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
print(1)
----- stderr -----
Found 2 errors (2 fixed, 0 remaining).
"###);
}
@@ -337,8 +264,6 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
print(sys.version)
----- stderr -----
-:3:4: F634 If test is a tuple, which is always `True`
Found 2 errors (1 fixed, 1 remaining).
"###);
}
@@ -485,7 +410,7 @@ fn stdin_format_jupyter() {
}
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
"###);
}
@@ -736,42 +661,6 @@ fn preview_disabled_prefix_empty() {
"###);
}
#[test]
fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
// Does not warn that the selection is empty since the user is not trying to enable the rule
let args = ["--ignore", "CPY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
Found 1 error.
----- stderr -----
"###);
}
#[test]
fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
// Does not warn that the selection is empty since the user is not trying to enable the rule
let args = ["--fixable", "CPY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
Found 1 error.
----- stderr -----
"###);
}
#[test]
fn preview_group_selector() {
// `--select PREVIEW` should error (selector was removed)
@@ -900,7 +789,7 @@ fn check_input_from_argfile() -> Result<()> {
----- stdout -----
/path/to/a.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
"###);
@@ -910,7 +799,9 @@ fn check_input_from_argfile() -> Result<()> {
}
#[test]
fn check_hints_hidden_unsafe_fixes() {
fn displays_fix_applicability_levels() {
// `--fix` should only apply automatic fixes, but should tell the user about `--fix-suggested` if
// there are remaining fixes.
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
@@ -925,17 +816,18 @@ fn check_hints_hidden_unsafe_fixes() {
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
-:1:14: F601 [**] Dictionary key literal `'a'` repeated
-:2:7: UP034 [*] Avoid extraneous parentheses
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
[*] 1 fixable with the --fix option.
[**] 1 potentially fixable with the --fix-suggested option.
----- stderr -----
"###);
}
#[test]
fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
fn displays_remaining_suggested_fixes() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["-", "--output-format", "text", "--no-cache", "--isolated", "--select", "F601"])
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
@@ -943,244 +835,110 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
-:1:14: F601 [**] Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
[**] 1 potentially fixable with the --fix-suggested option.
----- stderr -----
"###);
}
#[test]
fn check_shows_unsafe_fixes_with_opt_in() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format=text",
"--isolated",
"--select",
"F601,UP034",
"--no-cache",
"--unsafe-fixes",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
fn fix_applies_automatic_fixes_only() {
// `--fix` should only apply automatic fixes. Since we're runnnig in `stdin` mode, output shouldn't
// be printed.
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated","--no-cache",
"--select",
"F601,UP034",
"--fix",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
x = {'a': 1, 'a': 1}
print('foo')
----- stderr -----
"###);
}
#[test]
fn fix_applies_automatic_and_suggested_fixes_with_fix_suggested() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated","--no-cache",
"--select",
"F601,UP034",
"--fix",
"--fix-suggested",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 [**] Dictionary key literal `'a'` repeated
-:2:7: UP034 [*] Avoid extraneous parentheses
Found 2 errors.
[*] 2 fixable with the --fix option.
[*] 1 fixable with the --fix option.
[**] 1 potentially fixable with the --fix-suggested option.
----- stderr -----
"###);
}
#[test]
fn fix_applies_safe_fixes_by_default() {
fn diff_shows_automatic_and_suggested_fixes_with_fix_suggested() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--isolated","--no-cache",
"--select",
"F601,UP034",
"--fix",
"--diff",
"--fix-suggested",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
x = {'a': 1, 'a': 1}
print('foo')
@@ -1,2 +1,2 @@
-x = {'a': 1, 'a': 1}
-print(('foo'))
+x = {'a': 1}
+print('foo')
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
"###);
"###
);
}
#[test]
fn fix_applies_unsafe_fixes_with_opt_in() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"F601,UP034",
"--fix",
"--unsafe-fixes",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: true
exit_code: 0
----- stdout -----
x = {'a': 1}
print('foo')
----- stderr -----
Found 2 errors (2 fixed, 0 remaining).
"###);
}
#[test]
fn fix_does_not_apply_display_only_fixes() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"B006",
"--fix",
])
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r###"
success: false
exit_code: 1
----- stdout -----
def add_to_list(item, some_list=[]): ...
----- stderr -----
-:1:33: B006 Do not use mutable data structures for argument defaults
Found 1 error.
"###);
}
#[test]
fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"B006",
"--fix",
"--unsafe-fixes",
])
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r###"
success: false
exit_code: 1
----- stdout -----
def add_to_list(item, some_list=[]): ...
----- stderr -----
-:1:33: B006 Do not use mutable data structures for argument defaults
Found 1 error.
"###);
}
#[test]
fn fix_only_unsafe_fixes_available() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"F601",
"--fix",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
x = {'a': 1, 'a': 1}
print(('foo'))
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
"###);
}
#[test]
fn fix_only_flag_applies_safe_fixes_by_default() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"F601,UP034",
"--fix-only",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: true
exit_code: 0
----- stdout -----
x = {'a': 1, 'a': 1}
print('foo')
----- stderr -----
Fixed 1 error (1 additional fix available with `--unsafe-fixes`).
"###);
}
#[test]
fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"F601,UP034",
"--fix-only",
"--unsafe-fixes",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: true
exit_code: 0
----- stdout -----
x = {'a': 1}
print('foo')
----- stderr -----
Fixed 2 errors.
"###);
}
#[test]
fn diff_shows_safe_fixes_by_default() {
fn diff_shows_automatic_fixes_only() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--isolated","--no-cache",
"--select",
"F601,UP034",
"--diff",
@@ -1197,210 +955,57 @@ fn diff_shows_safe_fixes_by_default() {
----- stderr -----
Would fix 1 error (1 additional fix available with `--unsafe-fixes`).
"###
);
}
#[test]
fn diff_shows_unsafe_fixes_with_opt_in() {
fn fix_only_applies_automatic_and_suggested_fixes_with_fix_suggested() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"F601,UP034",
"--diff",
"--unsafe-fixes",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
@@ -1,2 +1,2 @@
-x = {'a': 1, 'a': 1}
-print(('foo'))
+x = {'a': 1}
+print('foo')
----- stderr -----
Would fix 2 errors.
"###
);
}
#[test]
fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--select",
"B006",
"--diff",
"--unsafe-fixes",
])
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
"###);
}
#[test]
fn diff_only_unsafe_fixes_available() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--isolated",
"--no-cache",
"--isolated","--no-cache",
"--select",
"F601",
"--diff",
"F601,UP034",
"--fix-only",
"--fix-suggested",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
success: true
exit_code: 0
----- stdout -----
----- stderr -----
No errors would be fixed (1 fix available with `--unsafe-fixes`).
"###
);
}
#[test]
fn check_extend_unsafe_fixes() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint]
extend-unsafe-fixes = ["UP034"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--config"])
.arg(&ruff_toml)
.arg("-")
.args([
"--output-format",
"text",
"--no-cache",
"--select",
"F601,UP034",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
-:2:7: UP034 Avoid extraneous parentheses
Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
x = {'a': 1}
print('foo')
----- stderr -----
"###);
Ok(())
}
#[test]
fn check_extend_safe_fixes() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint]
extend-safe-fixes = ["F601"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--config"])
.arg(&ruff_toml)
.arg("-")
fn fix_only_applies_automatic_fixes_only() {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args([
"-",
"--output-format",
"text",
"--no-cache",
"--isolated","--no-cache",
"--select",
"F601,UP034",
"--fix-only",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
@r###"
success: true
exit_code: 0
----- stdout -----
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
-:2:7: UP034 [*] Avoid extraneous parentheses
Found 2 errors.
[*] 2 fixable with the `--fix` option.
x = {'a': 1, 'a': 1}
print('foo')
----- stderr -----
"###);
Ok(())
}
#[test]
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes() -> Result<()> {
// Adding a rule to both options should result in it being treated as unsafe
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint]
extend-unsafe-fixes = ["UP034"]
extend-safe-fixes = ["UP034"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--config"])
.arg(&ruff_toml)
.arg("-")
.args([
"--output-format",
"text",
"--no-cache",
"--select",
"F601,UP034",
])
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
@r###"
success: false
exit_code: 1
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
-:2:7: UP034 Avoid extraneous parentheses
Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
----- stderr -----
"###);
Ok(())
}

View File

@@ -40,7 +40,7 @@ inline-quotes = "single"
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
"###);
@@ -75,7 +75,7 @@ inline-quotes = "single"
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
"###);
@@ -110,7 +110,7 @@ inline-quotes = "single"
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
"###);
@@ -149,7 +149,7 @@ inline-quotes = "single"
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
-:1:19: Q000 [*] Double quotes found but single quotes preferred
Found 3 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
"###);

View File

@@ -17,7 +17,6 @@ exit_code: 1
----- stdout -----
[
{
"cell": null,
"code": "F401",
"end_location": {
"column": 10,
@@ -25,7 +24,7 @@ exit_code: 1
},
"filename": "/path/to/F401.py",
"fix": {
"applicability": "safe",
"applicability": "Automatic",
"edits": [
{
"content": "",

View File

@@ -17,5 +17,4 @@ ruff_text_size = { path = "../ruff_text_size" }
anyhow = { workspace = true }
log = { workspace = true }
is-macro = { workspace = true }
serde = { workspace = true, optional = true, features = [] }

View File

@@ -5,22 +5,36 @@ use ruff_text_size::{Ranged, TextSize};
use crate::edit::Edit;
/// Indicates if a fix can be applied.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
/// Indicates confidence in the correctness of a suggested fix. Rust internally allows comparison
/// of enum values based on their order (see Rust's [enum
/// documentation](https://doc.rust-lang.org/reference/items/enumerations.html)). This allows us to
/// apply [`Fix`]es based on their [`Applicability`].
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Applicability {
/// The fix is unsafe and should only be displayed for manual application by the user.
/// The fix is likely to be incorrect or the resulting code may have invalid syntax.
Display,
/// The fix has a good chance of being incorrect or the code be incomplete.
/// The fix may result in invalid code if it is applied.
/// The fix should only be manually applied by the user.
Manual,
/// The fix is unsafe and should only be applied with user opt-in.
/// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax.
Unsafe,
/// The fix may be what the user intended, but it is uncertain.
/// The fix should result in valid code if it is applied.
/// The fix can be applied with user opt-in.
Suggested,
/// The fix is safe and can always be applied.
/// The fix is definitely what the user intended, or it maintains the exact meaning of the code.
Safe,
/// The fix is definitely what the user intended, or maintains the exact meaning of the code.
/// This fix should be automatically applied.
Automatic,
}
impl Applicability {
pub fn symbol(&self) -> &'static str {
match self {
Self::Automatic => "*",
Self::Suggested => "**",
Self::Manual => "-",
}
}
}
/// Indicates the level of isolation required to apply a fix.
@@ -47,62 +61,62 @@ pub struct Fix {
}
impl Fix {
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from an [`Edit`] element.
pub fn safe_edit(edit: Edit) -> Self {
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from an [`Edit`] element.
pub fn automatic(edit: Edit) -> Self {
Self {
edits: vec![edit],
applicability: Applicability::Safe,
applicability: Applicability::Automatic,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from multiple [`Edit`] elements.
pub fn safe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from multiple [`Edit`] elements.
pub fn automatic_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability: Applicability::Safe,
applicability: Applicability::Automatic,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from an [`Edit`] element.
pub fn unsafe_edit(edit: Edit) -> Self {
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from an [`Edit`] element.
pub fn suggested(edit: Edit) -> Self {
Self {
edits: vec![edit],
applicability: Applicability::Unsafe,
applicability: Applicability::Suggested,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from multiple [`Edit`] elements.
pub fn unsafe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from multiple [`Edit`] elements.
pub fn suggested_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability: Applicability::Unsafe,
applicability: Applicability::Suggested,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from an [`Edit`] element .
pub fn display_edit(edit: Edit) -> Self {
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from an [`Edit`] element.
pub fn manual(edit: Edit) -> Self {
Self {
edits: vec![edit],
applicability: Applicability::Display,
applicability: Applicability::Manual,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from multiple [`Edit`] elements.
pub fn display_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from multiple [`Edit`] elements.
pub fn manual_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability: Applicability::Display,
applicability: Applicability::Manual,
isolation_level: IsolationLevel::default(),
}
}
@@ -138,11 +152,4 @@ impl Fix {
pub fn applies(&self, applicability: Applicability) -> bool {
self.applicability >= applicability
}
/// Create a new [`Fix`] with the given [`Applicability`].
#[must_use]
pub fn with_applicability(mut self, applicability: Applicability) -> Self {
self.applicability = applicability;
self
}
}

View File

@@ -20,7 +20,7 @@ rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
static_assertions = { workspace = true }
tracing = { workspace = true }
tracing = { version = "0.1.37", default-features = false, features = ["std"] }
unicode-width = { workspace = true }
[dev-dependencies]

View File

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

View File

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

View File

@@ -70,8 +70,6 @@ settings.set_enable_developer_extras(True)
foo.is_(True)
bar.is_not(False)
next(iter([]), False)
sa.func.coalesce(tbl.c.valid, False)
class Registry:
def __init__(self) -> None:

View File

@@ -8,8 +8,6 @@ Foo.objects.create(**{"_id": some_id}) # PIE804
Foo.objects.create(**{**bar}) # PIE804
foo(**{})
foo(**{**data, "foo": "buzz"})
foo(**buzz)

View File

@@ -28,12 +28,4 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
def func():
from typing import Union as U
# PYI055
x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@@ -21,5 +21,4 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@@ -31,15 +31,6 @@ if isinstance(a, bool) or isinstance(b, str):
if isinstance(a, int) or isinstance(a.b, float):
pass
# OK
if isinstance(a, int) or unrelated_condition or isinstance(a, float):
pass
if x or isinstance(a, int) or isinstance(a, float):
pass
if x or y or isinstance(a, int) or isinstance(a, float) or z:
pass
def f():
# OK

View File

@@ -185,24 +185,3 @@ async def f():
if check(x):
return True
return False
async def f():
# SIM110
for x in await iterable:
if check(x):
return True
return False
def f():
# OK (can't turn this into any() because the yield would end up inside a genexp)
for x in iterable:
if (yield check(x)):
return True
return False
def f():
# OK (same)
for x in iterable:
if (yield from check(x)):
return True
return False

View File

@@ -42,7 +42,3 @@ with contextlib.ExitStack():
with contextlib.ExitStack() as exit_stack:
exit_stack_ = exit_stack
f = exit_stack_.enter_context(open("filename"))
# OK (quick one-liner to clear file contents)
open("filename", "w").close()
pathlib.Path("filename").open("w").close()

View File

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

View File

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

View File

@@ -33,8 +33,6 @@ with open(p) as fp:
fp.read()
open(p).close()
os.getcwdb(p)
os.path.join(p, *q)
os.sep.join(p, *q)
# https://github.com/astral-sh/ruff/issues/7620
def opener(path, flags):

View File

@@ -54,8 +54,3 @@ f"{a=}"
f"{a:=1}"
f"{foo(a=1)}"
f"normal {f"{a=}"} normal"
# Okay as the `=` is used inside a f-string...
print(f"{foo = }")
# ...but then it creates false negatives for now
print(f"{foo(a = 1)}")

View File

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

View File

@@ -1,19 +0,0 @@
"""Test bindings created within annotations."""
from typing import Annotated
foo = [1, 2, 3, 4, 5]
class Bar:
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
# load in the scope of the annotation).
baz: Annotated[
str,
[qux for qux in foo],
]
# OK: Allow named expressions in annotations.
x: (y := 1)
print(y)

View File

@@ -1,21 +0,0 @@
"""Test bindings created within annotations under `__future__` annotations."""
from __future__ import annotations
from typing import Annotated
foo = [1, 2, 3, 4, 5]
class Bar:
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
# load in the scope of the annotation).
baz: Annotated[
str,
[qux for qux in foo],
]
# Error: `y` is not defined.
x: (y := 1)
print(y)

View File

@@ -1,73 +0,0 @@
# OK
1<2 and 'b' and 'c'
1<2 or 'a' and 'b'
1<2 and 'a'
1<2 or 'a'
2>1
1<2 and 'a' or 'b' and 'c'
1<2 and 'a' or 'b' or 'c'
1<2 and 'a' or 'b' or 'c' or (lambda x: x+1)
1<2 and 'a' or 'b' or (lambda x: x+1) or 'c'
default = 'default'
if (not isinstance(default, bool) and isinstance(default, int)) \
or (isinstance(default, str) and default):
pass
docid, token = None, None
(docid is None and token is None) or (docid is not None and token is not None)
vendor, os_version = 'darwin', '14'
vendor == "debian" and os_version in ["12"] or vendor == "ubuntu" and os_version in []
# Don't emit if the parent is an `if` statement.
if (task_id in task_dict and task_dict[task_id] is not task) \
or task_id in used_group_ids:
pass
no_target, is_x64, target = True, False, 'target'
if (no_target and not is_x64) or target == 'ARM_APPL_RUST_TARGET':
pass
# Don't emit if the parent is a `bool_op` expression.
isinstance(val, str) and ((len(val) == 7 and val[0] == "#") or val in enums.NamedColor)
# Errors
1<2 and 'a' or 'b'
(lambda x: x+1) and 'a' or 'b'
'a' and (lambda x: x+1) or 'orange'
val = '#0000FF'
(len(val) == 7 and val[0] == "#") or val in {'green'}
marker = 'marker'
isinstance(marker, dict) and 'field' in marker or marker in {}
def has_oranges(oranges, apples=None) -> bool:
return apples and False or oranges
[x for x in l if a and b or c]
{x: y for x in l if a and b or c}
{x for x in l if a and b or c}
new_list = [
x
for sublist in all_lists
if a and b or c
for x in sublist
if (isinstance(operator, list) and x in operator) or x != operator
]

View File

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

View File

@@ -1,19 +1,8 @@
# Errors
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}.")
for item in {1,}:
print(f"I can count to {item}!")
for item in {
"apples", "lemons", "water"
}: # flags in-line set literals
print(f"I like {item}.")
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -202,8 +202,3 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
"{}".format(
1 # comment
)
# The fixed string will exceed the line length, but it's still smaller than the
# existing line length, so it's fine.
"<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)

View File

@@ -1,9 +0,0 @@
from django.utils.translation import gettext
long = 'long'
split_to = 'split_to'
gettext(
'some super {} and complicated string so that the error code '
'E501 Triggers when this is not {} multi-line'.format(
long, split_to)
)

View File

@@ -196,9 +196,3 @@ if sys.version_info < (3,10000000):
if sys.version_info <= (3,10000000):
print("py3")
if sys.version_info > (3,12):
print("py3")
if sys.version_info >= (3,12):
print("py3")

View File

@@ -1,7 +0,0 @@
import typing
from typing import TypeAlias
# UP040
# Fixes in type stub files should be safe to apply unlike in regular code where runtime behavior could change
x: typing.TypeAlias = int
x: TypeAlias = int

View File

@@ -1,45 +0,0 @@
# Errors.
if 1 in (1,):
print("Single-element tuple")
if 1 in [1]:
print("Single-element list")
if 1 in {1}:
print("Single-element set")
if "a" in "a":
print("Single-element string")
if 1 not in (1,):
print("Check `not in` membership test")
if not 1 in (1,):
print("Check the negated membership test")
# Non-errors.
if 1 in (1, 2):
pass
if 1 in [1, 2]:
pass
if 1 in {1, 2}:
pass
if "a" in "ab":
pass
if 1 == (1,):
pass
if 1 > [1]:
pass
if 1 is {1}:
pass
if "a" == "a":
pass

View File

@@ -38,13 +38,6 @@ y = range(10)
list(range(10))[0]
list(x.y)[0]
list(x["y"])[0]
[*range(10)][0]
[*x["y"]][0]
[*x.y][0]
[* x.y][0]
[
*x.y
][0]
# RUF015 (multi-line)
revision_heads_map_ast = [
@@ -52,12 +45,3 @@ revision_heads_map_ast = [
for a in revision_heads_map_ast_obj.body
if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
][0]
# RUF015 (zip)
list(zip(x, y))[0]
[*zip(x, y)][0]
def test():
zip = list # Overwrite the builtin zip
list(zip(x, y))[0]

View File

@@ -1,7 +0,0 @@
# RUF018
assert (x := 0) == 0
assert x, (y := "error")
# OK
if z := 0:
pass

View File

@@ -1,20 +0,0 @@
d = {}
# RUF019
if "k" in d and d["k"]:
pass
k = "k"
if k in d and d[k]:
pass
if (k) in d and d[k]:
pass
if k in d and d[(k)]:
pass
# OK
v = "k" in d and d["k"]
if f() in d and d[f()]:
pass

View File

@@ -32,10 +32,15 @@ pub(crate) fn bindings(checker: &mut Checker) {
},
binding.range(),
);
diagnostic.try_set_fix(|| {
pyflakes::fixes::remove_exception_handler_assignment(binding, checker.locator)
.map(Fix::safe_edit)
});
if checker.patch(Rule::UnusedVariable) {
diagnostic.try_set_fix(|| {
pyflakes::fixes::remove_exception_handler_assignment(
binding,
checker.locator,
)
.map(Fix::automatic)
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

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

View File

@@ -414,7 +414,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::format_literals(checker, call, &summary);
}
if checker.enabled(Rule::FString) {
pyupgrade::rules::f_strings(checker, call, &summary, value);
pyupgrade::rules::f_strings(
checker,
call,
&summary,
value,
checker.settings.line_length,
);
}
}
}
@@ -786,9 +792,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
pylint::rules::subprocess_run_without_check(checker, call);
}
if checker.enabled(Rule::UnspecifiedEncoding) {
pylint::rules::unspecified_encoding(checker, call);
}
if checker.any_enabled(&[
Rule::PytestRaisesWithoutException,
Rule::PytestRaisesTooBroad,
@@ -1197,9 +1200,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::ComparisonWithItself) {
pylint::rules::comparison_with_itself(checker, left, ops, comparators);
}
if checker.enabled(Rule::LiteralMembership) {
pylint::rules::literal_membership(checker, compare);
}
if checker.enabled(Rule::ComparisonOfConstant) {
pylint::rules::comparison_of_constant(checker, left, ops, comparators);
}
@@ -1224,9 +1224,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
comparators,
);
}
if checker.enabled(Rule::SingleItemMembershipTest) {
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
}
}
Expr::Constant(ast::ExprConstant {
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
@@ -1418,17 +1415,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::RepeatedEqualityComparison) {
pylint::rules::repeated_equality_comparison(checker, bool_op);
}
if checker.enabled(Rule::AndOrTernary) {
pylint::rules::and_or_ternary(checker, bool_op);
}
if checker.enabled(Rule::UnnecessaryKeyCheck) {
ruff::rules::unnecessary_key_check(checker, expr);
}
}
Expr::NamedExpr(..) => {
if checker.enabled(Rule::AssignmentInAssert) {
ruff::rules::assignment_in_assert(checker, expr);
}
}
_ => {}
};

View File

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

View File

@@ -143,6 +143,11 @@ impl<'a> Checker<'a> {
}
impl<'a> Checker<'a> {
/// Return `true` if a patch should be generated for a given [`Rule`].
pub(crate) fn patch(&self, code: Rule) -> bool {
self.settings.rules.should_fix(code)
}
/// Return `true` if a [`Rule`] is disabled by a `noqa` directive.
pub(crate) fn rule_is_ignored(&self, code: Rule, offset: TextSize) -> bool {
// TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`.
@@ -524,7 +529,6 @@ where
);
self.semantic.push_definition(definition);
self.semantic.push_scope(ScopeKind::Function(function_def));
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
self.deferred.functions.push(self.semantic.snapshot());
@@ -563,7 +567,6 @@ where
);
self.semantic.push_definition(definition);
self.semantic.push_scope(ScopeKind::Class(class_def));
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
// Extract any global bindings from the class body.
if let Some(globals) = Globals::from_body(body) {
@@ -582,9 +585,7 @@ where
if let Some(type_params) = type_params {
self.visit_type_params(type_params);
}
// The value in a `type` alias has annotation semantics, in that it's never
// evaluated at runtime.
self.visit_annotation(value);
self.visit_expr(value);
self.semantic.pop_scope();
self.visit_expr(name);
}
@@ -1167,14 +1168,13 @@ where
range: _,
}) = slice.as_ref()
{
let mut iter = elts.iter();
if let Some(expr) = iter.next() {
if let Some(expr) = elts.first() {
self.visit_expr(expr);
for expr in elts.iter().skip(1) {
self.visit_non_type_definition(expr);
}
self.visit_expr_context(ctx);
}
for expr in iter {
self.visit_non_type_definition(expr);
}
self.visit_expr_context(ctx);
} else {
debug!("Found non-Expr::Tuple argument to PEP 593 Annotation.");
}
@@ -1366,7 +1366,7 @@ where
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
self.visit_pattern(&match_case.pattern);
if let Some(expr) = &match_case.guard {
self.visit_boolean_test(expr);
self.visit_expr(expr);
}
self.semantic.push_branch();
@@ -1618,12 +1618,10 @@ impl<'a> Checker<'a> {
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
let parent = self.semantic.current_statement();
// Match the left-hand side of an annotated assignment, like `x` in `x: int`.
if matches!(
parent,
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
) && !self.semantic.in_annotation()
{
) {
self.add_binding(
id,
expr.range(),
@@ -1770,7 +1768,7 @@ impl<'a> Checker<'a> {
bound: Some(bound), ..
}) = type_param
{
self.visit_annotation(bound);
self.visit_expr(bound);
}
}
}

View File

@@ -5,7 +5,7 @@ use ruff_python_parser::TokenKind;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::registry::AsRule;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::logical_lines::{
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_after_comma, space_around_operator,
@@ -38,6 +38,17 @@ pub(crate) fn check_logical_lines(
) -> Vec<Diagnostic> {
let mut context = LogicalLinesContext::new(settings);
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
let should_fix_whitespace_before_parameters =
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
let should_fix_whitespace_after_open_bracket =
settings.rules.should_fix(Rule::WhitespaceAfterOpenBracket);
let should_fix_whitespace_before_close_bracket = settings
.rules
.should_fix(Rule::WhitespaceBeforeCloseBracket);
let should_fix_whitespace_before_punctuation =
settings.rules.should_fix(Rule::WhitespaceBeforePunctuation);
let mut prev_line = None;
let mut prev_indent_level = None;
let indent_char = stylist.indentation().as_char();
@@ -47,7 +58,7 @@ pub(crate) fn check_logical_lines(
space_around_operator(&line, &mut context);
whitespace_around_named_parameter_equals(&line, &mut context);
missing_whitespace_around_operator(&line, &mut context);
missing_whitespace(&line, &mut context);
missing_whitespace(&line, should_fix_missing_whitespace, &mut context);
}
if line.flags().contains(TokenFlags::PUNCTUATION) {
space_after_comma(&line, &mut context);
@@ -57,7 +68,13 @@ pub(crate) fn check_logical_lines(
.flags()
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
{
extraneous_whitespace(&line, &mut context);
extraneous_whitespace(
&line,
&mut context,
should_fix_whitespace_after_open_bracket,
should_fix_whitespace_before_close_bracket,
should_fix_whitespace_before_punctuation,
);
}
if line.flags().contains(TokenFlags::KEYWORD) {
@@ -70,7 +87,11 @@ pub(crate) fn check_logical_lines(
}
if line.flags().contains(TokenFlags::BRACKET) {
whitespace_before_parameters(&line, &mut context);
whitespace_before_parameters(
&line,
should_fix_whitespace_before_parameters,
&mut context,
);
}
// Extract the indentation level.

View File

@@ -109,8 +109,10 @@ pub(crate) fn check_noqa(
if line.matches.is_empty() {
let mut diagnostic =
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
diagnostic.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
if settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic
.set_fix(Fix::suggested(delete_noqa(directive.range(), locator)));
}
diagnostics.push(diagnostic);
}
}
@@ -171,14 +173,18 @@ pub(crate) fn check_noqa(
},
directive.range(),
);
if valid_codes.is_empty() {
diagnostic
.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("# noqa: {}", valid_codes.join(", ")),
directive.range(),
)));
if settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.set_fix(Fix::suggested(delete_noqa(
directive.range(),
locator,
)));
} else {
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
format!("# noqa: {}", valid_codes.join(", ")),
directive.range(),
)));
}
}
diagnostics.push(diagnostic);
}

View File

@@ -71,7 +71,11 @@ pub(crate) fn check_physical_lines(
}
if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file(locator, stylist) {
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}
}

View File

@@ -72,7 +72,7 @@ pub(crate) fn check_tokens(
}
if settings.rules.enabled(Rule::UTF8EncodingDeclaration) {
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer);
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer, settings);
}
if settings.rules.enabled(Rule::InvalidEscapeSequence) {
@@ -83,6 +83,7 @@ pub(crate) fn check_tokens(
indexer,
tok,
*range,
settings.rules.should_fix(Rule::InvalidEscapeSequence),
);
}
}
@@ -108,7 +109,13 @@ pub(crate) fn check_tokens(
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
]) {
pycodestyle::rules::compound_statements(&mut diagnostics, tokens, locator, indexer);
pycodestyle::rules::compound_statements(
&mut diagnostics,
tokens,
locator,
indexer,
settings,
);
}
if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape {
@@ -130,7 +137,7 @@ pub(crate) fn check_tokens(
flake8_implicit_str_concat::rules::implicit(
&mut diagnostics,
tokens,
settings,
&settings.flake8_implicit_str_concat,
locator,
indexer,
);
@@ -141,11 +148,11 @@ pub(crate) fn check_tokens(
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
]) {
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator);
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, settings);
}
if settings.rules.enabled(Rule::ExtraneousParentheses) {
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator);
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator, settings);
}
if is_stub && settings.rules.enabled(Rule::TypeCommentInStub) {
@@ -159,7 +166,7 @@ pub(crate) fn check_tokens(
Rule::ShebangNotFirstLine,
Rule::ShebangMissingPython,
]) {
flake8_executable::rules::from_tokens(tokens, path, locator, &mut diagnostics);
flake8_executable::rules::from_tokens(tokens, path, locator, settings, &mut diagnostics);
}
if settings.rules.any_enabled(&[
@@ -184,7 +191,7 @@ pub(crate) fn check_tokens(
TodoComment::from_comment(comment, *comment_range, i)
})
.collect();
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer);
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer, settings);
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,18 +3,16 @@
use anyhow::{Context, Result};
use ruff_diagnostics::Edit;
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_trivia::{
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
};
use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines};
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::fix::codemods;
use crate::line_width::{LineLength, LineWidthBuilder, TabSize};
/// Return the `Fix` to use when deleting a `Stmt`.
///
@@ -287,75 +285,6 @@ pub(crate) fn pad(content: String, range: TextRange, locator: &Locator) -> Strin
)
}
/// Returns `true` if the fix fits within the maximum configured line length.
pub(crate) fn fits(
fix: &str,
node: AnyNodeRef,
locator: &Locator,
line_length: LineLength,
tab_size: TabSize,
) -> bool {
all_lines_fit(fix, node, locator, line_length.value() as usize, tab_size)
}
/// Returns `true` if the fix fits within the maximum configured line length, or produces lines that
/// are shorter than the maximum length of the existing AST node.
pub(crate) fn fits_or_shrinks(
fix: &str,
node: AnyNodeRef,
locator: &Locator,
line_length: LineLength,
tab_size: TabSize,
) -> bool {
// Use the larger of the line length limit, or the longest line in the existing AST node.
let line_length = std::iter::once(line_length.value() as usize)
.chain(
locator
.slice(locator.lines_range(node.range()))
.universal_newlines()
.map(|line| LineWidthBuilder::new(tab_size).add_str(&line).get()),
)
.max()
.unwrap_or(line_length.value() as usize);
all_lines_fit(fix, node, locator, line_length, tab_size)
}
/// Returns `true` if all lines in the fix are shorter than the given line length.
fn all_lines_fit(
fix: &str,
node: AnyNodeRef,
locator: &Locator,
line_length: usize,
tab_size: TabSize,
) -> bool {
let prefix = locator.slice(TextRange::new(
locator.line_start(node.start()),
node.start(),
));
// Ensure that all lines are shorter than the line length limit.
fix.universal_newlines().enumerate().all(|(idx, line)| {
// If `template` is a multiline string, `col_offset` should only be applied to the first
// line:
// ```
// a = """{} -> offset = col_offset (= 4)
// {} -> offset = 0
// """.format(0, 1) -> offset = 0
// ```
let measured_length = if idx == 0 {
LineWidthBuilder::new(tab_size)
.add_str(prefix)
.add_str(&line)
.get()
} else {
LineWidthBuilder::new(tab_size).add_str(&line).get()
};
measured_length <= line_length
})
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -4,12 +4,12 @@ use std::collections::BTreeSet;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
use ruff_source_file::Locator;
use crate::linter::FixTable;
use crate::registry::{AsRule, Rule};
use crate::settings::types::UnsafeFixes;
use crate::settings::flags::SuggestedFixes;
pub(crate) mod codemods;
pub(crate) mod edits;
@@ -28,9 +28,13 @@ pub(crate) struct FixResult {
pub(crate) fn fix_file(
diagnostics: &[Diagnostic],
locator: &Locator,
unsafe_fixes: UnsafeFixes,
fix_suggested: SuggestedFixes,
) -> Option<FixResult> {
let required_applicability = unsafe_fixes.required_applicability();
let required_applicability = if fix_suggested.is_apply() {
Applicability::Suggested
} else {
Applicability::Automatic
};
let mut with_fixes = diagnostics
.iter()
@@ -156,14 +160,13 @@ mod tests {
use crate::fix::{apply_fixes, FixResult};
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
#[allow(deprecated)]
fn create_diagnostics(edit: impl IntoIterator<Item = Edit>) -> Vec<Diagnostic> {
edit.into_iter()
.map(|edit| Diagnostic {
// The choice of rule here is arbitrary.
// The choice of rule and fix applicability here is arbitarary
kind: MissingNewlineAtEndOfFile.into(),
range: edit.range(),
fix: Some(Fix::safe_edit(edit)),
fix: Some(Fix::automatic(edit)),
parent: None,
})
.collect()

View File

@@ -1,23 +1,19 @@
use ruff_cache::{CacheKey, CacheKeyHasher};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::hash::Hasher;
use std::num::{NonZeroU16, NonZeroU8, ParseIntError};
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthChar;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_macros::CacheKey;
use ruff_text_size::TextSize;
/// The length of a line of text that is considered too long.
///
/// The allowed range of values is 1..=320
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct LineLength(
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16,
);
pub struct LineLength(NonZeroU16);
impl LineLength {
/// Maximum allowed value for a valid [`LineLength`]
@@ -27,10 +23,6 @@ impl LineLength {
pub fn value(&self) -> u16 {
self.0.get()
}
pub fn text_len(&self) -> TextSize {
TextSize::from(u32::from(self.value()))
}
}
impl Default for LineLength {

View File

@@ -8,7 +8,7 @@ use itertools::Itertools;
use log::error;
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Applicability, Diagnostic};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
@@ -32,7 +32,6 @@ use crate::message::Message;
use crate::noqa::add_noqa;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle;
use crate::settings::types::UnsafeFixes;
use crate::settings::{flags, LinterSettings};
use crate::source_kind::SourceKind;
use crate::{directives, fs};
@@ -260,36 +259,6 @@ pub fn check_path(
}
}
// Remove fixes for any rules marked as unfixable.
for diagnostic in &mut diagnostics {
if !settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.fix = None;
}
}
// Update fix applicability to account for overrides
if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() {
for diagnostic in &mut diagnostics {
if let Some(fix) = diagnostic.fix.take() {
// Enforce demotions over promotions so if someone puts a rule in both we are conservative
if fix.applicability().is_safe()
&& settings
.extend_unsafe_fixes
.contains(diagnostic.kind.rule())
{
diagnostic.set_fix(fix.with_applicability(Applicability::Unsafe));
} else if fix.applicability().is_unsafe()
&& settings.extend_safe_fixes.contains(diagnostic.kind.rule())
{
diagnostic.set_fix(fix.with_applicability(Applicability::Safe));
} else {
// Retain the existing fix (will be dropped from `.take()` otherwise)
diagnostic.set_fix(fix);
}
}
}
}
LinterResult::new((diagnostics, imports), error)
}
@@ -451,10 +420,10 @@ pub fn lint_fix<'a>(
path: &Path,
package: Option<&Path>,
noqa: flags::Noqa,
unsafe_fixes: UnsafeFixes,
settings: &LinterSettings,
source_kind: &'a SourceKind,
source_type: PySourceType,
fix_suggested: flags::SuggestedFixes,
) -> Result<FixerResult<'a>> {
let mut transformed = Cow::Borrowed(source_kind);
@@ -527,7 +496,7 @@ pub fn lint_fix<'a>(
code: fixed_contents,
fixes: applied,
source_map,
}) = fix_file(&result.data.0, &locator, unsafe_fixes)
}) = fix_file(&result.data.0, &locator, fix_suggested)
{
if iterations < MAX_ITERATIONS {
// Count the number of fixed errors.

View File

@@ -177,15 +177,18 @@ impl Display for DisplayParseError<'_> {
f,
"cell {cell}{colon}",
cell = jupyter_index
.cell(source_location.row)
.unwrap_or(OneIndexed::MIN),
.cell(source_location.row.get())
.unwrap_or_default(),
colon = ":".cyan(),
)?;
SourceLocation {
row: jupyter_index
.cell_row(source_location.row)
.unwrap_or(OneIndexed::MIN),
row: OneIndexed::new(
jupyter_index
.cell_row(source_location.row.get())
.unwrap_or(1) as usize,
)
.unwrap(),
column: source_location.column,
}
} else {

View File

@@ -35,7 +35,6 @@ impl<'a> Diff<'a> {
impl Display for Diff<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(self.source_code.source_text().len());
let mut last_end = TextSize::default();
@@ -53,10 +52,9 @@ impl Display for Diff<'_> {
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Fix",
Applicability::Unsafe => "Suggested fix",
Applicability::Display => "Possible fix",
Applicability::Automatic => "Fix",
Applicability::Suggested => "Suggested fix",
Applicability::Manual => "Possible fix",
};
writeln!(f, " {}", message.blue())?;

View File

@@ -13,13 +13,11 @@ use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
use crate::message::{
group_messages_by_filename, Emitter, EmitterContext, Message, MessageWithLocation,
};
use crate::settings::types::UnsafeFixes;
#[derive(Default)]
pub struct GroupedEmitter {
show_fix_status: bool,
show_source: bool,
unsafe_fixes: UnsafeFixes,
}
impl GroupedEmitter {
@@ -34,12 +32,6 @@ impl GroupedEmitter {
self.show_source = show_source;
self
}
#[must_use]
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
self.unsafe_fixes = unsafe_fixes;
self
}
}
impl Emitter for GroupedEmitter {
@@ -76,7 +68,6 @@ impl Emitter for GroupedEmitter {
notebook_index: context.notebook_index(message.filename()),
message,
show_fix_status: self.show_fix_status,
unsafe_fixes: self.unsafe_fixes,
show_source: self.show_source,
row_length,
column_length,
@@ -98,7 +89,6 @@ impl Emitter for GroupedEmitter {
struct DisplayGroupedMessage<'a> {
message: MessageWithLocation<'a>,
show_fix_status: bool,
unsafe_fixes: UnsafeFixes,
show_source: bool,
row_length: NonZeroUsize,
column_length: NonZeroUsize,
@@ -125,18 +115,18 @@ impl Display for DisplayGroupedMessage<'_> {
f,
"cell {cell}{sep}",
cell = jupyter_index
.cell(start_location.row)
.unwrap_or(OneIndexed::MIN),
.cell(start_location.row.get())
.unwrap_or_default(),
sep = ":".cyan()
)?;
(
jupyter_index
.cell_row(start_location.row)
.unwrap_or(OneIndexed::MIN),
start_location.column,
.cell_row(start_location.row.get())
.unwrap_or(1) as usize,
start_location.column.get(),
)
} else {
(start_location.row, start_location.column)
(start_location.row.get(), start_location.column.get())
};
writeln!(
@@ -148,8 +138,7 @@ impl Display for DisplayGroupedMessage<'_> {
),
code_and_body = RuleCodeAndBody {
message,
show_fix_status: self.show_fix_status,
unsafe_fixes: self.unsafe_fixes
show_fix_status: self.show_fix_status
},
)?;
@@ -207,7 +196,6 @@ mod tests {
use crate::message::tests::{capture_emitter_output, create_messages};
use crate::message::GroupedEmitter;
use crate::settings::types::UnsafeFixes;
#[test]
fn default() {
@@ -234,15 +222,4 @@ mod tests {
assert_snapshot!(content);
}
#[test]
fn fix_status_unsafe() {
let mut emitter = GroupedEmitter::default()
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
}

View File

@@ -5,8 +5,7 @@ use serde::{Serialize, Serializer};
use serde_json::{json, Value};
use ruff_diagnostics::Edit;
use ruff_notebook::NotebookIndex;
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
use ruff_source_file::SourceCode;
use ruff_text_size::Ranged;
use crate::message::{Emitter, EmitterContext, Message};
@@ -20,9 +19,9 @@ impl Emitter for JsonEmitter {
&mut self,
writer: &mut dyn Write,
messages: &[Message],
context: &EmitterContext,
_context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages, context })?;
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages })?;
Ok(())
}
@@ -30,7 +29,6 @@ impl Emitter for JsonEmitter {
struct ExpandedMessages<'a> {
messages: &'a [Message],
context: &'a EmitterContext<'a>,
}
impl Serialize for ExpandedMessages<'_> {
@@ -41,7 +39,7 @@ impl Serialize for ExpandedMessages<'_> {
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
for message in self.messages {
let value = message_to_json_value(message, self.context);
let value = message_to_json_value(message);
s.serialize_element(&value)?;
}
@@ -49,40 +47,26 @@ impl Serialize for ExpandedMessages<'_> {
}
}
pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) -> Value {
pub(crate) fn message_to_json_value(message: &Message) -> Value {
let source_code = message.file.to_source_code();
let notebook_index = context.notebook_index(message.filename());
let fix = message.fix.as_ref().map(|fix| {
json!({
"applicability": fix.applicability(),
"message": message.kind.suggestion.as_deref(),
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code, notebook_index },
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code },
})
});
let mut start_location = source_code.source_location(message.start());
let mut end_location = source_code.source_location(message.end());
let mut noqa_location = source_code.source_location(message.noqa_offset);
let mut notebook_cell_index = None;
if let Some(notebook_index) = notebook_index {
notebook_cell_index = Some(
notebook_index
.cell(start_location.row)
.unwrap_or(OneIndexed::MIN),
);
start_location = notebook_index.translate_location(&start_location);
end_location = notebook_index.translate_location(&end_location);
noqa_location = notebook_index.translate_location(&noqa_location);
}
let start_location = source_code.source_location(message.start());
let end_location = source_code.source_location(message.end());
let noqa_location = source_code.source_location(message.noqa_offset);
json!({
"code": message.kind.rule().noqa_code().to_string(),
"url": message.kind.rule().url(),
"message": message.kind.body,
"fix": fix,
"cell": notebook_cell_index,
"location": start_location,
"end_location": end_location,
"filename": message.filename(),
@@ -93,7 +77,6 @@ pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext)
struct ExpandedEdits<'a> {
edits: &'a [Edit],
source_code: &'a SourceCode<'a, 'a>,
notebook_index: Option<&'a NotebookIndex>,
}
impl Serialize for ExpandedEdits<'_> {
@@ -104,57 +87,10 @@ impl Serialize for ExpandedEdits<'_> {
let mut s = serializer.serialize_seq(Some(self.edits.len()))?;
for edit in self.edits {
let mut location = self.source_code.source_location(edit.start());
let mut end_location = self.source_code.source_location(edit.end());
if let Some(notebook_index) = self.notebook_index {
// There exists a newline between each cell's source code in the
// concatenated source code in Ruff. This newline doesn't actually
// exists in the JSON source field.
//
// Now, certain edits may try to remove this newline, which means
// the edit will spill over to the first character of the next cell.
// If it does, we need to translate the end location to the last
// character of the previous cell.
match (
notebook_index.cell(location.row),
notebook_index.cell(end_location.row),
) {
(Some(start_cell), Some(end_cell)) if start_cell != end_cell => {
debug_assert_eq!(end_location.column.get(), 1);
let prev_row = end_location.row.saturating_sub(1);
end_location = SourceLocation {
row: notebook_index.cell_row(prev_row).unwrap_or(OneIndexed::MIN),
column: self
.source_code
.source_location(self.source_code.line_end_exclusive(prev_row))
.column,
};
}
(Some(_), None) => {
debug_assert_eq!(end_location.column.get(), 1);
let prev_row = end_location.row.saturating_sub(1);
end_location = SourceLocation {
row: notebook_index.cell_row(prev_row).unwrap_or(OneIndexed::MIN),
column: self
.source_code
.source_location(self.source_code.line_end_exclusive(prev_row))
.column,
};
}
_ => {
end_location = notebook_index.translate_location(&end_location);
}
}
location = notebook_index.translate_location(&location);
}
let value = json!({
"content": edit.content().unwrap_or_default(),
"location": location,
"end_location": end_location
"location": self.source_code.source_location(edit.start()),
"end_location": self.source_code.source_location(edit.end())
});
s.serialize_element(&value)?;
@@ -168,10 +104,7 @@ impl Serialize for ExpandedEdits<'_> {
mod tests {
use insta::assert_snapshot;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages,
};
use crate::message::tests::{capture_emitter_output, create_messages};
use crate::message::JsonEmitter;
#[test]
@@ -181,13 +114,4 @@ mod tests {
assert_snapshot!(content);
}
#[test]
fn notebook_output() {
let mut emitter = JsonEmitter;
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);
}
}

View File

@@ -11,11 +11,11 @@ impl Emitter for JsonLinesEmitter {
&mut self,
writer: &mut dyn Write,
messages: &[Message],
context: &EmitterContext,
_context: &EmitterContext,
) -> anyhow::Result<()> {
let mut w = writer;
for message in messages {
serde_json::to_writer(&mut w, &message_to_json_value(message, context))?;
serde_json::to_writer(&mut w, &message_to_json_value(message))?;
w.write_all(b"\n")?;
}
Ok(())
@@ -27,10 +27,7 @@ mod tests {
use insta::assert_snapshot;
use crate::message::json_lines::JsonLinesEmitter;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages,
};
use crate::message::tests::{capture_emitter_output, create_messages};
#[test]
fn output() {
@@ -39,13 +36,4 @@ mod tests {
assert_snapshot!(content);
}
#[test]
fn notebook_output() {
let mut emitter = JsonLinesEmitter;
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);
}
}

View File

@@ -150,8 +150,7 @@ mod tests {
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
use ruff_notebook::NotebookIndex;
use ruff_source_file::{OneIndexed, SourceFileBuilder};
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::message::{Emitter, EmitterContext, Message};
@@ -179,7 +178,7 @@ def fibonacci(n):
},
TextRange::new(TextSize::from(7), TextSize::from(9)),
)
.with_fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
.with_fix(Fix::suggested(Edit::range_deletion(TextRange::new(
TextSize::from(0),
TextSize::from(10),
))));
@@ -194,7 +193,7 @@ def fibonacci(n):
},
TextRange::new(TextSize::from(94), TextSize::from(95)),
)
.with_fix(Fix::unsafe_edit(Edit::deletion(
.with_fix(Fix::suggested(Edit::deletion(
TextSize::from(94),
TextSize::from(99),
)));
@@ -222,113 +221,6 @@ def fibonacci(n):
]
}
pub(super) fn create_notebook_messages() -> (Vec<Message>, FxHashMap<String, NotebookIndex>) {
let notebook = r"# cell 1
import os
# cell 2
import math
print('hello world')
# cell 3
def foo():
print()
x = 1
";
let unused_import_os = Diagnostic::new(
DiagnosticKind {
name: "UnusedImport".to_string(),
body: "`os` imported but unused".to_string(),
suggestion: Some("Remove unused import: `os`".to_string()),
},
TextRange::new(TextSize::from(16), TextSize::from(18)),
)
.with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(9),
TextSize::from(19),
))));
let unused_import_math = Diagnostic::new(
DiagnosticKind {
name: "UnusedImport".to_string(),
body: "`math` imported but unused".to_string(),
suggestion: Some("Remove unused import: `math`".to_string()),
},
TextRange::new(TextSize::from(35), TextSize::from(39)),
)
.with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(28),
TextSize::from(40),
))));
let unused_variable = Diagnostic::new(
DiagnosticKind {
name: "UnusedVariable".to_string(),
body: "Local variable `x` is assigned to but never used".to_string(),
suggestion: Some("Remove assignment to unused variable `x`".to_string()),
},
TextRange::new(TextSize::from(98), TextSize::from(99)),
)
.with_fix(Fix::unsafe_edit(Edit::deletion(
TextSize::from(94),
TextSize::from(104),
)));
let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish();
let mut notebook_indexes = FxHashMap::default();
notebook_indexes.insert(
"notebook.ipynb".to_string(),
NotebookIndex::new(
vec![
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(2),
],
vec![
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(3),
OneIndexed::from_zero_indexed(0),
OneIndexed::from_zero_indexed(1),
OneIndexed::from_zero_indexed(2),
OneIndexed::from_zero_indexed(3),
],
),
);
let unused_import_os_start = unused_import_os.start();
let unused_import_math_start = unused_import_math.start();
let unused_variable_start = unused_variable.start();
(
vec![
Message::from_diagnostic(
unused_import_os,
notebook_source.clone(),
unused_import_os_start,
),
Message::from_diagnostic(
unused_import_math,
notebook_source.clone(),
unused_import_math_start,
),
Message::from_diagnostic(unused_variable, notebook_source, unused_variable_start),
],
notebook_indexes,
)
}
pub(super) fn capture_emitter_output(
emitter: &mut dyn Emitter,
messages: &[Message],
@@ -340,16 +232,4 @@ def foo():
String::from_utf8(output).expect("Output to be valid UTF-8")
}
pub(super) fn capture_emitter_notebook_output(
emitter: &mut dyn Emitter,
messages: &[Message],
notebook_indexes: &FxHashMap<String, NotebookIndex>,
) -> String {
let context = EmitterContext::new(notebook_indexes);
let mut output: Vec<u8> = Vec::new();
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
}
}

View File

@@ -3,14 +3,14 @@ source: crates/ruff_linter/src/message/grouped.rs
expression: content
---
fib.py:
1:8 F401 `os` imported but unused
1:8 F401 [**] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 Local variable `x` is assigned to but never used
6:5 F841 [**] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""

View File

@@ -1,31 +0,0 @@
---
source: crates/ruff_linter/src/message/grouped.rs
expression: content
---
fib.py:
1:8 F401 [*] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 [*] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,105 +0,0 @@
---
source: crates/ruff_linter/src/message/json.rs
expression: content
---
[
{
"cell": 1,
"code": "F401",
"end_location": {
"column": 10,
"row": 2
},
"filename": "notebook.ipynb",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 10,
"row": 2
},
"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"
},
{
"cell": 2,
"code": "F401",
"end_location": {
"column": 12,
"row": 2
},
"filename": "notebook.ipynb",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 3
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `math`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`math` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
},
{
"cell": 3,
"code": "F841",
"end_location": {
"column": 6,
"row": 4
},
"filename": "notebook.ipynb",
"fix": {
"applicability": "unsafe",
"edits": [
{
"content": "",
"end_location": {
"column": 10,
"row": 4
},
"location": {
"column": 1,
"row": 4
}
}
],
"message": "Remove assignment to unused variable `x`"
},
"location": {
"column": 5,
"row": 4
},
"message": "Local variable `x` is assigned to but never used",
"noqa_row": 4,
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
}
]

View File

@@ -4,7 +4,6 @@ expression: content
---
[
{
"cell": null,
"code": "F401",
"end_location": {
"column": 10,
@@ -12,7 +11,7 @@ expression: content
},
"filename": "fib.py",
"fix": {
"applicability": "unsafe",
"applicability": "Suggested",
"edits": [
{
"content": "",
@@ -37,7 +36,6 @@ expression: content
"url": "https://docs.astral.sh/ruff/rules/unused-import"
},
{
"cell": null,
"code": "F841",
"end_location": {
"column": 6,
@@ -45,7 +43,7 @@ expression: content
},
"filename": "fib.py",
"fix": {
"applicability": "unsafe",
"applicability": "Suggested",
"edits": [
{
"content": "",
@@ -70,7 +68,6 @@ expression: content
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
},
{
"cell": null,
"code": "F821",
"end_location": {
"column": 5,

View File

@@ -1,8 +0,0 @@
---
source: crates/ruff_linter/src/message/json_lines.rs
expression: content
---
{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"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"}
{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":4},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}

View File

@@ -2,7 +2,7 @@
source: crates/ruff_linter/src/message/json_lines.rs
expression: content
---
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
{"cell":null,"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}

View File

@@ -2,14 +2,14 @@
source: crates/ruff_linter/src/message/text.rs
expression: content
---
fib.py:1:8: F401 `os` imported but unused
fib.py:1:8: F401 [**] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
fib.py:6:5: F841 Local variable `x` is assigned to but never used
fib.py:6:5: F841 [**] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""

View File

@@ -1,29 +0,0 @@
---
source: crates/ruff_linter/src/message/text.rs
expression: content
---
fib.py:1:8: F401 [*] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,32 +0,0 @@
---
source: crates/ruff_linter/src/message/text.rs
expression: content
---
notebook.ipynb:cell 1:2:8: F401 [*] `os` imported but unused
|
1 | # cell 1
2 | import os
| ^^ F401
|
= help: Remove unused import: `os`
notebook.ipynb:cell 2:2:8: F401 [*] `math` imported but unused
|
1 | # cell 2
2 | import math
| ^^^^ F401
3 |
4 | print('hello world')
|
= help: Remove unused import: `math`
notebook.ipynb:cell 3:4:5: F841 [*] Local variable `x` is assigned to but never used
|
2 | def foo():
3 | print()
4 | x = 1
| ^ F841
|
= help: Remove assignment to unused variable `x`

View File

@@ -16,24 +16,22 @@ use crate::line_width::{LineWidthBuilder, TabSize};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::AsRule;
use crate::settings::types::UnsafeFixes;
bitflags! {
#[derive(Default)]
struct EmitterFlags: u8 {
/// Whether to show the fix status of a diagnostic.
const SHOW_FIX_STATUS = 0b0000_0001;
const SHOW_FIX_STATUS = 0b0000_0001;
/// Whether to show the diff of a fix, for diagnostics that have a fix.
const SHOW_FIX_DIFF = 0b0000_0010;
const SHOW_FIX_DIFF = 0b0000_0010;
/// Whether to show the source code of a diagnostic.
const SHOW_SOURCE = 0b0000_0100;
const SHOW_SOURCE = 0b0000_0100;
}
}
#[derive(Default)]
pub struct TextEmitter {
flags: EmitterFlags,
unsafe_fixes: UnsafeFixes,
}
impl TextEmitter {
@@ -55,12 +53,6 @@ impl TextEmitter {
self.flags.set(EmitterFlags::SHOW_SOURCE, show_source);
self
}
#[must_use]
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
self.unsafe_fixes = unsafe_fixes;
self
}
}
impl Emitter for TextEmitter {
@@ -87,15 +79,18 @@ impl Emitter for TextEmitter {
writer,
"cell {cell}{sep}",
cell = notebook_index
.cell(start_location.row)
.unwrap_or(OneIndexed::MIN),
.cell(start_location.row.get())
.unwrap_or_default(),
sep = ":".cyan(),
)?;
SourceLocation {
row: notebook_index
.cell_row(start_location.row)
.unwrap_or(OneIndexed::MIN),
row: OneIndexed::new(
notebook_index
.cell_row(start_location.row.get())
.unwrap_or(1) as usize,
)
.unwrap(),
column: start_location.column,
}
} else {
@@ -110,8 +105,7 @@ impl Emitter for TextEmitter {
sep = ":".cyan(),
code_and_body = RuleCodeAndBody {
message,
show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS),
unsafe_fixes: self.unsafe_fixes,
show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS)
}
)?;
@@ -140,33 +134,30 @@ impl Emitter for TextEmitter {
pub(super) struct RuleCodeAndBody<'a> {
pub(crate) message: &'a Message,
pub(crate) show_fix_status: bool,
pub(crate) unsafe_fixes: UnsafeFixes,
}
impl Display for RuleCodeAndBody<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let kind = &self.message.kind;
if self.show_fix_status {
if let Some(fix) = self.message.fix.as_ref() {
// Do not display an indicator for unapplicable fixes
if fix.applies(self.unsafe_fixes.required_applicability()) {
return write!(
f,
"{code} {fix}{body}",
code = kind.rule().noqa_code().to_string().red().bold(),
fix = format_args!("[{}] ", "*".cyan()),
body = kind.body,
);
}
}
};
write!(
f,
"{code} {body}",
code = kind.rule().noqa_code().to_string().red().bold(),
body = kind.body,
)
if self.show_fix_status && self.message.fix.is_some() {
let indicator = self.message.fix.as_ref().unwrap().applicability().symbol();
write!(
f,
"{code} {fix}{body}",
code = kind.rule().noqa_code().to_string().red().bold(),
fix = format_args!("[{}] ", indicator.cyan()),
body = kind.body,
)
} else {
write!(
f,
"{code} {body}",
code = kind.rule().noqa_code().to_string().red().bold(),
body = kind.body,
)
}
}
}
@@ -200,9 +191,9 @@ impl Display for MessageCodeFrame<'_> {
// If we're working with a Jupyter Notebook, skip the lines which are
// outside of the cell containing the diagnostic.
if let Some(index) = self.notebook_index {
let content_start_cell = index.cell(content_start_index).unwrap_or(OneIndexed::MIN);
let content_start_cell = index.cell(content_start_index.get()).unwrap_or_default();
while start_index < content_start_index {
if index.cell(start_index).unwrap_or(OneIndexed::MIN) == content_start_cell {
if index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
break;
}
start_index = start_index.saturating_add(1);
@@ -225,9 +216,9 @@ impl Display for MessageCodeFrame<'_> {
// If we're working with a Jupyter Notebook, skip the lines which are
// outside of the cell containing the diagnostic.
if let Some(index) = self.notebook_index {
let content_end_cell = index.cell(content_end_index).unwrap_or(OneIndexed::MIN);
let content_end_cell = index.cell(content_end_index.get()).unwrap_or_default();
while end_index > content_end_index {
if index.cell(end_index).unwrap_or(OneIndexed::MIN) == content_end_cell {
if index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
break;
}
end_index = end_index.saturating_sub(1);
@@ -267,9 +258,8 @@ impl Display for MessageCodeFrame<'_> {
|| start_index.get(),
|notebook_index| {
notebook_index
.cell_row(start_index)
.unwrap_or(OneIndexed::MIN)
.get()
.cell_row(start_index.get())
.unwrap_or_default() as usize
},
),
annotations: vec![SourceAnnotation {
@@ -351,12 +341,8 @@ struct SourceCode<'a> {
mod tests {
use insta::assert_snapshot;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages,
};
use crate::message::tests::{capture_emitter_output, create_messages};
use crate::message::TextEmitter;
use crate::settings::types::UnsafeFixes;
#[test]
fn default() {
@@ -375,27 +361,4 @@ mod tests {
assert_snapshot!(content);
}
#[test]
fn fix_status_unsafe() {
let mut emitter = TextEmitter::default()
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
#[test]
fn notebook_output() {
let mut emitter = TextEmitter::default()
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);
}
}

View File

@@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::registry::Rule;
use crate::settings::LinterSettings;
use super::super::detection::comment_contains_code;
@@ -66,9 +67,11 @@ pub(crate) fn commented_out_code(
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
diagnostic.set_fix(Fix::display_edit(Edit::range_deletion(
locator.full_lines_range(*range),
)));
if settings.rules.should_fix(Rule::CommentedOutCode) {
diagnostic.set_fix(Fix::manual(Edit::range_deletion(
locator.full_lines_range(*range),
)));
}
diagnostics.push(diagnostic);
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/eradicate/mod.rs
---
ERA001.py:1:1: ERA001 Found commented-out code
ERA001.py:1:1: ERA001 [*] Found commented-out code
|
1 | #import os
| ^^^^^^^^^^ ERA001
@@ -16,7 +16,7 @@ ERA001.py:1:1: ERA001 Found commented-out code
3 2 | #a = 3
4 3 | a = 4
ERA001.py:2:1: ERA001 Found commented-out code
ERA001.py:2:1: ERA001 [*] Found commented-out code
|
1 | #import os
2 | # from foo import junk
@@ -33,7 +33,7 @@ ERA001.py:2:1: ERA001 Found commented-out code
4 3 | a = 4
5 4 | #foo(1, 2, 3)
ERA001.py:3:1: ERA001 Found commented-out code
ERA001.py:3:1: ERA001 [*] Found commented-out code
|
1 | #import os
2 | # from foo import junk
@@ -52,7 +52,7 @@ ERA001.py:3:1: ERA001 Found commented-out code
5 4 | #foo(1, 2, 3)
6 5 |
ERA001.py:5:1: ERA001 Found commented-out code
ERA001.py:5:1: ERA001 [*] Found commented-out code
|
3 | #a = 3
4 | a = 4
@@ -72,7 +72,7 @@ ERA001.py:5:1: ERA001 Found commented-out code
7 6 | def foo(x, y, z):
8 7 | content = 1 # print('hello')
ERA001.py:13:5: ERA001 Found commented-out code
ERA001.py:13:5: ERA001 [*] Found commented-out code
|
11 | # This is a real comment.
12 | # # This is a (nested) comment.
@@ -91,7 +91,7 @@ ERA001.py:13:5: ERA001 Found commented-out code
15 14 |
16 15 | #import os # noqa: ERA001
ERA001.py:21:5: ERA001 Found commented-out code
ERA001.py:21:5: ERA001 [*] Found commented-out code
|
19 | class A():
20 | pass
@@ -109,7 +109,7 @@ ERA001.py:21:5: ERA001 Found commented-out code
23 22 |
24 23 | dictionary = {
ERA001.py:26:5: ERA001 Found commented-out code
ERA001.py:26:5: ERA001 [*] Found commented-out code
|
24 | dictionary = {
25 | # "key1": 123, # noqa: ERA001
@@ -129,7 +129,7 @@ ERA001.py:26:5: ERA001 Found commented-out code
28 27 | }
29 28 |
ERA001.py:27:5: ERA001 Found commented-out code
ERA001.py:27:5: ERA001 [*] Found commented-out code
|
25 | # "key1": 123, # noqa: ERA001
26 | # "key2": 456,

View File

@@ -11,7 +11,7 @@ use ruff_python_stdlib::typing::simple_magic_return_type;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
use crate::registry::{AsRule, Rule};
use crate::rules::ruff::typing::type_hint_resolves_to_any;
/// ## What it does
@@ -702,10 +702,12 @@ pub(crate) fn definition(
},
function.identifier(),
);
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
" -> None".to_string(),
function.parameters.range().end(),
)));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::suggested(Edit::insertion(
" -> None".to_string(),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
}
}
@@ -717,11 +719,13 @@ pub(crate) fn definition(
},
function.identifier(),
);
if let Some(return_type) = simple_magic_return_type(name) {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
if checker.patch(diagnostic.kind.rule()) {
if let Some(return_type) = simple_magic_return_type(name) {
diagnostic.set_fix(Fix::suggested(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
}
diagnostics.push(diagnostic);
}

View File

@@ -242,7 +242,7 @@ annotation_presence.py:154:10: ANN401 Dynamically typed expressions (typing.Any)
| ^^^^^^^^^^^^^^^^^^^^^^^^ ANN401
|
annotation_presence.py:159:9: ANN204 [*] Missing return type annotation for special method `__init__`
annotation_presence.py:159:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
157 | class Foo:
158 | @decorator()
@@ -262,7 +262,7 @@ annotation_presence.py:159:9: ANN204 [*] Missing return type annotation for spec
161 161 |
162 162 |
annotation_presence.py:165:9: ANN204 [*] Missing return type annotation for special method `__init__`
annotation_presence.py:165:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
163 | # Regression test for: https://github.com/astral-sh/ruff/issues/7711
164 | class Class:

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
---
mypy_init_return.py:5:9: ANN204 [*] Missing return type annotation for special method `__init__`
mypy_init_return.py:5:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
3 | # Error
4 | class Foo:
@@ -21,7 +21,7 @@ mypy_init_return.py:5:9: ANN204 [*] Missing return type annotation for special m
7 7 |
8 8 |
mypy_init_return.py:11:9: ANN204 [*] Missing return type annotation for special method `__init__`
mypy_init_return.py:11:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
9 | # Error
10 | class Foo:
@@ -49,7 +49,7 @@ mypy_init_return.py:40:5: ANN202 Missing return type annotation for private func
41 | ...
|
mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special method `__init__`
mypy_init_return.py:47:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
45 | # of a vararg falsely indicated that the function has a typed argument.
46 | class Foo:

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
---
simple_magic_methods.py:2:9: ANN204 [*] Missing return type annotation for special method `__str__`
simple_magic_methods.py:2:9: ANN204 [**] Missing return type annotation for special method `__str__`
|
1 | class Foo:
2 | def __str__(self):
@@ -18,7 +18,7 @@ simple_magic_methods.py:2:9: ANN204 [*] Missing return type annotation for speci
4 4 |
5 5 | def __repr__(self):
simple_magic_methods.py:5:9: ANN204 [*] Missing return type annotation for special method `__repr__`
simple_magic_methods.py:5:9: ANN204 [**] Missing return type annotation for special method `__repr__`
|
3 | ...
4 |
@@ -38,7 +38,7 @@ simple_magic_methods.py:5:9: ANN204 [*] Missing return type annotation for speci
7 7 |
8 8 | def __len__(self):
simple_magic_methods.py:8:9: ANN204 [*] Missing return type annotation for special method `__len__`
simple_magic_methods.py:8:9: ANN204 [**] Missing return type annotation for special method `__len__`
|
6 | ...
7 |
@@ -58,7 +58,7 @@ simple_magic_methods.py:8:9: ANN204 [*] Missing return type annotation for speci
10 10 |
11 11 | def __length_hint__(self):
simple_magic_methods.py:11:9: ANN204 [*] Missing return type annotation for special method `__length_hint__`
simple_magic_methods.py:11:9: ANN204 [**] Missing return type annotation for special method `__length_hint__`
|
9 | ...
10 |
@@ -78,7 +78,7 @@ simple_magic_methods.py:11:9: ANN204 [*] Missing return type annotation for spec
13 13 |
14 14 | def __init__(self):
simple_magic_methods.py:14:9: ANN204 [*] Missing return type annotation for special method `__init__`
simple_magic_methods.py:14:9: ANN204 [**] Missing return type annotation for special method `__init__`
|
12 | ...
13 |
@@ -98,7 +98,7 @@ simple_magic_methods.py:14:9: ANN204 [*] Missing return type annotation for spec
16 16 |
17 17 | def __del__(self):
simple_magic_methods.py:17:9: ANN204 [*] Missing return type annotation for special method `__del__`
simple_magic_methods.py:17:9: ANN204 [**] Missing return type annotation for special method `__del__`
|
15 | ...
16 |
@@ -118,7 +118,7 @@ simple_magic_methods.py:17:9: ANN204 [*] Missing return type annotation for spec
19 19 |
20 20 | def __bool__(self):
simple_magic_methods.py:20:9: ANN204 [*] Missing return type annotation for special method `__bool__`
simple_magic_methods.py:20:9: ANN204 [**] Missing return type annotation for special method `__bool__`
|
18 | ...
19 |
@@ -138,7 +138,7 @@ simple_magic_methods.py:20:9: ANN204 [*] Missing return type annotation for spec
22 22 |
23 23 | def __bytes__(self):
simple_magic_methods.py:23:9: ANN204 [*] Missing return type annotation for special method `__bytes__`
simple_magic_methods.py:23:9: ANN204 [**] Missing return type annotation for special method `__bytes__`
|
21 | ...
22 |
@@ -158,7 +158,7 @@ simple_magic_methods.py:23:9: ANN204 [*] Missing return type annotation for spec
25 25 |
26 26 | def __format__(self, format_spec):
simple_magic_methods.py:26:9: ANN204 [*] Missing return type annotation for special method `__format__`
simple_magic_methods.py:26:9: ANN204 [**] Missing return type annotation for special method `__format__`
|
24 | ...
25 |
@@ -178,7 +178,7 @@ simple_magic_methods.py:26:9: ANN204 [*] Missing return type annotation for spec
28 28 |
29 29 | def __contains__(self, item):
simple_magic_methods.py:29:9: ANN204 [*] Missing return type annotation for special method `__contains__`
simple_magic_methods.py:29:9: ANN204 [**] Missing return type annotation for special method `__contains__`
|
27 | ...
28 |
@@ -198,7 +198,7 @@ simple_magic_methods.py:29:9: ANN204 [*] Missing return type annotation for spec
31 31 |
32 32 | def __complex__(self):
simple_magic_methods.py:32:9: ANN204 [*] Missing return type annotation for special method `__complex__`
simple_magic_methods.py:32:9: ANN204 [**] Missing return type annotation for special method `__complex__`
|
30 | ...
31 |
@@ -218,7 +218,7 @@ simple_magic_methods.py:32:9: ANN204 [*] Missing return type annotation for spec
34 34 |
35 35 | def __int__(self):
simple_magic_methods.py:35:9: ANN204 [*] Missing return type annotation for special method `__int__`
simple_magic_methods.py:35:9: ANN204 [**] Missing return type annotation for special method `__int__`
|
33 | ...
34 |
@@ -238,7 +238,7 @@ simple_magic_methods.py:35:9: ANN204 [*] Missing return type annotation for spec
37 37 |
38 38 | def __float__(self):
simple_magic_methods.py:38:9: ANN204 [*] Missing return type annotation for special method `__float__`
simple_magic_methods.py:38:9: ANN204 [**] Missing return type annotation for special method `__float__`
|
36 | ...
37 |
@@ -258,7 +258,7 @@ simple_magic_methods.py:38:9: ANN204 [*] Missing return type annotation for spec
40 40 |
41 41 | def __index__(self):
simple_magic_methods.py:41:9: ANN204 [*] Missing return type annotation for special method `__index__`
simple_magic_methods.py:41:9: ANN204 [**] Missing return type annotation for special method `__index__`
|
39 | ...
40 |

View File

@@ -12,7 +12,6 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
| "assertNotEquals"
| "bool"
| "bytes"
| "coalesce"
| "count"
| "failIfEqual"
| "failUnlessEqual"
@@ -23,15 +22,12 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
| "getboolean"
| "getfloat"
| "getint"
| "ifnull"
| "index"
| "insert"
| "int"
| "is_"
| "is_not"
| "isnull"
| "next"
| "nvl"
| "param"
| "pop"
| "remove"

View File

@@ -81,12 +81,12 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
21 | kwonly_nonvalued_nohint,
|
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
FBT.py:87:19: FBT001 Boolean-typed positional argument in function definition
|
88 | # FBT001: Boolean positional arg in function definition
89 | def foo(self, value: bool) -> None:
86 | # FBT001: Boolean positional arg in function definition
87 | def foo(self, value: bool) -> None:
| ^^^^^ FBT001
90 | pass
88 | pass
|

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