Compare commits

..

1 Commits

Author SHA1 Message Date
Zanie
9288e61cf8 Fix comparison when using greater than or equal in UP036 2023-10-10 14:22:05 -05:00
330 changed files with 4983 additions and 7757 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.2.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

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

View File

@@ -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.

62
Cargo.lock generated
View File

@@ -28,9 +28,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
dependencies = [
"memchr",
]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"anyhow",
"clap",
@@ -1084,9 +1084,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.34.0"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
dependencies = [
"console",
"globset",
@@ -1925,14 +1925,14 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
"regex-automata 0.3.9",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1949,16 +1949,10 @@ name = "regex-automata"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.2",
"regex-syntax 0.7.5",
]
[[package]]
@@ -1973,12 +1967,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "result-like"
version = "0.4.6"
@@ -2051,7 +2039,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2155,7 +2143,6 @@ name = "ruff_diagnostics"
version = "0.0.0"
dependencies = [
"anyhow",
"is-macro",
"log",
"ruff_text_size",
"serde",
@@ -2188,7 +2175,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.0"
version = "0.0.292"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
@@ -2658,9 +2645,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"
@@ -3104,10 +3091,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,9 +3104,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.27"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
@@ -3127,9 +3115,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.32"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
"valuable",
@@ -3259,9 +3247,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 +3257,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",

View File

@@ -21,7 +21,7 @@ filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.34.0", feature = ["filters", "glob"] }
insta = { version = "1.33.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
itertools = { version = "0.11.0" }
libcst = { version = "1.1.0", default-features = false }
@@ -31,7 +31,7 @@ once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.1.1" }
proc-macro2 = { version = "1.0.69" }
quote = { version = "1.0.23" }
regex = { version = "1.10.2" }
regex = { version = "1.9.6" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.152", features = ["derive"] }
@@ -46,11 +46,11 @@ syn = { version = "2.0.38" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.49" }
toml = { version = "0.7.8" }
tracing = { version = "0.1.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" }

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

@@ -117,6 +117,16 @@ pub struct CheckCommand {
#[arg(long)]
ignore_noqa: bool,
/// Output serialization format for violations. (Deprecated: Use `--output-format` instead).
#[arg(
long,
value_enum,
env = "RUFF_FORMAT",
conflicts_with = "output_format",
hide = true
)]
pub format: Option<SerializationFormat>,
/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
pub output_format: Option<SerializationFormat>,
@@ -497,7 +507,7 @@ impl CheckCommand {
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
.map(UnsafeFixes::from),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
output_format: self.output_format,
output_format: self.output_format.or(self.format),
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
},
)

View File

@@ -6,7 +6,6 @@ use std::sync::mpsc::channel;
use anyhow::Result;
use clap::CommandFactory;
use colored::Colorize;
use log::warn;
use notify::{recommended_watcher, RecursiveMode, Watcher};
@@ -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`

View File

@@ -128,23 +128,17 @@ impl Printer {
let fix_prefix = format!("[{}]", "*".cyan());
if self.unsafe_fixes.is_enabled() {
if fixables.applicable > 0 {
writeln!(
writer,
"{fix_prefix} {} fixable with the --fix option.",
fixables.applicable
)?;
}
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"
};
if fixables.applicable > 0 && fixables.unapplicable > 0 {
let es = if fixables.unapplicable == 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
fixables.applicable, fixables.unapplicable
)?;
} else if fixables.applicable > 0 {
// Only applicable fixes
@@ -155,14 +149,10 @@ impl Printer {
)?;
} else {
// Only unapplicable fixes
let es = if fixables.unapplicable_unsafe == 1 {
""
} else {
"es"
};
let es = if fixables.unapplicable == 1 { "" } else { "es" };
writeln!(writer,
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
fixables.unapplicable_unsafe
"{} hidden fix{es} can be enabled with the `--unsafe-fixes` option.",
fixables.unapplicable
)?;
}
}
@@ -171,7 +161,7 @@ impl Printer {
// Check if there are unapplied fixes
let unapplied = {
if let Some(fixables) = fixables {
fixables.unapplicable_unsafe
fixables.unapplicable
} else {
0
}
@@ -507,33 +497,30 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
#[derive(Debug)]
struct FixableStatistics {
applicable: u32,
unapplicable_unsafe: u32,
unapplicable: u32,
}
impl FixableStatistics {
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
let mut applicable = 0;
let mut unapplicable_unsafe = 0;
let mut unapplicable = 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;
}
unapplicable += 1;
}
}
}
if applicable == 0 && unapplicable_unsafe == 0 {
if applicable == 0 && unapplicable == 0 {
None
} else {
Some(Self {
applicable,
unapplicable_unsafe,
unapplicable,
})
}
}

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": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 3
},
"location": {
"column": 1,
"row": 2
}
}
],
"message": "Remove unused import: `os`"
},
"location": {
"column": 8,
"row": 2
},
"message": "`os` imported but unused",
"noqa_row": 2,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
}
]
----- stderr -----
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
"###);
Ok(())
}

View File

@@ -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)]
@@ -485,7 +486,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.
"###);
}
@@ -945,7 +946,7 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
----- stdout -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
----- stderr -----
"###);
@@ -986,7 +987,7 @@ fn fix_applies_safe_fixes_by_default() {
"--output-format",
"text",
"--isolated",
"--no-cache",
"--no-cache",
"--select",
"F601,UP034",
"--fix",
@@ -1002,7 +1003,7 @@ fn fix_applies_safe_fixes_by_default() {
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
"###);
}
@@ -1015,7 +1016,7 @@ fn fix_applies_unsafe_fixes_with_opt_in() {
"--output-format",
"text",
"--isolated",
"--no-cache",
"--no-cache",
"--select",
"F601,UP034",
"--fix",
@@ -1034,59 +1035,6 @@ fn fix_applies_unsafe_fixes_with_opt_in() {
"###);
}
#[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!(
@@ -1096,7 +1044,7 @@ fn fix_only_unsafe_fixes_available() {
"--output-format",
"text",
"--isolated",
"--no-cache",
"--no-cache",
"--select",
"F601",
"--fix",
@@ -1112,7 +1060,7 @@ fn fix_only_unsafe_fixes_available() {
----- stderr -----
-:1:14: F601 Dictionary key literal `'a'` repeated
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
1 hidden fix can be enabled with the `--unsafe-fixes` option.
"###);
}
@@ -1125,7 +1073,7 @@ fn fix_only_flag_applies_safe_fixes_by_default() {
"--output-format",
"text",
"--isolated",
"--no-cache",
"--no-cache",
"--select",
"F601,UP034",
"--fix-only",
@@ -1152,7 +1100,7 @@ fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
"--output-format",
"text",
"--isolated",
"--no-cache",
"--no-cache",
"--select",
"F601,UP034",
"--fix-only",
@@ -1235,31 +1183,6 @@ fn diff_shows_unsafe_fixes_with_opt_in() {
);
}
#[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!(
@@ -1285,122 +1208,3 @@ fn diff_only_unsafe_fixes_available() {
"###
);
}
#[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).
----- 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("-")
.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.
[*] 2 fixable with the `--fix` option.
----- 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

@@ -17,7 +17,6 @@ exit_code: 1
----- stdout -----
[
{
"cell": null,
"code": "F401",
"end_location": {
"column": 10,

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

@@ -6,7 +6,7 @@ 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)]
#[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 {
@@ -138,11 +138,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

@@ -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

@@ -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

@@ -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

@@ -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,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

@@ -197,8 +197,8 @@ 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")
if sys.version_info > (3, 12):
print('py3')
if sys.version_info >= (3, 12):
print('py3')

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)
if checker.patch(Rule::UnusedVariable) {
diagnostic.try_set_fix(|| {
pyflakes::fixes::remove_exception_handler_assignment(
binding,
checker.locator,
)
.map(Fix::safe_edit)
});
});
}
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

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

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);
}
@@ -1366,7 +1367,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();
@@ -1770,7 +1771,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::safe_edit(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::safe_edit(delete_noqa(
directive.range(),
locator,
)));
} else {
diagnostic.set_fix(Fix::safe_edit(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

@@ -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;
@@ -260,36 +260,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)
}

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();

View File

@@ -125,18 +125,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!(

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};
@@ -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

@@ -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,
@@ -37,7 +36,6 @@ expression: content
"url": "https://docs.astral.sh/ruff/rules/unused-import"
},
{
"cell": null,
"code": "F841",
"end_location": {
"column": 6,
@@ -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":"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"}
{"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"}
{"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

@@ -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

@@ -87,15 +87,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 {
@@ -200,9 +203,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 +228,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 +270,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,10 +353,7 @@ 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;
@@ -386,16 +385,4 @@ mod tests {
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::display_edit(Edit::range_deletion(
locator.full_lines_range(*range),
)));
}
diagnostics.push(diagnostic);
}
}

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::unsafe_edit(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::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
}
diagnostics.push(diagnostic);
}

View File

@@ -6,6 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_false;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `assert False`.
@@ -74,9 +75,11 @@ pub(crate) fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg:
}
let mut diagnostic = Diagnostic::new(AssertFalse, test.range());
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&assertion_error(msg)),
stmt.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().stmt(&assertion_error(msg)),
stmt.range(),
)));
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -11,7 +11,7 @@ use ruff_python_ast::call_path::CallPath;
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::registry::Rule;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for `try-except` blocks with duplicate exception handlers.
@@ -146,22 +146,24 @@ fn duplicate_handler_exceptions<'a>(
},
expr.range(),
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
// Single exceptions don't require parentheses, but since we're _removing_
// parentheses, insert whitespace as needed.
if let [elt] = unique_elts.as_slice() {
pad(
checker.generator().expr(elt),
expr.range(),
checker.locator(),
)
} else {
// Multiple exceptions must always be parenthesized. This is done
// manually as the generator never parenthesizes lone tuples.
format!("({})", checker.generator().expr(&type_pattern(unique_elts)))
},
expr.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
// Single exceptions don't require parentheses, but since we're _removing_
// parentheses, insert whitespace as needed.
if let [elt] = unique_elts.as_slice() {
pad(
checker.generator().expr(elt),
expr.range(),
checker.locator(),
)
} else {
// Multiple exceptions must always be parenthesized. This is done
// manually as the generator never parenthesizes lone tuples.
format!("({})", checker.generator().expr(&type_pattern(unique_elts)))
},
expr.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -6,6 +6,7 @@ use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `getattr` that take a constant attribute value as an
@@ -84,24 +85,26 @@ pub(crate) fn getattr_with_constant(
}
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(
if matches!(
obj,
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
) && !checker.locator().contains_line_break(obj.range())
{
format!("{}.{}", checker.locator().slice(obj), value)
} else {
// Defensively parenthesize any other expressions. For example, attribute accesses
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
// `(1).real`. The same is true for named expressions and others.
format!("({}).{}", checker.locator().slice(obj), value)
},
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(
if matches!(
obj,
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
) && !checker.locator().contains_line_break(obj.range())
{
format!("{}.{}", checker.locator().slice(obj), value)
} else {
// Defensively parenthesize any other expressions. For example, attribute accesses
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
// `(1).real`. The same is true for named expressions and others.
format!("({}).{}", checker.locator().slice(obj), value)
},
expr.range(),
checker.locator(),
),
expr.range(),
checker.locator(),
),
expr.range(),
)));
)));
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -11,6 +11,7 @@ use ruff_source_file::Locator;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of mutable objects as function argument defaults.
@@ -109,16 +110,18 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
let mut diagnostic = Diagnostic::new(MutableArgumentDefault, default.range());
// If the function body is on the same line as the function def, do not fix
if let Some(fix) = move_initialization(
function_def,
parameter,
default,
checker.locator(),
checker.stylist(),
checker.indexer(),
checker.generator(),
) {
diagnostic.set_fix(fix);
if checker.patch(diagnostic.kind.rule()) {
if let Some(fix) = move_initialization(
function_def,
parameter,
default,
checker.locator(),
checker.stylist(),
checker.indexer(),
checker.generator(),
) {
diagnostic.set_fix(fix);
}
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -6,6 +6,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix::edits::pad;
use crate::registry::AsRule;
/// ## What it does
/// Checks for single-element tuples in exception handlers (e.g.,
@@ -76,21 +77,23 @@ pub(crate) fn redundant_tuple_in_exception_handler(
},
type_.range(),
);
// If there's no space between the `except` and the tuple, we need to insert a space,
// as in:
// ```python
// except(ValueError,):
// ```
// Otherwise, the output will be invalid syntax, since we're removing a set of
// parentheses.
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(
checker.generator().expr(elt),
if checker.patch(diagnostic.kind.rule()) {
// If there's no space between the `except` and the tuple, we need to insert a space,
// as in:
// ```python
// except(ValueError,):
// ```
// Otherwise, the output will be invalid syntax, since we're removing a set of
// parentheses.
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
pad(
checker.generator().expr(elt),
type_.range(),
checker.locator(),
),
type_.range(),
checker.locator(),
),
type_.range(),
)));
)));
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -7,6 +7,7 @@ use ruff_python_codegen::Generator;
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `setattr` that take a constant attribute value as an
@@ -107,10 +108,12 @@ pub(crate) fn setattr_with_constant(
{
if expr == child.as_ref() {
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
assignment(obj, name, value, checker.generator()),
expr.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
assignment(obj, name, value, checker.generator()),
expr.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `hasattr` to test if an object is callable (e.g.,
@@ -79,12 +80,14 @@ pub(crate) fn unreliable_callable_check(
}
let mut diagnostic = Diagnostic::new(UnreliableCallableCheck, expr.range());
if id == "hasattr" {
if checker.semantic().is_builtin("callable") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("callable({})", checker.locator().slice(obj)),
expr.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
if id == "hasattr" {
if checker.semantic().is_builtin("callable") {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("callable({})", checker.locator().slice(obj)),
expr.range(),
)));
}
}
}
checker.diagnostics.push(diagnostic);

View File

@@ -8,6 +8,7 @@ use ruff_python_ast::{helpers, visitor};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
#[derive(Debug, Copy, Clone, PartialEq, Eq, result_like::BoolLike)]
enum Certainty {
@@ -155,21 +156,23 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, stmt_for: &ast
},
expr.range(),
);
if let Some(rename) = rename {
if certainty.into() {
// Avoid fixing if the variable, or any future bindings to the variable, are
// used _after_ the loop.
let scope = checker.semantic().current_scope();
if scope
.get_all(name)
.map(|binding_id| checker.semantic().binding(binding_id))
.filter(|binding| binding.start() >= expr.start())
.all(|binding| !binding.is_used())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
rename,
expr.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
if let Some(rename) = rename {
if certainty.into() {
// Avoid fixing if the variable, or any future bindings to the variable, are
// used _after_ the loop.
let scope = checker.semantic().current_scope();
if scope
.get_all(name)
.map(|binding_id| checker.semantic().binding(binding_id))
.filter(|binding| binding.start() >= expr.start())
.all(|binding| !binding.is_used())
{
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
rename,
expr.range(),
)));
}
}
}
}

View File

@@ -8,6 +8,9 @@ use ruff_python_parser::Tok;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::registry::Rule;
use crate::settings::LinterSettings;
/// Simplified token type.
#[derive(Copy, Clone, PartialEq, Eq)]
enum TokenType {
@@ -222,6 +225,7 @@ pub(crate) fn trailing_commas(
diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult],
locator: &Locator,
settings: &LinterSettings,
) {
let tokens = tokens
.iter()
@@ -320,7 +324,9 @@ pub(crate) fn trailing_commas(
if comma_prohibited {
let comma = prev.spanned.unwrap();
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, comma.1);
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
if settings.rules.should_fix(Rule::ProhibitedTrailingComma) {
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
}
diagnostics.push(diagnostic);
}
@@ -353,15 +359,17 @@ pub(crate) fn trailing_commas(
MissingTrailingComma,
TextRange::empty(missing_comma.1.end()),
);
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 fix
// removing any brackets in the same linter pass - doing both at the same time could
// lead to a syntax error.
let contents = locator.slice(missing_comma.1);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{contents},"),
missing_comma.1,
)));
if settings.rules.should_fix(Rule::MissingTrailingComma) {
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 fix
// removing any brackets in the same linter pass - doing both at the same time could
// lead to a syntax error.
let contents = locator.slice(missing_comma.1);
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{contents},"),
missing_comma.1,
)));
}
diagnostics.push(diagnostic);
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
@@ -82,14 +82,19 @@ pub(crate) fn unnecessary_call_around_sorted(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
let edit =
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?;
if outer.id == "reversed" {
Ok(Fix::unsafe_edit(edit))
} else {
Ok(Fix::safe_edit(edit))
}
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let edit = fixes::fix_unnecessary_call_around_sorted(
expr,
checker.locator(),
checker.stylist(),
)?;
if outer.id == "reversed" {
Ok(Fix::unsafe_edit(edit))
} else {
Ok(Fix::safe_edit(edit))
}
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use crate::rules::flake8_comprehensions::settings::Settings;
@@ -86,8 +86,10 @@ pub(crate) fn unnecessary_collection_call(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_collection_call(expr, checker).map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_collection_call(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_python_ast::{self as ast, Comprehension, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
@@ -64,10 +64,12 @@ fn add_diagnostic(checker: &mut Checker, expr: &Expr) {
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -7,7 +7,7 @@ use ruff_python_ast::helpers::any_over_expr;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
@@ -89,9 +89,11 @@ pub(crate) fn unnecessary_comprehension_any_all(
}
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, arg.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist())
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist())
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
@@ -130,14 +130,16 @@ pub(crate) fn unnecessary_double_cast_or_process(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_double_cast_or_process(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_double_cast_or_process(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -67,7 +67,10 @@ pub(crate) fn unnecessary_generator_dict(
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorDict, expr.range());
diagnostic
.try_set_fix(|| fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::unsafe_edit));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_generator_dict(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -60,10 +60,12 @@ pub(crate) fn unnecessary_generator_list(
}
if let Expr::GeneratorExp(_) = argument {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_generator_list(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_generator_list(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -60,9 +60,11 @@ pub(crate) fn unnecessary_generator_set(
}
if let Expr::GeneratorExp(_) = argument {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_generator_set(expr, checker).map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_generator_set(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -56,9 +56,11 @@ pub(crate) fn unnecessary_list_call(
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryListCall, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_call(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_call(expr, checker.locator(), checker.stylist())
.map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -65,8 +65,10 @@ pub(crate) fn unnecessary_list_comprehension_dict(
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionDict, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_comprehension_dict(expr, checker).map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_comprehension_dict(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -58,9 +58,11 @@ pub(crate) fn unnecessary_list_comprehension_set(
}
if argument.is_list_comp_expr() {
let mut diagnostic = Diagnostic::new(UnnecessaryListComprehensionSet, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_comprehension_set(expr, checker).map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_list_comprehension_set(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -80,7 +80,10 @@ pub(crate) fn unnecessary_literal_dict(
},
expr.range(),
);
diagnostic
.try_set_fix(|| fixes::fix_unnecessary_literal_dict(expr, checker).map(Fix::unsafe_edit));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_dict(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -75,7 +75,10 @@ pub(crate) fn unnecessary_literal_set(
},
expr.range(),
);
diagnostic
.try_set_fix(|| fixes::fix_unnecessary_literal_set(expr, checker).map(Fix::unsafe_edit));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_set(expr, checker).map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -7,7 +7,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -91,9 +91,15 @@ pub(crate) fn unnecessary_literal_within_dict_call(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_dict_call(expr, checker.locator(), checker.stylist())
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_dict_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -4,7 +4,7 @@ use ruff_python_ast::{Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -93,9 +93,15 @@ pub(crate) fn unnecessary_literal_within_list_call(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_list_call(expr, checker.locator(), checker.stylist())
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_list_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -95,9 +95,15 @@ pub(crate) fn unnecessary_literal_within_tuple_call(
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_tuple_call(expr, checker.locator(), checker.stylist())
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_literal_within_tuple_call(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -9,7 +9,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Parameters, Stm
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
use super::helpers;
@@ -221,16 +221,18 @@ pub(crate) fn unnecessary_map(
};
let mut diagnostic = Diagnostic::new(UnnecessaryMap { object_type }, expr.range());
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_map(
expr,
parent,
object_type,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_map(
expr,
parent,
object_type,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -7,7 +7,7 @@ use ruff_python_ast::whitespace;
use ruff_python_codegen::{Generator, Stylist};
use crate::checkers::ast::Checker;
use crate::registry::Rule;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for the use of string literals in exception constructors.
@@ -190,6 +190,30 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
if string.len() >= checker.settings.flake8_errmsg.max_string_length {
let mut diagnostic =
Diagnostic::new(RawStringInException, first.range());
if checker.patch(diagnostic.kind.rule()) {
if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
}
}
}
checker.diagnostics.push(diagnostic);
}
}
}
// Check for f-strings.
Expr::FString(_) => {
if checker.enabled(Rule::FStringInException) {
let mut diagnostic = Diagnostic::new(FStringInException, first.range());
if checker.patch(diagnostic.kind.rule()) {
if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt)
{
@@ -203,25 +227,6 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
));
}
}
checker.diagnostics.push(diagnostic);
}
}
}
// Check for f-strings.
Expr::FString(_) => {
if checker.enabled(Rule::FStringInException) {
let mut diagnostic = Diagnostic::new(FStringInException, first.range());
if let Some(indentation) = whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
}
}
checker.diagnostics.push(diagnostic);
}
@@ -235,17 +240,19 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
if attr == "format" && value.is_constant_expr() {
let mut diagnostic =
Diagnostic::new(DotFormatInException, first.range());
if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
if checker.patch(diagnostic.kind.rule()) {
if let Some(indentation) =
whitespace::indentation(checker.locator(), stmt)
{
if checker.semantic().is_available("msg") {
diagnostic.set_fix(generate_fix(
stmt,
first,
indentation,
checker.stylist(),
checker.generator(),
));
}
}
}
checker.diagnostics.push(diagnostic);

View File

@@ -12,6 +12,7 @@ pub(crate) use shebang_not_executable::*;
pub(crate) use shebang_not_first_line::*;
use crate::comments::shebang::ShebangDirective;
use crate::settings::LinterSettings;
mod shebang_leading_whitespace;
mod shebang_missing_executable_file;
@@ -23,6 +24,7 @@ pub(crate) fn from_tokens(
tokens: &[LexResult],
path: &Path,
locator: &Locator,
settings: &LinterSettings,
diagnostics: &mut Vec<Diagnostic>,
) {
let mut has_any_shebang = false;
@@ -39,7 +41,7 @@ pub(crate) fn from_tokens(
diagnostics.push(diagnostic);
}
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator) {
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator, settings) {
diagnostics.push(diagnostic);
}

View File

@@ -5,6 +5,9 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_trivia::is_python_whitespace;
use ruff_source_file::Locator;
use crate::registry::AsRule;
use crate::settings::LinterSettings;
/// ## What it does
/// Checks for whitespace before a shebang directive.
///
@@ -47,6 +50,7 @@ impl AlwaysFixableViolation for ShebangLeadingWhitespace {
pub(crate) fn shebang_leading_whitespace(
range: TextRange,
locator: &Locator,
settings: &LinterSettings,
) -> Option<Diagnostic> {
// If the shebang is at the beginning of the file, abort.
if range.start() == TextSize::from(0) {
@@ -64,6 +68,8 @@ pub(crate) fn shebang_leading_whitespace(
let prefix = TextRange::up_to(range.start());
let mut diagnostic = Diagnostic::new(ShebangLeadingWhitespace, prefix);
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix)));
if settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prefix)));
}
Some(diagnostic)
}

View File

@@ -1,15 +1,15 @@
use itertools::Itertools;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_text_size::{Ranged, TextRange};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::str::{leading_quote, trailing_quote};
use ruff_python_index::Indexer;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::settings::LinterSettings;
use crate::rules::flake8_implicit_str_concat::settings::Settings;
/// ## What it does
/// Checks for implicitly concatenated strings on a single line.
@@ -94,7 +94,7 @@ impl Violation for MultiLineImplicitStringConcatenation {
pub(crate) fn implicit(
diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult],
settings: &LinterSettings,
settings: &Settings,
locator: &Locator,
indexer: &Indexer,
) {
@@ -102,9 +102,7 @@ pub(crate) fn implicit(
.iter()
.flatten()
.filter(|(tok, _)| {
!tok.is_comment()
&& (settings.flake8_implicit_str_concat.allow_multiline
|| !tok.is_non_logical_newline())
!tok.is_comment() && (settings.allow_multiline || !tok.is_non_logical_newline())
})
.tuple_windows()
{

View File

@@ -6,7 +6,7 @@ use ruff_python_semantic::{Binding, Imported};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::renamer::Renamer;
/// ## What it does
@@ -79,14 +79,16 @@ pub(crate) fn unconventional_import_alias(
},
binding.range(),
);
if !import.is_submodule_import() {
if checker.semantic().is_available(expected_alias) {
diagnostic.try_set_fix(|| {
let scope = &checker.semantic().scopes[binding.scope];
let (edit, rest) =
Renamer::rename(name, expected_alias, scope, checker.semantic())?;
Ok(Fix::unsafe_edits(edit, rest))
});
if checker.patch(diagnostic.kind.rule()) {
if !import.is_submodule_import() {
if checker.semantic().is_available(expected_alias) {
diagnostic.try_set_fix(|| {
let scope = &checker.semantic().scopes[binding.scope];
let (edit, rest) =
Renamer::rename(name, expected_alias, scope, checker.semantic())?;
Ok(Fix::unsafe_edits(edit, rest))
});
}
}
}
Some(diagnostic)

View File

@@ -5,6 +5,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does
/// Checks for direct instantiation of `logging.Logger`, as opposed to using
@@ -60,15 +61,17 @@ pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::Exp
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"]))
{
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "getLogger"),
call.func.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, call.func.range());
Ok(Fix::unsafe_edits(import_edit, [reference_edit]))
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "getLogger"),
call.func.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, call.func.range());
Ok(Fix::unsafe_edits(import_edit, [reference_edit]))
});
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -4,6 +4,7 @@ use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for any usage of `__cached__` and `__file__` as an argument to
@@ -79,11 +80,13 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp
}
let mut diagnostic = Diagnostic::new(InvalidGetLoggerArgument, expr.range());
if checker.semantic().is_builtin("__name__") {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"__name__".to_string(),
expr.range(),
)));
if checker.patch(diagnostic.kind.rule()) {
if checker.semantic().is_builtin("__name__") {
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
"__name__".to_string(),
expr.range(),
)));
}
}
checker.diagnostics.push(diagnostic);
}

View File

@@ -6,6 +6,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::registry::AsRule;
/// ## What it does
/// Checks for uses of `logging.WARN`.
@@ -54,15 +55,17 @@ pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"]))
{
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "WARNING"),
expr.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, expr.range());
Ok(Fix::safe_edits(import_edit, [reference_edit]))
});
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("logging", "WARNING"),
expr.start(),
checker.semantic(),
)?;
let reference_edit = Edit::range_replacement(binding, expr.range());
Ok(Fix::safe_edits(import_edit, [reference_edit]))
});
}
checker.diagnostics.push(diagnostic);
}
}

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