Compare commits

..

20 Commits

Author SHA1 Message Date
Douglas Creager
f33ca3a622 minimize more 2025-10-17 16:23:47 -04:00
Douglas Creager
a79ba2036b propagate impossible deeply 2025-10-17 16:23:46 -04:00
Douglas Creager
d6f28b7428 keep all minimizations 2025-10-17 16:23:46 -04:00
Douglas Creager
9df9adae1e minimize before display 2025-10-17 16:23:46 -04:00
Douglas Creager
eec4e2ed11 add impossible terminal 2025-10-17 16:23:46 -04:00
Douglas Creager
a44fbd6658 debug display simplification 2025-10-17 16:23:46 -04:00
Douglas Creager
22075d5ed7 debug 2025-10-17 16:23:46 -04:00
Douglas Creager
5d451979c4 shannon 2025-10-17 16:23:46 -04:00
Douglas Creager
73773b4ea4 more intersection replacements 2025-10-17 16:23:46 -04:00
Douglas Creager
7cfdc4a550 replace with intersection 2025-10-17 16:23:46 -04:00
Douglas Creager
2f0e7d6af7 fix pos/neg implication 2025-10-17 16:23:46 -04:00
Douglas Creager
8d44f8b7b5 remove old simplify 2025-10-17 16:23:46 -04:00
Douglas Creager
c0faa2dc3d use simplify_new 2025-10-17 16:23:46 -04:00
Douglas Creager
f4fff7fb24 new simplification 2025-10-17 16:23:46 -04:00
Douglas Creager
a6bd68886f xor 2025-10-17 16:23:46 -04:00
Douglas Creager
5c2c3f00ff constraint implication check 2025-10-17 16:23:46 -04:00
Douglas Creager
5affc120b3 order typevars near each other 2025-10-17 16:23:46 -04:00
Douglas Creager
1e284933ec normalize bounds 2025-10-17 16:23:46 -04:00
Douglas Creager
c529ee4f80 simplify individual clauses for display 2025-10-17 16:23:46 -04:00
Douglas Creager
f88ff62da5 add BDD graph display 2025-10-17 16:23:46 -04:00
1179 changed files with 8890 additions and 22592 deletions

View File

@@ -8,3 +8,7 @@ benchmark = "bench -p ruff_benchmark --bench linter --bench formatter --"
# See: https://github.com/astral-sh/ruff/issues/11503
[target.'cfg(all(target_env="msvc", target_os = "windows"))']
rustflags = ["-C", "target-feature=+crt-static"]
[target.'wasm32-unknown-unknown']
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
rustflags = ["--cfg", 'getrandom_backend="wasm_js"']

View File

@@ -22,7 +22,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.14"
PYTHON_VERSION: "3.13"
NEXTEST_PROFILE: ci
jobs:
@@ -277,8 +277,7 @@ jobs:
run: cargo test -p ty_python_semantic --test mdtest || true
- name: "Run tests"
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
# Dogfood ty on py-fuzzer
- run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -334,14 +333,9 @@ jobs:
- name: "Run tests"
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
cargo-test-other:
strategy:
matrix:
platform:
- ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
- macos-latest
name: "cargo test (${{ matrix.platform }})"
runs-on: ${{ matrix.platform }}
cargo-test-windows:
name: "cargo test (windows)"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
@@ -360,6 +354,37 @@ jobs:
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
enable-cache: "true"
- name: "Run tests"
env:
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
run: |
cargo nextest run --all-features --profile ci
cargo test --all-features --doc
cargo-test-macos:
name: "cargo test (macos)"
runs-on: macos-latest
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
enable-cache: "true"
- name: "Run tests"
run: |
cargo nextest run --all-features --profile ci
@@ -438,7 +463,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@afcf9780305558bcc9e4bc94b7589ab2bb8b6106 # v1.15.9
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -473,10 +498,9 @@ jobs:
chmod +x "${DOWNLOAD_PATH}/ruff"
(
uv run \
uvx \
--python="${PYTHON_VERSION}" \
--project=./python/py-fuzzer \
--locked \
--from=./python/py-fuzzer \
fuzz \
--test-executable="${DOWNLOAD_PATH}/ruff" \
--bin=ruff \
@@ -494,7 +518,6 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -509,11 +532,6 @@ jobs:
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
- run: cargo check
# Lint/format/type-check py-fuzzer
# (dogfooding with ty is done in a separate job)
- run: uv run --directory=./python/py-fuzzer mypy
- run: uv run --directory=./python/py-fuzzer ruff format --check
- run: uv run --directory=./python/py-fuzzer ruff check
ecosystem:
name: "ecosystem"
@@ -529,10 +547,9 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
name: Download comparison Ruff binary
@@ -551,7 +568,7 @@ jobs:
- name: Install ruff-ecosystem
run: |
uv pip install ./python/ruff-ecosystem
pip install ./python/ruff-ecosystem
- name: Run `ruff check` stable ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
@@ -649,7 +666,7 @@ jobs:
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 5 || 20 }}
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
@@ -677,16 +694,15 @@ jobs:
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
(
uv run \
uvx \
--python="${PYTHON_VERSION}" \
--project=./python/py-fuzzer \
--locked \
--from=./python/py-fuzzer \
fuzz \
--test-executable="${NEW_TY}/ty" \
--baseline-executable="${PWD}/ty" \
--only-new-bugs \
--bin=ty \
0-1000
0-500
)
cargo-shear:
@@ -698,7 +714,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@afcf9780305558bcc9e4bc94b7589ab2bb8b6106 # v1.15.9
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -787,6 +803,9 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.13"
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
@@ -797,15 +816,12 @@ jobs:
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
python-version: 3.13
activate-environment: true
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt
run: uv pip install -r docs/requirements-insiders.txt --system
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: uv pip install -r docs/requirements.txt
run: uv pip install -r docs/requirements.txt --system
- name: "Update README File"
run: python scripts/transform_readme.py --target mkdocs
- name: "Generate docs"

View File

@@ -48,10 +48,9 @@ jobs:
run: |
# shellcheck disable=SC2046
(
uv run \
--python=3.14 \
--project=./python/py-fuzzer \
--locked \
uvx \
--python=3.12 \
--from=./python/py-fuzzer \
fuzz \
--test-executable=target/debug/ruff \
--bin=ruff \

View File

@@ -18,8 +18,6 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
permissions: {}
jobs:
publish:
runs-on: ubuntu-latest
@@ -34,7 +32,8 @@ jobs:
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
package-manager-cache: false
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci

View File

@@ -38,7 +38,6 @@ jobs:
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci

View File

@@ -1,6 +1,7 @@
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
# This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist
#
# Copyright 2022-2024, axodotdev
# Copyright 2025 Astral Software Inc.
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
@@ -68,9 +69,9 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.0/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -86,7 +87,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -128,14 +129,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: target/distrib/
@@ -153,7 +154,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: artifacts-build-global
path: |
@@ -179,14 +180,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: target/distrib/
@@ -200,7 +201,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -256,7 +257,7 @@ jobs:
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: artifacts

View File

@@ -34,13 +34,10 @@ jobs:
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show

View File

@@ -30,13 +30,10 @@ jobs:
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show

15
.github/zizmor.yml vendored
View File

@@ -1,5 +1,5 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://docs.zizmor.sh/configuration/
# https://woodruffw.github.io/zizmor/configuration/
#
# TODO: can we remove the ignores here so that our workflows are more secure?
rules:
@@ -9,18 +9,13 @@ rules:
cache-poisoning:
ignore:
- build-docker.yml
- publish-playground.yml
- ty-ecosystem-analyzer.yaml
- ty-ecosystem-report.yaml
excessive-permissions:
# it's hard to test what the impact of removing these ignores would be
# without actually running the release workflow...
ignore:
- build-docker.yml
- publish-playground.yml
- publish-docs.yml
secrets-inherit:
# `cargo dist` makes extensive use of `secrets: inherit`,
# and we can't easily fix that until an upstream release changes that.
disable: true
template-injection:
ignore:
# like with `secrets-inherit`, `cargo dist` introduces some
# template injections. We've manually audited these usages for safety.
- release.yml

View File

@@ -35,7 +35,6 @@ repos:
rev: 0.7.22
hooks:
- id: mdformat
language: python # means renovate will also update `additional_dependencies`
additional_dependencies:
- mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.1
@@ -59,7 +58,6 @@ repos:
rev: 1.19.1
hooks:
- id: blacken-docs
language: python # means renovate will also update `additional_dependencies`
args: ["--pyi", "--line-length", "130"]
files: '^crates/.*/resources/mdtest/.*\.md'
exclude: |
@@ -101,8 +99,8 @@ repos:
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.16.0
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.11.0
hooks:
- id: zizmor

View File

@@ -1,53 +1,5 @@
# Changelog
## 0.14.2
Released on 2025-10-23.
### Preview features
- \[`flake8-gettext`\] Resolve qualified names and built-in bindings (`INT001`, `INT002`, `INT003`) ([#19045](https://github.com/astral-sh/ruff/pull/19045))
### Bug fixes
- Avoid reusing nested, interpolated quotes before Python 3.12 ([#20930](https://github.com/astral-sh/ruff/pull/20930))
- Catch syntax errors in nested interpolations before Python 3.12 ([#20949](https://github.com/astral-sh/ruff/pull/20949))
- \[`fastapi`\] Handle ellipsis defaults in `FAST002` autofix ([#20810](https://github.com/astral-sh/ruff/pull/20810))
- \[`flake8-simplify`\] Skip `SIM911` when unknown arguments are present ([#20697](https://github.com/astral-sh/ruff/pull/20697))
- \[`pyupgrade`\] Always parenthesize assignment expressions in fix for `f-string` (`UP032`) ([#21003](https://github.com/astral-sh/ruff/pull/21003))
- \[`pyupgrade`\] Fix `UP032` conversion for decimal ints with underscores ([#21022](https://github.com/astral-sh/ruff/pull/21022))
- \[`fastapi`\] Skip autofix for keyword and `__debug__` path params (`FAST003`) ([#20960](https://github.com/astral-sh/ruff/pull/20960))
### Rule changes
- \[`flake8-bugbear`\] Skip `B905` and `B912` for fewer than two iterables and no starred arguments ([#20998](https://github.com/astral-sh/ruff/pull/20998))
- \[`ruff`\] Use `DiagnosticTag` for more `pyflakes` and `pandas` rules ([#20801](https://github.com/astral-sh/ruff/pull/20801))
### CLI
- Improve JSON output from `ruff rule` ([#20168](https://github.com/astral-sh/ruff/pull/20168))
### Documentation
- Add source to testimonial ([#20971](https://github.com/astral-sh/ruff/pull/20971))
- Document when a rule was added ([#21035](https://github.com/astral-sh/ruff/pull/21035))
### Other changes
- [syntax-errors] Name is parameter and global ([#20426](https://github.com/astral-sh/ruff/pull/20426))
- [syntax-errors] Alternative `match` patterns bind different names ([#20682](https://github.com/astral-sh/ruff/pull/20682))
### Contributors
- [@hengky-kurniawan-1](https://github.com/hengky-kurniawan-1)
- [@ShalokShalom](https://github.com/ShalokShalom)
- [@robsdedude](https://github.com/robsdedude)
- [@LoicRiegel](https://github.com/LoicRiegel)
- [@TaKO8Ki](https://github.com/TaKO8Ki)
- [@dylwil3](https://github.com/dylwil3)
- [@11happy](https://github.com/11happy)
- [@ntBre](https://github.com/ntBre)
## 0.14.1
Released on 2025-10-16.

157
Cargo.lock generated
View File

@@ -295,9 +295,9 @@ checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata",
@@ -433,9 +433,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
@@ -443,9 +443,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
@@ -486,9 +486,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.49"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
@@ -633,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -642,7 +642,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1007,7 +1007,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@@ -1093,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -1224,9 +1224,9 @@ dependencies = [
[[package]]
name = "get-size-derive2"
version = "0.7.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b134aa084df7c3a513a1035c52f623e4b3065dfaf3d905a4f28a2e79b5bb3f"
checksum = "e3814abc7da8ab18d2fd820f5b540b5e39b6af0a32de1bdd7c47576693074843"
dependencies = [
"attribute-derive",
"quote",
@@ -1235,9 +1235,9 @@ dependencies = [
[[package]]
name = "get-size2"
version = "0.7.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0d51c9f2e956a517619ad9e7eaebc7a573f9c49b38152e12eade750f89156f9"
checksum = "5dfe2cec5b5ce8fb94dcdb16a1708baa4d0609cc3ce305ca5d3f6f2ffb59baed"
dependencies = [
"compact_str",
"get-size-derive2",
@@ -1262,20 +1262,20 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.4"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasip2",
"wasi 0.14.7+wasi-0.2.4",
"wasm-bindgen",
]
@@ -1287,9 +1287,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
version = "0.4.17"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
dependencies = [
"aho-corasick",
"bstr",
@@ -1523,9 +1523,9 @@ dependencies = [
[[package]]
name = "ignore"
version = "0.4.24"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403"
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
dependencies = [
"crossbeam-deque",
"globset",
@@ -1690,7 +1690,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1754,7 +1754,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1789,7 +1789,7 @@ version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.3.3",
"libc",
]
@@ -2072,7 +2072,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@@ -2627,9 +2627,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.41"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@@ -2724,7 +2724,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.3.3",
]
[[package]]
@@ -2767,26 +2767,6 @@ dependencies = [
"thiserror 2.0.16",
]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "regex"
version = "1.11.3"
@@ -2801,9 +2781,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.13"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@@ -2835,7 +2815,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.14.2"
version = "0.14.1"
dependencies = [
"anyhow",
"argfile",
@@ -3092,7 +3072,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.2"
version = "0.14.1"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3217,7 +3197,6 @@ dependencies = [
"salsa",
"schemars",
"serde",
"serde_json",
"thiserror 2.0.16",
]
@@ -3447,11 +3426,11 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.2"
version = "0.14.1"
dependencies = [
"console_error_panic_hook",
"console_log",
"getrandom 0.3.4",
"getrandom 0.3.3",
"js-sys",
"log",
"ruff_formatter",
@@ -3505,7 +3484,6 @@ dependencies = [
"rustc-hash",
"schemars",
"serde",
"serde_json",
"shellexpand",
"strum",
"tempfile",
@@ -3545,7 +3523,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -3563,7 +3541,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
dependencies = [
"boxcar",
"compact_str",
@@ -3587,12 +3565,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
[[package]]
name = "salsa-macros"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
dependencies = [
"proc-macro2",
"quote",
@@ -3611,12 +3589,11 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.0.4"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [
"dyn-clone",
"ref-cast",
"schemars_derive",
"serde",
"serde_json",
@@ -3624,9 +3601,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "1.0.4"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [
"proc-macro2",
"quote",
@@ -3648,9 +3625,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.228"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
dependencies = [
"serde_core",
"serde_derive",
@@ -3669,18 +3646,18 @@ dependencies = [
[[package]]
name = "serde_core"
version = "1.0.228"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
version = "1.0.226"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
dependencies = [
"proc-macro2",
"quote",
@@ -3818,9 +3795,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "snapbox"
version = "0.6.22"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805d09a74586d9b17061e5be6ee5f8cc37e5982c349948114ffc5f68093fe5ec"
checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
dependencies = [
"anstream",
"anstyle",
@@ -3833,7 +3810,7 @@ dependencies = [
"similar",
"snapbox-macros",
"wait-timeout",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -3938,10 +3915,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -4376,7 +4353,6 @@ dependencies = [
"tracing",
"ty_project",
"ty_python_semantic",
"ty_vendored",
]
[[package]]
@@ -4408,7 +4384,6 @@ dependencies = [
"salsa",
"schemars",
"serde",
"serde_json",
"thiserror 2.0.16",
"toml",
"tracing",
@@ -4434,12 +4409,10 @@ dependencies = [
"glob",
"hashbrown 0.16.0",
"indexmap",
"indoc",
"insta",
"itertools 0.14.0",
"memchr",
"ordermap",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"ruff_annotate_snippets",
@@ -4458,7 +4431,6 @@ dependencies = [
"salsa",
"schemars",
"serde",
"serde_json",
"smallvec",
"static_assertions",
"strsim",
@@ -4507,6 +4479,7 @@ dependencies = [
"ty_ide",
"ty_project",
"ty_python_semantic",
"ty_vendored",
]
[[package]]
@@ -4521,6 +4494,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.4",
"camino",
"colored 3.0.0",
"insta",
@@ -4565,7 +4539,7 @@ version = "0.0.0"
dependencies = [
"console_error_panic_hook",
"console_log",
"getrandom 0.3.4",
"getrandom 0.3.3",
"js-sys",
"log",
"ruff_db",
@@ -4758,7 +4732,7 @@ version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.3.3",
"js-sys",
"rand 0.9.2",
"uuid-macro-internal",
@@ -4870,6 +4844,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
@@ -5021,7 +5004,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]

View File

@@ -146,13 +146,13 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "cdd0b85516a52c18b8a6d17a2279a96ed6c3e198", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef9f9329be6923acd050c8dddd172e3bc93e8051", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
] }
schemars = { version = "1.0.4" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.4" }

View File

@@ -89,7 +89,8 @@ creator of [isort](https://github.com/PyCQA/isort):
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
> it was working till I intentionally introduced some errors.
[**Tim Abbott**](https://github.com/zulip/zulip/pull/23431#issuecomment-1302557034), lead developer of [Zulip](https://github.com/zulip/zulip) (also [here](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028)):
[**Tim Abbott**](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028), lead
developer of [Zulip](https://github.com/zulip/zulip):
> This is just ridiculously fast... `ruff` is amazing.
@@ -147,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.14.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.2/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.14.1/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.1/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -181,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.2
rev: v0.14.1
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.2"
version = "0.14.1"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -879,7 +879,19 @@ impl From<&FormatCommandError> for Diagnostic {
| FormatCommandError::Write(_, source_error) => {
Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error)
}
FormatCommandError::Format(_, format_module_error) => format_module_error.into(),
FormatCommandError::Format(_, format_module_error) => match format_module_error {
FormatModuleError::ParseError(parse_error) => Diagnostic::new(
DiagnosticId::InternalError,
Severity::Error,
&parse_error.error,
),
FormatModuleError::FormatError(format_error) => {
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, format_error)
}
FormatModuleError::PrintError(print_error) => {
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, print_error)
}
},
FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new(
DiagnosticId::InvalidCliOption,
Severity::Error,

View File

@@ -7,7 +7,6 @@ use serde::{Serialize, Serializer};
use strum::IntoEnumIterator;
use ruff_linter::FixAvailability;
use ruff_linter::codes::RuleGroup;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use crate::args::HelpFormat;
@@ -20,12 +19,9 @@ struct Explanation<'a> {
summary: &'a str,
message_formats: &'a [&'a str],
fix: String,
fix_availability: FixAvailability,
#[expect(clippy::struct_field_names)]
explanation: Option<&'a str>,
preview: bool,
status: RuleGroup,
source_location: SourceLocation,
}
impl<'a> Explanation<'a> {
@@ -40,14 +36,8 @@ impl<'a> Explanation<'a> {
summary: rule.message_formats()[0],
message_formats: rule.message_formats(),
fix,
fix_availability: rule.fixable(),
explanation: rule.explanation(),
preview: rule.is_preview(),
status: rule.group(),
source_location: SourceLocation {
file: rule.file(),
line: rule.line(),
},
}
}
}
@@ -132,14 +122,3 @@ pub(crate) fn rules(format: HelpFormat) -> Result<()> {
}
Ok(())
}
/// The location of the rule's implementation in the Ruff source tree, relative to the repository
/// root.
///
/// For most rules this will point to the `#[derive(ViolationMetadata)]` line above the rule's
/// struct.
#[derive(Serialize)]
struct SourceLocation {
file: &'static str,
line: u32,
}

View File

@@ -9,7 +9,9 @@ use itertools::{Itertools, iterate};
use ruff_linter::linter::FixTable;
use serde::Serialize;
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, SecondaryCode};
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
};
use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{EmitterContext, render_diagnostics};
@@ -388,18 +390,21 @@ impl Printer {
let context = EmitterContext::new(&diagnostics.notebook_indexes);
let format = if preview {
self.format
DiagnosticFormat::Full
} else {
OutputFormat::Concise
DiagnosticFormat::Concise
};
let config = DisplayDiagnosticConfig::default()
.preview(preview)
.hide_severity(true)
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_fix_applicability(self.unsafe_fixes.required_applicability())
.show_fix_diff(preview);
render_diagnostics(writer, format, config, &context, &diagnostics.inner)?;
.format(format)
.with_fix_applicability(self.unsafe_fixes.required_applicability());
write!(
writer,
"{}",
DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
)?;
}
writer.flush()?;

View File

@@ -15,7 +15,6 @@ use std::{
};
use tempfile::TempDir;
mod format;
mod lint;
const BIN_NAME: &str = "ruff";
@@ -58,16 +57,6 @@ impl CliTest {
Self::with_settings(|_, settings| settings)
}
pub(crate) fn with_files<'a>(
files: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> anyhow::Result<Self> {
let case = Self::new()?;
for file in files {
case.write_file(file.0, file.1)?;
}
Ok(case)
}
pub(crate) fn with_settings(
setup_settings: impl FnOnce(&Path, insta::Settings) -> insta::Settings,
) -> Result<Self> {
@@ -185,10 +174,4 @@ impl CliTest {
command
}
pub(crate) fn format_command(&self) -> Command {
let mut command = self.command();
command.args(["format", "--no-cache"]);
command
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -951,20 +951,6 @@ fn rule_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
}
#[test]
fn rule_f401_output_json() {
insta::with_settings!({filters => vec![
(r#"("file": ")[^"]+(",)"#, "$1<FILE>$2"),
]}, {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
});
}
#[test]
fn rule_f401_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "text"]));
}
#[test]
fn rule_invalid_rule_name() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r"
@@ -979,34 +965,6 @@ fn rule_invalid_rule_name() {
");
}
#[test]
fn rule_invalid_rule_name_output_json() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'RUF404' for '[RULE]'
For more information, try '--help'.
");
}
#[test]
fn rule_invalid_rule_name_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'RUF404' for '[RULE]'
For more information, try '--help'.
");
}
#[test]
fn show_statistics() {
let mut cmd = RuffCheck::default()

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:
@@ -7,7 +7,6 @@ info:
- "--no-cache"
- "--output-format"
- grouped
- "--preview"
- "--check"
- input.py
---

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:
@@ -7,7 +7,6 @@ info:
- "--no-cache"
- "--output-format"
- pylint
- "--preview"
- "--check"
- input.py
---

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/format.rs
source: crates/ruff/tests/format.rs
info:
program: ruff
args:

View File

@@ -1,38 +0,0 @@
---
source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- rule
- F401
- "--output-format"
- json
---
success: true
exit_code: 0
----- stdout -----
{
"name": "unused-import",
"code": "F401",
"linter": "Pyflakes",
"summary": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
"message_formats": [
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias",
"`{name}` imported but unused"
],
"fix": "Fix is sometimes available.",
"fix_availability": "Sometimes",
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
"preview": false,
"status": {
"Stable": {
"since": "v0.0.18"
}
},
"source_location": {
"file": "<FILE>",
"line": 145
}
}
----- stderr -----

View File

@@ -1,140 +0,0 @@
---
source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- rule
- F401
- "--output-format"
- text
---
success: true
exit_code: 0
----- stdout -----
# unused-import (F401)
Derived from the **Pyflakes** linter.
Fix is sometimes available.
## What it does
Checks for unused imports.
## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. They also increase the cognitive load of reading the code.
If an import statement is used to check for the availability or existence
of a module, consider using `importlib.util.find_spec` instead.
If an import statement is used to re-export a symbol as part of a module's
public interface, consider using a "redundant" import alias, which
instructs Ruff (and other tools) to respect the re-export, and avoid
marking it as unused, as in:
```python
from module import member as member
```
Alternatively, you can use `__all__` to declare a symbol as part of the module's
interface, as in:
```python
# __init__.py
import some_module
__all__ = ["some_module"]
```
## Preview
When [preview] is enabled (and certain simplifying assumptions
are met), we analyze all import statements for a given module
when determining whether an import is used, rather than simply
the last of these statements. This can result in both different and
more import statements being marked as unused.
For example, if a module consists of
```python
import a
import a.b
```
then both statements are marked as unused under [preview], whereas
only the second is marked as unused under stable behavior.
As another example, if a module consists of
```python
import a.b
import a
a.b.foo()
```
then a diagnostic will only be emitted for the first line under [preview],
whereas a diagnostic would only be emitted for the second line under
stable behavior.
Note that this behavior is somewhat subjective and is designed
to conform to the developer's intuition rather than Python's actual
execution. To wit, the statement `import a.b` automatically executes
`import a`, so in some sense `import a` is _always_ redundant
in the presence of `import a.b`.
## Fix safety
Fixes to remove unused imports are safe, except in `__init__.py` files.
Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the
type of the unused import. Ruff will suggest a safe fix to export first-party imports with
either a redundant alias or, if already present in the file, an `__all__` entry. If multiple
`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix
to remove third-party and standard library imports -- the fix is unsafe because the module's
interface changes.
See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)
for more details on how Ruff
determines whether an import is first or third-party.
## Example
```python
import numpy as np # unused import
def area(radius):
return 3.14 * radius**2
```
Use instead:
```python
def area(radius):
return 3.14 * radius**2
```
To check the availability of a module, use `importlib.util.find_spec`:
```python
from importlib.util import find_spec
if find_spec("numpy") is not None:
print("numpy is installed")
else:
print("numpy is not installed")
```
## Options
- `lint.ignore-init-module-imports`
- `lint.pyflakes.allowed-unused-imports`
## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)
[preview]: https://docs.astral.sh/ruff/preview/
----- stderr -----

View File

@@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
110,
100,
);
bench_project(&benchmark, criterion);
@@ -684,7 +684,7 @@ fn anyio(criterion: &mut Criterion) {
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
150,
100,
);
bench_project(&benchmark, criterion);

View File

@@ -210,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
320,
100,
);
static STATIC_FRAME: Benchmark = Benchmark::new(
@@ -226,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
800,
630,
);
#[track_caller]

View File

@@ -200,12 +200,7 @@ impl System for OsSystem {
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
/// when setting [`WalkDirectoryBuilder::standard_filters`] to true.
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
WalkDirectoryBuilder::new(
path,
OsDirectoryWalker {
cwd: self.current_directory().to_path_buf(),
},
)
WalkDirectoryBuilder::new(path, OsDirectoryWalker {})
}
fn glob(
@@ -459,9 +454,7 @@ struct ListedDirectory {
}
#[derive(Debug)]
struct OsDirectoryWalker {
cwd: SystemPathBuf,
}
struct OsDirectoryWalker;
impl DirectoryWalker for OsDirectoryWalker {
fn walk(
@@ -480,7 +473,6 @@ impl DirectoryWalker for OsDirectoryWalker {
};
let mut builder = ignore::WalkBuilder::new(first.as_std_path());
builder.current_dir(self.cwd.as_std_path());
builder.standard_filters(standard_filters);
builder.hidden(hidden);

View File

@@ -8,7 +8,6 @@ use std::path::PathBuf;
use anyhow::Result;
use itertools::Itertools;
use regex::{Captures, Regex};
use ruff_linter::codes::RuleGroup;
use strum::IntoEnumIterator;
use ruff_linter::FixAvailability;
@@ -32,47 +31,6 @@ pub(crate) fn main(args: &Args) -> Result<()> {
let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
let status_text = match rule.group() {
RuleGroup::Stable { since } => {
format!(
r#"Added in <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>"#
)
}
RuleGroup::Preview { since } => {
format!(
r#"Preview (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
RuleGroup::Deprecated { since } => {
format!(
r#"Deprecated (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
RuleGroup::Removed { since } => {
format!(
r#"Removed (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
};
let _ = writeln!(
&mut output,
r#"<small>
{status_text} ·
<a href="https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20(%27{encoded_name}%27%20OR%20{rule_code})" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
</small>
"#,
encoded_name =
url::form_urlencoded::byte_serialize(rule.name().as_str().as_bytes())
.collect::<String>(),
rule_code = rule.noqa_code(),
file =
url::form_urlencoded::byte_serialize(rule.file().replace('\\', "/").as_bytes())
.collect::<String>(),
line = rule.line(),
);
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
if linter.url().is_some() {
let common_prefix: String = match linter.common_prefix() {

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{Result, bail};
use pretty_assertions::StrComparison;
use schemars::generate::SchemaSettings;
use schemars::schema_for;
use crate::ROOT_DIR;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
@@ -17,9 +17,7 @@ pub(crate) struct Args {
}
pub(crate) fn main(args: &Args) -> Result<()> {
let settings = SchemaSettings::draft07();
let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<Options>();
let schema = schema_for!(Options);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
let filename = "ruff.schema.json";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);

View File

@@ -32,24 +32,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
table_out.push('\n');
for rule in rules {
let status_token = match rule.group() {
RuleGroup::Removed { since } => {
RuleGroup::Removed => {
format!(
"<span {SYMBOL_STYLE} title='Rule was removed in {since}'>{REMOVED_SYMBOL}</span>"
"<span {SYMBOL_STYLE} title='Rule has been removed'>{REMOVED_SYMBOL}</span>"
)
}
RuleGroup::Deprecated { since } => {
RuleGroup::Deprecated => {
format!(
"<span {SYMBOL_STYLE} title='Rule has been deprecated since {since}'>{WARNING_SYMBOL}</span>"
"<span {SYMBOL_STYLE} title='Rule has been deprecated'>{WARNING_SYMBOL}</span>"
)
}
RuleGroup::Preview { since } => {
format!(
"<span {SYMBOL_STYLE} title='Rule has been in preview since {since}'>{PREVIEW_SYMBOL}</span>"
)
}
RuleGroup::Stable { since } => {
format!("<span {SYMBOL_STYLE} title='Rule has been stable since {since}'></span>")
RuleGroup::Preview => {
format!("<span {SYMBOL_STYLE} title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
}
RuleGroup::Stable => format!("<span {SYMBOL_STYLE}></span>"),
};
let fix_token = match rule.fixable() {

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{Result, bail};
use pretty_assertions::StrComparison;
use schemars::generate::SchemaSettings;
use schemars::schema_for;
use crate::ROOT_DIR;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
@@ -17,9 +17,7 @@ pub(crate) struct Args {
}
pub(crate) fn main(args: &Args) -> Result<()> {
let settings = SchemaSettings::draft07();
let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<Options>();
let schema = schema_for!(Options);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
let filename = "ty.schema.json";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.14.2"
version = "0.14.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -10,7 +10,6 @@ from airflow.datasets import (
)
from airflow.datasets.manager import DatasetManager
from airflow.lineage.hook import DatasetLineageInfo, HookLineageCollector
from airflow.models.dag import DAG
from airflow.providers.amazon.aws.auth_manager.aws_auth_manager import AwsAuthManager
from airflow.providers.apache.beam.hooks import BeamHook, NotAir302HookError
from airflow.providers.google.cloud.secrets.secret_manager import (
@@ -21,7 +20,6 @@ from airflow.providers_manager import ProvidersManager
from airflow.secrets.base_secrets import BaseSecretsBackend
from airflow.secrets.local_filesystem import LocalFilesystemBackend
# airflow.Dataset
dataset_from_root = DatasetFromRoot()
dataset_from_root.iter_datasets()
@@ -58,10 +56,6 @@ hlc.add_input_dataset()
hlc.add_output_dataset()
hlc.collected_datasets()
# airflow.models.dag.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()
# airflow.providers.amazon.auth_manager.aws_auth_manager
aam = AwsAuthManager()
aam.is_authorized_dataset()
@@ -102,15 +96,3 @@ base_secret_backend.get_connections()
# airflow.secrets.local_filesystem
lfb = LocalFilesystemBackend()
lfb.get_connections()
from airflow.models import DAG
# airflow.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()
from airflow import DAG
# airflow.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()

View File

@@ -91,20 +91,10 @@ get_unique_task_id()
task_decorator_factory()
from airflow.models import DagParam, Param, ParamsDict
from airflow.models import Param
# airflow.models
Param()
DagParam()
ParamsDict()
from airflow.models.param import DagParam, Param, ParamsDict
# airflow.models.param
Param()
DagParam()
ParamsDict()
from airflow.sensors.base import (

View File

@@ -1,87 +0,0 @@
"""Test FAST002 ellipsis handling."""
from fastapi import Body, Cookie, FastAPI, Header, Query
app = FastAPI()
# Cases that should be fixed - ellipsis should be removed
@app.get("/test1")
async def test_ellipsis_query(
# This should become: param: Annotated[str, Query(description="Test param")]
param: str = Query(..., description="Test param"),
) -> str:
return param
@app.get("/test2")
async def test_ellipsis_header(
# This should become: auth: Annotated[str, Header(description="Auth header")]
auth: str = Header(..., description="Auth header"),
) -> str:
return auth
@app.post("/test3")
async def test_ellipsis_body(
# This should become: data: Annotated[dict, Body(description="Request body")]
data: dict = Body(..., description="Request body"),
) -> dict:
return data
@app.get("/test4")
async def test_ellipsis_cookie(
# This should become: session: Annotated[str, Cookie(description="Session ID")]
session: str = Cookie(..., description="Session ID"),
) -> str:
return session
@app.get("/test5")
async def test_simple_ellipsis(
# This should become: id: Annotated[str, Query()]
id: str = Query(...),
) -> str:
return id
@app.get("/test6")
async def test_multiple_kwargs_with_ellipsis(
# This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
param: str = Query(..., description="Test", min_length=1, max_length=10),
) -> str:
return param
# Cases with actual default values - these should preserve the default
@app.get("/test7")
async def test_with_default_value(
# This should become: param: Annotated[str, Query(description="Test")] = "default"
param: str = Query("default", description="Test"),
) -> str:
return param
@app.get("/test8")
async def test_with_default_none(
# This should become: param: Annotated[str | None, Query(description="Test")] = None
param: str | None = Query(None, description="Test"),
) -> str:
return param or "empty"
@app.get("/test9")
async def test_mixed_parameters(
# First param should be fixed with default preserved
optional_param: str = Query("default", description="Optional"),
# Second param should not be fixed because of the preceding default
required_param: str = Query(..., description="Required"),
# Third param should be fixed with default preserved
another_optional_param: int = Query(42, description="Another optional"),
) -> str:
return f"{required_param}-{optional_param}-{another_optional_param}"

View File

@@ -256,12 +256,3 @@ async def f4():
@app.get("/f5/{param: int}")
async def f5():
return locals()
# https://github.com/astral-sh/ruff/issues/20941
@app.get("/imports/{import}")
async def get_import():
...
@app.get("/debug/{__debug__}")
async def get_debug():
...

View File

@@ -1,10 +1,12 @@
from itertools import count, cycle, repeat
# Errors
zip()
zip(range(3))
zip("a", "b")
zip("a", "b", *zip("c"))
zip(zip("a", "b"), strict=False)
zip(zip("a", strict=True),"b")
zip(zip("a"), strict=False)
zip(zip("a", strict=True))
# OK
zip(range(3), strict=True)
@@ -25,10 +27,3 @@ zip([1, 2, 3], repeat(1, times=4))
import builtins
# Still an error even though it uses the qualified name
builtins.zip([1, 2, 3])
# Regression https://github.com/astral-sh/ruff/issues/20997
# Ok
zip()
zip(range(3))
# Error
zip(*lot_of_iterators)

View File

@@ -31,6 +31,3 @@ map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3]))
map(lambda x, y: x + y, [1, 2, 3], repeat(1))
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None))
map(lambda x, y: x + y, [1, 2, 3], count())
# Regression https://github.com/astral-sh/ruff/issues/20997
map(f, *lots_of_iterators)

View File

@@ -46,9 +46,3 @@ class CorrectModel(models.Model):
max_length=255, null=True, blank=True, unique=True
)
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)
class IncorrectModelWithSimpleAnnotations(models.Model):
charfield: models.CharField = models.CharField(max_length=255, null=True)
textfield: models.TextField = models.TextField(max_length=255, null=True)
slugfield: models.SlugField = models.SlugField(max_length=255, null=True)

View File

@@ -2,52 +2,3 @@ _(f"{'value'}")
# Don't trigger for t-strings
_(t"{'value'}")
gettext(f"{'value'}")
ngettext(f"{'value'}", f"{'values'}", 2)
_gettext(f"{'value'}") # no lint
gettext_fn(f"{'value'}") # no lint
# https://github.com/astral-sh/ruff/issues/19028 - import aliases
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext(f"Hello, {name}!")
gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
gettext_fn(f"Hello, {name}!")
ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_(f"{'mollusk'}"),
_(f"{'albatross'}"),
_(f"{'rat'}"),
_(f"{'penguin'}"),
_(f"{'python'}"),]
del _
for a in animals:
print(_(a))
print(_(f"{a}"))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._(f"{'value'}")
builtins.gettext(f"{'value'}")
builtins.ngettext(f"{'value'}", f"{'values'}", 2)

View File

@@ -1,49 +1 @@
_("{}".format("line"))
gettext("{}".format("line"))
ngettext("{}".format("line"), "{}".format("lines"), 2)
_gettext("{}".format("line")) # no lint
gettext_fn("{}".format("line")) # no lint
# https://github.com/astral-sh/ruff/issues/19028
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext("Hello, {}!".format(name))
gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
gettext_fn("Hello, {}!".format(name))
ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_("{}".format("mollusk")),
_("{}".format("albatross")),
_("{}".format("rat")),
_("{}".format("penguin")),
_("{}".format("python")),]
del _
for a in animals:
print(_(a))
print(_("{}".format(a)))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._("{}".format("line"))
builtins.gettext("{}".format("line"))
builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)

View File

@@ -1,49 +1 @@
_("%s" % "line")
gettext("%s" % "line")
ngettext("%s" % "line", "%s" % "lines", 2)
_gettext("%s" % "line") # no lint
gettext_fn("%s" % "line") # no lint
# https://github.com/astral-sh/ruff/issues/19028
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext("Hello, %s!" % name)
gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
gettext_fn("Hello, %s!" % name)
ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_("%s" %"mollusk"),
_("%s" %"albatross"),
_("%s" %"rat"),
_("%s" %"penguin"),
_("%s" %"python"),]
del _
for a in animals:
print(_(a))
print(_("%s" % a))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._("%s" % "line")
builtins.gettext("%s" % "line")
builtins.ngettext("%s" % "line", "%s" % "lines", 2)

View File

@@ -1,7 +0,0 @@
# Regression test for https://github.com/astral-sh/ruff/issues/21023
'' '
"" ""
'' '' '
"" "" "
f"" f"
f"" f"" f"

View File

@@ -359,29 +359,3 @@ class Generic5(list[PotentialTypeVar]):
def __new__(cls: type[Generic5]) -> Generic5: ...
def __enter__(self: Generic5) -> Generic5: ...
# Test cases based on issue #20781 - metaclasses that triggers IsMetaclass::Maybe
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
def __new__(
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
) -> MetaclassInWhichSelfCannotBeUsed5:
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
return new_class
import django.db.models.base
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
...
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
...
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
...

View File

@@ -252,28 +252,3 @@ from some_module import PotentialTypeVar
class Generic5(list[PotentialTypeVar]):
def __new__(cls: type[Generic5]) -> Generic5: ...
def __enter__(self: Generic5) -> Generic5: ...
# Test case based on issue #20781 - metaclass that triggers IsMetaclass::Maybe
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
def __new__(
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
) -> MetaclassInWhichSelfCannotBeUsed5: ...
import django.db.models.base
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
...
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
...
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
...

View File

@@ -34,7 +34,3 @@ def foo():
# https://github.com/astral-sh/ruff/issues/18776
flag_stars = {}
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...
# Regression test for https://github.com/astral-sh/ruff/issues/18778
d = {}
for country, stars in zip(d.keys(*x), d.values("hello")):...

View File

@@ -14,14 +14,3 @@ def f():
import os
print(os)
# regression test for https://github.com/astral-sh/ruff/issues/21121
from dataclasses import KW_ONLY, dataclass
@dataclass
class DataClass:
a: int
_: KW_ONLY # should be an exception to TC003, even with future-annotations
b: int

View File

@@ -370,22 +370,3 @@ class Foo:
The flag converter instance with all flags parsed.
"""
return
# OK
def baz(x: int) -> int:
"""
Show a `Warnings` DOC102 false positive.
Parameters
----------
x : int
Warnings
--------
This function demonstrates a DOC102 false positive
Returns
-------
int
"""
return x

View File

@@ -81,55 +81,3 @@ def calculate_speed(distance: float, time: float) -> float:
except TypeError:
print("Not a number? Shame on you!")
raise
# This should NOT trigger DOC502 because OSError is explicitly re-raised
def f():
"""Do nothing.
Raises:
OSError: If the OS errors.
"""
try:
pass
except OSError as e:
raise e
# This should NOT trigger DOC502 because OSError is explicitly re-raised with from None
def g():
"""Do nothing.
Raises:
OSError: If the OS errors.
"""
try:
pass
except OSError as e:
raise e from None
# This should NOT trigger DOC502 because ValueError is explicitly re-raised from tuple exception
def h():
"""Do nothing.
Raises:
ValueError: If something goes wrong.
"""
try:
pass
except (ValueError, TypeError) as e:
raise e
# This should NOT trigger DOC502 because TypeError is explicitly re-raised from tuple exception
def i():
"""Do nothing.
Raises:
TypeError: If something goes wrong.
"""
try:
pass
except (ValueError, TypeError) as e:
raise e

View File

@@ -1,21 +0,0 @@
class C:
f = lambda self: __class__
print(C().f().__name__)
# Test: nested lambda
class D:
g = lambda self: (lambda: __class__)
print(D().g()().__name__)
# Test: lambda outside class (should still fail)
h = lambda: __class__
# Test: lambda referencing module-level variable (should not be flagged as F821)
import uuid
class E:
uuid = lambda: str(uuid.uuid4())

View File

@@ -1,131 +0,0 @@
"""Test cases for PLR1708 stop-iteration-return."""
# Valid cases - should not trigger the rule
def normal_function():
raise StopIteration # Not a generator, should not trigger
def normal_function_with_value():
raise StopIteration("value") # Not a generator, should not trigger
def generator_with_return():
yield 1
yield 2
return "finished" # This is the correct way
def generator_with_yield_from():
yield from [1, 2, 3]
def generator_without_stop_iteration():
yield 1
yield 2
# No explicit termination
def generator_with_other_exception():
yield 1
raise ValueError("something else") # Different exception
# Invalid cases - should trigger the rule
def generator_with_stop_iteration():
yield 1
yield 2
raise StopIteration # Should trigger
def generator_with_stop_iteration_value():
yield 1
yield 2
raise StopIteration("finished") # Should trigger
def generator_with_stop_iteration_expr():
yield 1
yield 2
raise StopIteration(1 + 2) # Should trigger
def async_generator_with_stop_iteration():
yield 1
yield 2
raise StopIteration("async") # Should trigger
def nested_generator():
def inner_gen():
yield 1
raise StopIteration("inner") # Should trigger
yield from inner_gen()
def generator_in_class():
class MyClass:
def generator_method(self):
yield 1
raise StopIteration("method") # Should trigger
return MyClass
# Complex cases
def complex_generator():
try:
yield 1
yield 2
raise StopIteration("complex") # Should trigger
except ValueError:
yield 3
finally:
pass
def generator_with_conditional_stop_iteration(condition):
yield 1
if condition:
raise StopIteration("conditional") # Should trigger
yield 2
# Edge cases
def generator_with_bare_stop_iteration():
yield 1
raise StopIteration # Should trigger (no arguments)
def generator_with_stop_iteration_in_loop():
for i in range(5):
yield i
if i == 3:
raise StopIteration("loop") # Should trigger
# Should not trigger - different exceptions
def generator_with_runtime_error():
yield 1
raise RuntimeError("not StopIteration") # Should not trigger
def generator_with_custom_exception():
yield 1
raise CustomException("custom") # Should not trigger
class CustomException(Exception):
pass
# Generator comprehensions should not be affected
list_comp = [x for x in range(10)] # Should not trigger
# Lambda in generator context
def generator_with_lambda():
yield 1
func = lambda x: x # Just a regular lambda
yield 2

View File

@@ -269,10 +269,3 @@ raise ValueError(
# Not a valid type annotation but this test shouldn't result in a panic.
# Refer: https://github.com/astral-sh/ruff/issues/11736
x: "'{} + {}'.format(x, y)"
# Regression https://github.com/astral-sh/ruff/issues/21000
# Fix should parenthesize walrus
if __name__ == "__main__":
number = 0
string = "{}".format(number := number + 1)
print(string)

View File

@@ -26,8 +26,3 @@
"{.real}".format({1, 2})
"{.real}".format({1: 2, 3: 4})
"{}".format((i for i in range(2)))
# https://github.com/astral-sh/ruff/issues/21017
"{.real}".format(1_2)
"{0.real}".format(1_2)
"{a.real}".format(a=1_2)

View File

@@ -1,17 +0,0 @@
"""
Regression test for an ecosystem hit on
https://github.com/astral-sh/ruff/pull/21125.
We should mark all of the components of special dataclass annotations as
runtime-required, not just the first layer.
"""
from dataclasses import dataclass
from typing import ClassVar, Optional
@dataclass(frozen=True)
class EmptyCell:
_singleton: ClassVar[Optional["EmptyCell"]] = None
# the behavior of _singleton above should match a non-ClassVar
_doubleton: "EmptyCell"

View File

@@ -1,13 +0,0 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP046 doesn't apply in this case.
"""
from typing import Generic
from typing_extensions import TypeVar
T = TypeVar("T", default=str)
class DefaultTypeVar(Generic[T]):
var: T

View File

@@ -1,12 +0,0 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP047 doesn't apply in this case.
"""
from typing_extensions import TypeVar
T = TypeVar("T", default=int)
def default_var(var: T) -> T:
return var

View File

@@ -69,19 +69,3 @@ Decimal(float("\N{space}\N{hyPHen-MINus}nan"))
Decimal(float("\x20\N{character tabulation}\N{hyphen-minus}nan"))
Decimal(float(" -" "nan"))
Decimal(float("-nAn"))
# Test cases for digit separators (safe fixes)
# https://github.com/astral-sh/ruff/issues/20572
Decimal("15_000_000") # Safe fix: normalizes separators, becomes Decimal(15_000_000)
Decimal("1_234_567") # Safe fix: normalizes separators, becomes Decimal(1_234_567)
Decimal("-5_000") # Safe fix: normalizes separators, becomes Decimal(-5_000)
Decimal("+9_999") # Safe fix: normalizes separators, becomes Decimal(+9_999)
# Test cases for non-thousands separators
Decimal("12_34_56_78") # Safe fix: preserves non-thousands separators
Decimal("1234_5678") # Safe fix: preserves non-thousands separators
# Separators _and_ leading zeros
Decimal("0001_2345")
Decimal("000_1_2345")
Decimal("000_000")

View File

@@ -43,29 +43,3 @@ logging.warning("Value: %r", repr(42))
logging.error("Error: %r", repr([1, 2, 3]))
logging.info("Debug info: %s", repr("test\nstring"))
logging.warning("Value: %s", repr(42))
# %s + ascii()
logging.info("ASCII: %s", ascii("Hello\nWorld"))
logging.warning("ASCII: %s", ascii("test"))
# %s + oct()
logging.info("Octal: %s", oct(42))
logging.warning("Octal: %s", oct(255))
# %s + hex()
logging.info("Hex: %s", hex(42))
logging.warning("Hex: %s", hex(255))
# Test with imported functions
from logging import info, log
info("ASCII: %s", ascii("Hello\nWorld"))
log(logging.INFO, "ASCII: %s", ascii("test"))
info("Octal: %s", oct(42))
log(logging.INFO, "Octal: %s", oct(255))
info("Hex: %s", hex(42))
log(logging.INFO, "Hex: %s", hex(255))

View File

@@ -1,12 +0,0 @@
async def f(): return [[x async for x in foo(n)] for n in range(3)]
async def test(): return [[x async for x in elements(n)] async for n in range(3)]
async def f(): [x for x in foo()] and [x async for x in foo()]
async def f():
def g(): ...
[x async for x in foo()]
[x async for x in y]

View File

@@ -1,3 +0,0 @@
match x:
case Point(x=1, x=2):
pass

View File

@@ -1,3 +0,0 @@
match x:
case {'key': 1, 'key': 2}:
pass

View File

@@ -1 +0,0 @@
class C[T, T]: pass

View File

@@ -1,29 +0,0 @@
def f(a):
global a
def g(a):
if True:
global a
def h(a):
def inner():
global a
def i(a):
try:
global a
except Exception:
pass
def f(a):
a = 1
global a
def f(a):
a = 1
a = 2
global a
def f(a):
class Inner:
global a # ok

View File

@@ -1,8 +0,0 @@
type X[T: (yield 1)] = int
type Y = (yield 1)
def f[T](x: int) -> (y := 3): return x
class C[T]((yield from [object])):
pass

View File

@@ -1,8 +0,0 @@
def func():
return *x
for *x in range(10):
pass
def func():
yield *x

View File

@@ -1,11 +0,0 @@
match value:
case _:
pass
case 1:
pass
match value:
case irrefutable:
pass
case 1:
pass

View File

@@ -1,5 +0,0 @@
match x:
case [a, a]:
pass
case _:
pass

View File

@@ -1 +0,0 @@
[x:= 2 for x in range(2)]

View File

@@ -1,7 +0,0 @@
__debug__ = False
def process(__debug__):
pass
class Generic[__debug__]:
pass

View File

@@ -1034,7 +1034,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
Rule::FormatInGetTextFuncCall,
Rule::PrintfInGetTextFuncCall,
]) && flake8_gettext::is_gettext_func_call(
checker,
func,
&checker.settings().flake8_gettext.functions_names,
) {

View File

@@ -951,9 +951,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.is_rule_enabled(Rule::MisplacedBareRaise) {
pylint::rules::misplaced_bare_raise(checker, raise);
}
if checker.is_rule_enabled(Rule::StopIterationReturn) {
pylint::rules::stop_iteration_return(checker, raise);
}
}
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
if checker.is_rule_enabled(Rule::GlobalStatement) {

View File

@@ -723,9 +723,7 @@ impl SemanticSyntaxContext for Checker<'_> {
| SemanticSyntaxErrorKind::IrrefutableCasePattern(_)
| SemanticSyntaxErrorKind::SingleStarredAssignment
| SemanticSyntaxErrorKind::WriteToDebug(_)
| SemanticSyntaxErrorKind::DifferentMatchPatternBindings
| SemanticSyntaxErrorKind::InvalidExpression(..)
| SemanticSyntaxErrorKind::GlobalParameter(_)
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
| SemanticSyntaxErrorKind::InvalidStarExpression
@@ -847,26 +845,6 @@ impl SemanticSyntaxContext for Checker<'_> {
}
false
}
fn is_bound_parameter(&self, name: &str) -> bool {
for scope in self.semantic.current_scopes() {
match scope.kind {
ScopeKind::Class(_) => return false,
ScopeKind::Function(ast::StmtFunctionDef { parameters, .. })
| ScopeKind::Lambda(ast::ExprLambda {
parameters: Some(parameters),
..
}) => return parameters.includes(name),
ScopeKind::Lambda(_)
| ScopeKind::Generator { .. }
| ScopeKind::Module
| ScopeKind::Type
| ScopeKind::DunderClassCell => {}
}
}
false
}
}
impl<'a> Visitor<'a> for Checker<'a> {
@@ -1400,14 +1378,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(annotation);
}
AnnotationContext::RuntimeEvaluated
if flake8_type_checking::helpers::is_dataclass_meta_annotation(
annotation,
self.semantic(),
) =>
{
self.visit_runtime_required_annotation(annotation);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(annotation);
}
@@ -2124,7 +2094,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
| Expr::DictComp(_)
| Expr::SetComp(_) => {
self.analyze.scopes.push(self.semantic.scope_id);
self.semantic.pop_scope(); // Lambda/Generator/Comprehension scope
self.semantic.pop_scope();
}
_ => {}
}
@@ -3049,35 +3019,7 @@ impl<'a> Checker<'a> {
if let Some(parameters) = parameters {
self.visit_parameters(parameters);
}
// Here we add the implicit scope surrounding a lambda which allows code in the
// lambda to access `__class__` at runtime when the lambda is defined within a class.
// See the `ScopeKind::DunderClassCell` docs for more information.
let added_dunder_class_scope = if self
.semantic
.current_scopes()
.any(|scope| scope.kind.is_class())
{
self.semantic.push_scope(ScopeKind::DunderClassCell);
let binding_id = self.semantic.push_binding(
TextRange::default(),
BindingKind::DunderClassCell,
BindingFlags::empty(),
);
self.semantic
.current_scope_mut()
.add("__class__", binding_id);
true
} else {
false
};
self.visit_expr(body);
// Pop the DunderClassCell scope if it was added
if added_dunder_class_scope {
self.semantic.pop_scope();
}
}
}
self.semantic.restore(snapshot);

View File

@@ -130,7 +130,6 @@ pub(crate) fn check_noqa(
let edit = delete_comment(directive.range(), locator);
let mut diagnostic = context
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
diagnostic.set_fix(Fix::safe_edit(edit));
}
}
@@ -227,7 +226,6 @@ pub(crate) fn check_noqa(
},
directive.range(),
);
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
diagnostic.set_fix(Fix::safe_edit(edit));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,6 @@ pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Warnings,
SectionKind::Warns,
SectionKind::Yields,
// Google-only
SectionKind::Args,
@@ -34,5 +32,7 @@ pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
SectionKind::Tip,
SectionKind::Todo,
SectionKind::Warning,
SectionKind::Warnings,
SectionKind::Warns,
SectionKind::Yield,
];

View File

@@ -11,14 +11,11 @@ pub(crate) static NUMPY_SECTIONS: &[SectionKind] = &[
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Warnings,
SectionKind::Warns,
SectionKind::Yields,
// NumPy-only
SectionKind::ExtendedSummary,
SectionKind::OtherParams,
SectionKind::OtherParameters,
SectionKind::Parameters,
SectionKind::Receives,
SectionKind::ShortSummary,
];

View File

@@ -36,7 +36,6 @@ pub(crate) enum SectionKind {
OtherParameters,
Parameters,
Raises,
Receives,
References,
Return,
Returns,
@@ -77,7 +76,6 @@ impl SectionKind {
"other parameters" => Some(Self::OtherParameters),
"parameters" => Some(Self::Parameters),
"raises" => Some(Self::Raises),
"receives" => Some(Self::Receives),
"references" => Some(Self::References),
"return" => Some(Self::Return),
"returns" => Some(Self::Returns),
@@ -119,7 +117,6 @@ impl SectionKind {
Self::OtherParameters => "Other Parameters",
Self::Parameters => "Parameters",
Self::Raises => "Raises",
Self::Receives => "Receives",
Self::References => "References",
Self::Return => "Return",
Self::Returns => "Returns",

View File

@@ -14,7 +14,7 @@ use ruff_text_size::TextSize;
/// The length of a line of text that is considered too long.
///
/// The allowed range of values is 1..=320
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct LineLength(
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16,
@@ -46,21 +46,6 @@ impl fmt::Display for LineLength {
}
}
impl<'de> serde::Deserialize<'de> for LineLength {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u16::deserialize(deserializer)?;
Self::try_from(value).map_err(|_| {
serde::de::Error::custom(format!(
"line-length must be between 1 and {} (got {value})",
Self::MAX,
))
})
}
}
impl CacheKey for LineLength {
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u16(self.0.get());

View File

@@ -919,6 +919,17 @@ mod tests {
Ok(())
}
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
/// file.
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Diagnostic> {
let contents = dedent(contents);
test_contents_syntax_errors(
&SourceKind::Python(contents.to_string()),
Path::new("<filename>"),
settings,
)
}
/// A custom test runner that prints syntax errors in addition to other diagnostics. Adapted
/// from `flakes` in pyflakes/mod.rs.
fn test_contents_syntax_errors(
@@ -961,38 +972,209 @@ mod tests {
}
#[test_case(
Path::new("async_comprehension_outside_async_function.py"),
PythonVersion::PY311
"async_in_sync_error_on_310",
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
PythonVersion::PY310,
"AsyncComprehensionOutsideAsyncFunction"
)]
#[test_case(
Path::new("async_comprehension_outside_async_function.py"),
PythonVersion::PY310
"async_in_sync_okay_on_311",
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
PythonVersion::PY311,
"AsyncComprehensionOutsideAsyncFunction"
)]
#[test_case(Path::new("rebound_comprehension.py"), PythonVersion::PY310)]
#[test_case(Path::new("duplicate_type_parameter.py"), PythonVersion::PY312)]
#[test_case(Path::new("multiple_case_assignment.py"), PythonVersion::PY310)]
#[test_case(Path::new("duplicate_match_key.py"), PythonVersion::PY310)]
#[test_case(Path::new("duplicate_match_class_attribute.py"), PythonVersion::PY310)]
#[test_case(Path::new("invalid_star_expression.py"), PythonVersion::PY310)]
#[test_case(Path::new("irrefutable_case_pattern.py"), PythonVersion::PY310)]
#[test_case(Path::new("single_starred_assignment.py"), PythonVersion::PY310)]
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY312)]
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
let snapshot = format!(
"semantic_syntax_error_{}_{}",
path.to_string_lossy(),
python_version
);
let path = Path::new("resources/test/fixtures/semantic_errors").join(path);
let contents = std::fs::read_to_string(&path)?;
let source_kind = SourceKind::Python(contents);
let diagnostics = test_contents_syntax_errors(
&source_kind,
&path,
#[test_case(
"async_in_sync_okay_on_310",
"async def test(): return [[x async for x in elements(n)] async for n in range(3)]",
PythonVersion::PY310,
"AsyncComprehensionOutsideAsyncFunction"
)]
#[test_case(
"deferred_function_body",
"
async def f(): [x for x in foo()] and [x async for x in foo()]
async def f():
def g(): ...
[x async for x in foo()]
",
PythonVersion::PY310,
"AsyncComprehensionOutsideAsyncFunction"
)]
#[test_case(
"async_in_sync_false_positive",
"[x async for x in y]",
PythonVersion::PY310,
"AsyncComprehensionOutsideAsyncFunction"
)]
#[test_case(
"rebound_comprehension",
"[x:= 2 for x in range(2)]",
PythonVersion::PY310,
"ReboundComprehensionVariable"
)]
#[test_case(
"duplicate_type_param",
"class C[T, T]: pass",
PythonVersion::PY312,
"DuplicateTypeParameter"
)]
#[test_case(
"multiple_case_assignment",
"
match x:
case [a, a]:
pass
case _:
pass
",
PythonVersion::PY310,
"MultipleCaseAssignment"
)]
#[test_case(
"duplicate_match_key",
"
match x:
case {'key': 1, 'key': 2}:
pass
",
PythonVersion::PY310,
"DuplicateMatchKey"
)]
#[test_case(
"duplicate_match_class_attribute",
"
match x:
case Point(x=1, x=2):
pass
",
PythonVersion::PY310,
"DuplicateMatchClassAttribute"
)]
#[test_case(
"invalid_star_expression",
"
def func():
return *x
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"invalid_star_expression_for",
"
for *x in range(10):
pass
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"invalid_star_expression_yield",
"
def func():
yield *x
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"irrefutable_case_pattern_wildcard",
"
match value:
case _:
pass
case 1:
pass
",
PythonVersion::PY310,
"IrrefutableCasePattern"
)]
#[test_case(
"irrefutable_case_pattern_capture",
"
match value:
case irrefutable:
pass
case 1:
pass
",
PythonVersion::PY310,
"IrrefutableCasePattern"
)]
#[test_case(
"single_starred_assignment",
"*a = [1, 2, 3, 4]",
PythonVersion::PY310,
"SingleStarredAssignment"
)]
#[test_case(
"write_to_debug",
"
__debug__ = False
",
PythonVersion::PY310,
"WriteToDebug"
)]
#[test_case(
"write_to_debug_in_function_param",
"
def process(__debug__):
pass
",
PythonVersion::PY310,
"WriteToDebug"
)]
#[test_case(
"write_to_debug_class_type_param",
"
class Generic[__debug__]:
pass
",
PythonVersion::PY312,
"WriteToDebug"
)]
#[test_case(
"invalid_expression_yield_in_type_param",
"
type X[T: (yield 1)] = int
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_yield_in_type_alias",
"
type Y = (yield 1)
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_walrus_in_return_annotation",
"
def f[T](x: int) -> (y := 3): return x
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_yield_from_in_base_class",
"
class C[T]((yield from [object])):
pass
",
PythonVersion::PY312,
"InvalidExpression"
)]
fn test_semantic_errors(
name: &str,
contents: &str,
python_version: PythonVersion,
error_type: &str,
) {
let snapshot = format!("semantic_syntax_error_{error_type}_{name}_{python_version}");
let diagnostics = test_snippet_syntax_errors(
contents,
&LinterSettings {
rules: settings::rule_table::RuleTable::empty(),
unresolved_target_version: python_version.into(),
@@ -1000,11 +1182,7 @@ mod tests {
..Default::default()
},
);
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
assert_diagnostics!(format!("{snapshot}"), diagnostics);
});
Ok(())
assert_diagnostics!(snapshot, diagnostics);
}
#[test_case(PythonVersion::PY310)]

View File

@@ -274,8 +274,3 @@ pub(crate) const fn is_fix_write_whole_file_enabled(settings: &LinterSettings) -
pub(crate) const fn is_typing_extensions_str_alias_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19045
pub(crate) const fn is_extended_i18n_function_matching_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -150,7 +150,7 @@ mod tests {
for rule in Rule::iter() {
let (code, group) = (rule.noqa_code(), rule.group());
if matches!(group, RuleGroup::Removed { .. }) {
if matches!(group, RuleGroup::Removed) {
continue;
}

View File

@@ -209,15 +209,15 @@ impl RuleSelector {
self.all_rules().filter(move |rule| {
match rule.group() {
// Always include stable rules
RuleGroup::Stable { .. } => true,
RuleGroup::Stable => true,
// Enabling preview includes all preview rules unless explicit selection is turned on
RuleGroup::Preview { .. } => {
RuleGroup::Preview => {
preview_enabled && (self.is_exact() || !preview_require_explicit)
}
// Deprecated rules are excluded by default unless explicitly selected
RuleGroup::Deprecated { .. } => !preview_enabled && self.is_exact(),
RuleGroup::Deprecated => !preview_enabled && self.is_exact(),
// Removed rules are included if explicitly selected but will error downstream
RuleGroup::Removed { .. } => self.is_exact(),
RuleGroup::Removed => self.is_exact(),
}
})
}
@@ -257,8 +257,9 @@ pub struct PreviewOptions {
#[cfg(feature = "schemars")]
mod schema {
use itertools::Itertools;
use schemars::{JsonSchema, Schema, SchemaGenerator};
use serde_json::Value;
use schemars::_serde_json::Value;
use schemars::JsonSchema;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use strum::IntoEnumIterator;
use crate::RuleSelector;
@@ -266,65 +267,64 @@ mod schema {
use crate::rule_selector::{Linter, RuleCodePrefix};
impl JsonSchema for RuleSelector {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("RuleSelector")
fn schema_name() -> String {
"RuleSelector".to_string()
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
let enum_values: Vec<String> = [
// Include the non-standard "ALL" selectors.
"ALL".to_string(),
// Include the legacy "C" and "T" selectors.
"C".to_string(),
"T".to_string(),
// Include some common redirect targets for those legacy selectors.
"C9".to_string(),
"T1".to_string(),
"T2".to_string(),
]
.into_iter()
.chain(
RuleCodePrefix::iter()
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
format!("{prefix}{code}")
fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
[
// Include the non-standard "ALL" selectors.
"ALL".to_string(),
// Include the legacy "C" and "T" selectors.
"C".to_string(),
"T".to_string(),
// Include some common redirect targets for those legacy selectors.
"C9".to_string(),
"T1".to_string(),
"T2".to_string(),
]
.into_iter()
.chain(
RuleCodePrefix::iter()
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
format!("{prefix}{code}")
})
.chain(Linter::iter().filter_map(|l| {
let prefix = l.common_prefix();
(!prefix.is_empty()).then(|| prefix.to_string())
})),
)
.filter(|p| {
// Exclude any prefixes where all of the rules are removed
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
RuleSelector::parse_no_redirect(p)
{
!prefix.rules().all(|rule| rule.is_removed())
} else {
true
}
})
.chain(Linter::iter().filter_map(|l| {
let prefix = l.common_prefix();
(!prefix.is_empty()).then(|| prefix.to_string())
})),
)
.filter(|p| {
// Exclude any prefixes where all of the rules are removed
if let Ok(Self::Rule { prefix, .. } | Self::Prefix { prefix, .. }) =
RuleSelector::parse_no_redirect(p)
{
!prefix.rules().all(|rule| rule.is_removed())
} else {
true
}
.filter(|_rule| {
// Filter out all test-only rules
#[cfg(any(feature = "test-rules", test))]
#[expect(clippy::used_underscore_binding)]
if _rule.starts_with("RUF9") || _rule == "PLW0101" {
return false;
}
true
})
.sorted()
.map(Value::String)
.collect(),
),
..SchemaObject::default()
})
.filter(|_rule| {
// Filter out all test-only rules
#[cfg(any(feature = "test-rules", test))]
#[expect(clippy::used_underscore_binding)]
if _rule.starts_with("RUF9") || _rule == "PLW0101" {
return false;
}
true
})
.sorted()
.collect();
let mut schema = schemars::json_schema!({ "type": "string" });
schema.ensure_object().insert(
"enum".to_string(),
Value::Array(enum_values.into_iter().map(Value::String).collect()),
);
schema
}
}
}

View File

@@ -41,7 +41,6 @@ use crate::checkers::ast::Checker;
/// dag = DAG(dag_id="my_dag", schedule=timedelta(days=1))
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct AirflowDagNoScheduleArgument;
impl Violation for AirflowDagNoScheduleArgument {

View File

@@ -35,7 +35,6 @@ use crate::{FixAvailability, Violation};
/// fab_auth_manager_app = FabAuthManager().get_fastapi_app()
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct Airflow3MovedToProvider<'a> {
deprecated: QualifiedName<'a>,
replacement: ProviderReplacement,

View File

@@ -41,7 +41,6 @@ use ruff_text_size::TextRange;
/// yesterday = today - timedelta(days=1)
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct Airflow3Removal {
deprecated: String,
replacement: Replacement,
@@ -492,12 +491,6 @@ fn check_method(checker: &Checker, call_expr: &ExprCall) {
"collected_datasets" => Replacement::AttrName("collected_assets"),
_ => return,
},
["airflow", "models", "dag", "DAG"] | ["airflow", "models", "DAG"] | ["airflow", "DAG"] => {
match attr.as_str() {
"create_dagrun" => Replacement::None,
_ => return,
}
}
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
"initialize_providers_dataset_uri_resources" => {
Replacement::AttrName("initialize_providers_asset_uri_resources")

View File

@@ -51,7 +51,6 @@ use ruff_text_size::TextRange;
/// )
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct Airflow3SuggestedToMoveToProvider<'a> {
deprecated: QualifiedName<'a>,
replacement: ProviderReplacement,

View File

@@ -37,7 +37,6 @@ use ruff_text_size::TextRange;
/// Asset(uri="test://test/")
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct Airflow3SuggestedUpdate {
deprecated: String,
replacement: Replacement,
@@ -262,14 +261,9 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
name: (*rest).to_string(),
}
}
[
"airflow",
"models",
..,
rest @ ("Param" | "ParamsDict" | "DagParam"),
] => Replacement::SourceModuleMoved {
["airflow", "models", "Param"] => Replacement::Rename {
module: "airflow.sdk.definitions.param",
name: (*rest).to_string(),
name: "Param",
},
// airflow.models.baseoperator
@@ -288,12 +282,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
},
// airflow.model..DAG
["airflow", "models", "dag", "DAG"] | ["airflow", "models", "DAG"] | ["airflow", "DAG"] => {
Replacement::SourceModuleMoved {
module: "airflow.sdk",
name: "DAG".to_string(),
}
}
["airflow", "models", .., "DAG"] => Replacement::SourceModuleMoved {
module: "airflow.sdk",
name: "DAG".to_string(),
},
// airflow.sensors.base
[

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