Compare commits
1 Commits
0.14.3
...
david/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6954063c1 |
@@ -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"']
|
||||
@@ -1,12 +1,3 @@
|
||||
# Define serial test group for running tests sequentially.
|
||||
[test-groups]
|
||||
serial = { max-threads = 1 }
|
||||
|
||||
# Run ty file watching tests sequentially to avoid race conditions.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(file_watching)'
|
||||
test-group = 'serial'
|
||||
|
||||
[profile.ci]
|
||||
# Print out output for failing tests as soon as they fail, and also at the end
|
||||
# of the run (for easy scrollability).
|
||||
|
||||
@@ -10,7 +10,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py,pyi,toml}]
|
||||
[*.{rs,py,pyi}]
|
||||
indent_size = 4
|
||||
|
||||
[*.snap]
|
||||
@@ -18,3 +18,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = 100
|
||||
|
||||
[*.toml]
|
||||
indent_size = 4
|
||||
126
.github/workflows/ci.yaml
vendored
126
.github/workflows/ci.yaml
vendored
@@ -12,18 +12,13 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.14"
|
||||
NEXTEST_PROFILE: ci
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -147,7 +142,7 @@ jobs:
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py-fuzzer/**' \
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py_fuzzer/**' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -242,7 +237,7 @@ jobs:
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
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
|
||||
@@ -276,9 +271,11 @@ jobs:
|
||||
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
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:
|
||||
@@ -302,13 +299,9 @@ jobs:
|
||||
|
||||
cargo-test-linux-release:
|
||||
name: "cargo test (linux, release)"
|
||||
# release builds timeout on GitHub runners, so this job is just skipped on forks in the `if` check
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'no-test') &&
|
||||
(needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main')
|
||||
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
|
||||
@@ -332,16 +325,14 @@ jobs:
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
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: depot-windows-2022-16
|
||||
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
|
||||
@@ -361,6 +352,11 @@ jobs:
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
# 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
|
||||
@@ -395,9 +391,26 @@ jobs:
|
||||
cd crates/ty_wasm
|
||||
wasm-pack test --node
|
||||
|
||||
cargo-build-release:
|
||||
name: "cargo build (release)"
|
||||
runs-on: macos-latest
|
||||
if: ${{ 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: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
cargo-build-msrv:
|
||||
name: "cargo build (msrv)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
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
|
||||
@@ -418,6 +431,7 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Build tests"
|
||||
shell: bash
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: cargo "+${MSRV}" test --no-run --all-features
|
||||
@@ -438,7 +452,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 +487,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 +507,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,15 +521,10 @@ 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"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
@@ -529,10 +536,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 +557,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' }}
|
||||
@@ -643,13 +649,13 @@ jobs:
|
||||
|
||||
fuzz-ty:
|
||||
name: "Fuzz for new ty panics"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- 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: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -677,16 +683,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,13 +703,13 @@ 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
|
||||
|
||||
ty-completion-evaluation:
|
||||
name: "ty completion evaluation"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
@@ -716,7 +721,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run ty completion evaluation"
|
||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
|
||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.1 --tasks /tmp/completion-evaluation-tasks.csv
|
||||
- name: "Ensure there are no changes"
|
||||
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
|
||||
|
||||
@@ -750,7 +755,7 @@ jobs:
|
||||
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -787,6 +792,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 +805,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"
|
||||
@@ -924,12 +929,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
(
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.formatter == 'true' ||
|
||||
needs.determine_changes.outputs.linter == 'true'
|
||||
)
|
||||
github.ref == 'refs/heads/main' ||
|
||||
(needs.determine_changes.outputs.formatter == 'true' || needs.determine_changes.outputs.linter == 'true')
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
@@ -963,11 +964,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
(
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
)
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
|
||||
7
.github/workflows/daily_fuzz.yaml
vendored
7
.github/workflows/daily_fuzz.yaml
vendored
@@ -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 \
|
||||
|
||||
10
.github/workflows/mypy_primer.yaml
vendored
10
.github/workflows/mypy_primer.yaml
vendored
@@ -19,10 +19,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
@@ -33,7 +29,7 @@ env:
|
||||
jobs:
|
||||
mypy_primer:
|
||||
name: Run mypy_primer
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -53,6 +49,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
@@ -75,7 +72,7 @@ jobs:
|
||||
|
||||
memory_usage:
|
||||
name: Run memory statistics
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -95,6 +92,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
TY_MAX_PARALLELISM: 1 # for deterministic memory numbers
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
|
||||
5
.github/workflows/publish-playground.yml
vendored
5
.github/workflows/publish-playground.yml
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/publish-ty-playground.yml
vendored
1
.github/workflows/publish-ty-playground.yml
vendored
@@ -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
|
||||
|
||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
76
.github/workflows/sync_typeshed.yaml
vendored
76
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,10 +16,8 @@ name: Sync typeshed
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Attempts to update any snapshots that might have changed
|
||||
# (this sub-step is allowed to fail)
|
||||
# d. Commits the changes and pushes them to the same upstream branch
|
||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# c. Commits the changes and pushes them to the same upstream branch
|
||||
# d. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
|
||||
on:
|
||||
@@ -28,18 +26,8 @@ on:
|
||||
# Run on the 1st and the 15th of every month:
|
||||
- cron: "0 0 1,15 * *"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
# Don't set this flag globally for the workflow: it does strange things
|
||||
# to the snapshots in the `cargo insta test --accept` step in the MacOS job.
|
||||
#
|
||||
# FORCE_COLOR: 1
|
||||
|
||||
CARGO_TERM_COLOR: always
|
||||
NEXTEST_PROFILE: "ci"
|
||||
FORCE_COLOR: 1
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
# The name of the upstream branch that the first worker creates,
|
||||
@@ -98,8 +86,6 @@ jobs:
|
||||
git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
|
||||
- name: Sync Linux docstrings
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
cd ruff
|
||||
./scripts/codemod_docstrings.sh
|
||||
@@ -138,8 +124,7 @@ jobs:
|
||||
git config --global user.email '<>'
|
||||
- name: Sync Windows docstrings
|
||||
id: docstrings
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
shell: bash
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit the changes
|
||||
if: ${{ steps.docstrings.outcome == 'success' }}
|
||||
@@ -176,63 +161,26 @@ jobs:
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync macOS docstrings
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit and push the changes
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
./scripts/codemod_docstrings.sh
|
||||
git commit -am "Sync macOS docstrings" --allow-empty
|
||||
- name: Format the changes
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
|
||||
# Here we just reformat the codemodded stubs so that they are
|
||||
# consistent with the other typeshed stubs around them.
|
||||
# Typeshed formats code using black in their CI, so we just invoke
|
||||
# black on the stubs the same way that typeshed does.
|
||||
uvx black "${VENDORED_TYPESHED}/stdlib" --config "${VENDORED_TYPESHED}/pyproject.toml" || true
|
||||
git commit -am "Format codemodded docstrings" --allow-empty
|
||||
- name: Remove typeshed pyproject.toml file
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
|
||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
if: ${{ success() }}
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
if: ${{ success() }}
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Update snapshots
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
|
||||
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
|
||||
#
|
||||
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
|
||||
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
|
||||
# We don't care about other tests failing here, we just want snapshots updated where possible,
|
||||
# so we use `|| true` here to ignore the exit code.
|
||||
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
|
||||
- name: Commit snapshot changes
|
||||
if: ${{ success() }}
|
||||
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
|
||||
- name: Push changes upstream and create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
|
||||
git push
|
||||
- name: Create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
gh pr list --repo "${GITHUB_REPOSITORY}" --head "${UPSTREAM_BRANCH}" --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
|
||||
|
||||
|
||||
9
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
9
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -22,7 +22,7 @@ env:
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
if: contains(github.event.label.name, 'ecosystem-analyzer')
|
||||
steps:
|
||||
@@ -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
|
||||
@@ -67,12 +64,12 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@279f8a15b0e7f77213bf9096dbc2335a19ef89c5"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
diff \
|
||||
--profile=profiling \
|
||||
--profile=release \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
|
||||
9
.github/workflows/ty-ecosystem-report.yaml
vendored
9
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -19,7 +19,7 @@ env:
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
name: Create ecosystem report
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -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
|
||||
@@ -52,13 +49,13 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@279f8a15b0e7f77213bf9096dbc2335a19ef89c5"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--profile=profiling \
|
||||
--profile=release \
|
||||
--projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
|
||||
--output ecosystem-diagnostics.json
|
||||
|
||||
|
||||
2
.github/workflows/typing_conformance.yaml
vendored
2
.github/workflows/typing_conformance.yaml
vendored
@@ -29,7 +29,7 @@ env:
|
||||
jobs:
|
||||
typing_conformance:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
15
.github/zizmor.yml
vendored
15
.github/zizmor.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
161
CHANGELOG.md
161
CHANGELOG.md
@@ -1,166 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.3
|
||||
|
||||
Released on 2025-10-30.
|
||||
|
||||
### Preview features
|
||||
|
||||
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
|
||||
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
|
||||
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
|
||||
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
|
||||
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
|
||||
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
|
||||
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
|
||||
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
|
||||
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
|
||||
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
|
||||
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
|
||||
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
|
||||
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
|
||||
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
|
||||
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
|
||||
|
||||
### Server
|
||||
|
||||
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@jvacek](https://github.com/jvacek)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@augustelalande](https://github.com/augustelalande)
|
||||
- [@prakhar1144](https://github.com/prakhar1144)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@fatelei](https://github.com/fatelei)
|
||||
- [@ShaharNaveh](https://github.com/ShaharNaveh)
|
||||
- [@Lee-W](https://github.com/Lee-W)
|
||||
|
||||
## 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.
|
||||
|
||||
### Preview features
|
||||
|
||||
- [formatter] Remove parentheses around multiple exception types on Python 3.14+ ([#20768](https://github.com/astral-sh/ruff/pull/20768))
|
||||
- \[`flake8-bugbear`\] Omit annotation in preview fix for `B006` ([#20877](https://github.com/astral-sh/ruff/pull/20877))
|
||||
- \[`flake8-logging-format`\] Avoid dropping implicitly concatenated pieces in the `G004` fix ([#20793](https://github.com/astral-sh/ruff/pull/20793))
|
||||
- \[`pydoclint`\] Implement `docstring-extraneous-parameter` (`DOC102`) ([#20376](https://github.com/astral-sh/ruff/pull/20376))
|
||||
- \[`pyupgrade`\] Extend `UP019` to detect `typing_extensions.Text` (`UP019`) ([#20825](https://github.com/astral-sh/ruff/pull/20825))
|
||||
- \[`pyupgrade`\] Fix false negative for `TypeVar` with default argument in `non-pep695-generic-class` (`UP046`) ([#20660](https://github.com/astral-sh/ruff/pull/20660))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix false negatives in `Truthiness::from_expr` for lambdas, generators, and f-strings ([#20704](https://github.com/astral-sh/ruff/pull/20704))
|
||||
- Fix syntax error false positives for escapes and quotes in f-strings ([#20867](https://github.com/astral-sh/ruff/pull/20867))
|
||||
- Fix syntax error false positives on parenthesized context managers ([#20846](https://github.com/astral-sh/ruff/pull/20846))
|
||||
- \[`fastapi`\] Fix false positives for path parameters that FastAPI doesn't recognize (`FAST003`) ([#20687](https://github.com/astral-sh/ruff/pull/20687))
|
||||
- \[`flake8-pyi`\] Fix operator precedence by adding parentheses when needed (`PYI061`) ([#20508](https://github.com/astral-sh/ruff/pull/20508))
|
||||
- \[`ruff`\] Suppress diagnostic for f-string interpolations with debug text (`RUF010`) ([#20525](https://github.com/astral-sh/ruff/pull/20525))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`airflow`\] Add warning to `airflow.datasets.DatasetEvent` usage (`AIR301`) ([#20551](https://github.com/astral-sh/ruff/pull/20551))
|
||||
- \[`flake8-bugbear`\] Mark `B905` and `B912` fixes as unsafe ([#20695](https://github.com/astral-sh/ruff/pull/20695))
|
||||
- Use `DiagnosticTag` for more rules - changes display in editors ([#20758](https://github.com/astral-sh/ruff/pull/20758),[#20734](https://github.com/astral-sh/ruff/pull/20734))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update Python compatibility from 3.13 to 3.14 in README.md ([#20852](https://github.com/astral-sh/ruff/pull/20852))
|
||||
- Update `lint.flake8-type-checking.quoted-annotations` docs ([#20765](https://github.com/astral-sh/ruff/pull/20765))
|
||||
- Update setup instructions for Zed 0.208.0+ ([#20902](https://github.com/astral-sh/ruff/pull/20902))
|
||||
- \[`flake8-datetimez`\] Clarify docs for several rules ([#20778](https://github.com/astral-sh/ruff/pull/20778))
|
||||
- Fix typo in `RUF015` description ([#20873](https://github.com/astral-sh/ruff/pull/20873))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Reduce binary size ([#20863](https://github.com/astral-sh/ruff/pull/20863))
|
||||
- Improved error recovery for unclosed strings (including f- and t-strings) ([#20848](https://github.com/astral-sh/ruff/pull/20848))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@Paillat-dev](https://github.com/Paillat-dev)
|
||||
- [@terror](https://github.com/terror)
|
||||
- [@pieterh-oai](https://github.com/pieterh-oai)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@ageorgou](https://github.com/ageorgou)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@mgaitan](https://github.com/mgaitan)
|
||||
- [@augustelalande](https://github.com/augustelalande)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@Lee-W](https://github.com/Lee-W)
|
||||
- [@injust](https://github.com/injust)
|
||||
- [@CarrotManMatt](https://github.com/CarrotManMatt)
|
||||
|
||||
## 0.14.0
|
||||
|
||||
Released on 2025-10-07.
|
||||
|
||||
163
Cargo.lock
generated
163
Cargo.lock
generated
@@ -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.3"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3092,7 +3072,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.3"
|
||||
version = "0.14.0"
|
||||
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.3"
|
||||
version = "0.14.0"
|
||||
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]]
|
||||
@@ -3562,8 +3540,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3586,13 +3564,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=cdd0b85516a52c18b8a6d17a2279a96ed6c3e198#cdd0b85516a52c18b8a6d17a2279a96ed6c3e198"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
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]]
|
||||
|
||||
34
Cargo.toml
34
Cargo.toml
@@ -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 = "29ab321b45d00daa4315fa2a06f7207759a8c87e", 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" }
|
||||
@@ -268,7 +268,12 @@ large_stack_arrays = "allow"
|
||||
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
# Note that we set these explicitly, and these values
|
||||
# were chosen based on a trade-off between compile times
|
||||
# and runtime performance[1].
|
||||
#
|
||||
# [1]: https://github.com/astral-sh/ruff/pull/9031
|
||||
lto = "thin"
|
||||
codegen-units = 16
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
@@ -278,8 +283,6 @@ codegen-units = 16
|
||||
codegen-units = 1
|
||||
[profile.release.package.ruff_python_ast]
|
||||
codegen-units = 1
|
||||
[profile.release.package.salsa]
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
@@ -295,30 +298,11 @@ opt-level = 3
|
||||
[profile.dev.package.ruff_python_parser]
|
||||
opt-level = 1
|
||||
|
||||
# This profile is meant to mimic the `release` profile as closely as
|
||||
# possible, but using settings that are more beneficial for iterative
|
||||
# development. That is, the `release` profile is intended for actually
|
||||
# building the release, where as `profiling` is meant for building ty/ruff
|
||||
# for running benchmarks.
|
||||
#
|
||||
# The main differences here are to avoid stripping debug information
|
||||
# and disabling fat lto. This does result in a mismatch between our release
|
||||
# configuration and our benchmarking configuration, which is unfortunate.
|
||||
# But compile times with `lto = fat` are completely untenable.
|
||||
#
|
||||
# This setup does risk that we are measuring something in benchmarks
|
||||
# that we aren't shipping, but in order to make those two the same, we'd
|
||||
# either need to make compile times way worse for development, or take
|
||||
# a hit to binary size and a slight hit to runtime performance in our
|
||||
# release builds.
|
||||
#
|
||||
# Use the `--profile profiling` flag to show symbols in release mode.
|
||||
# e.g. `cargo build --profile profiling`
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = "full"
|
||||
lto = false
|
||||
debug = 1
|
||||
|
||||
# The profile that 'cargo dist' will build with.
|
||||
[profile.dist]
|
||||
|
||||
11
README.md
11
README.md
@@ -28,7 +28,7 @@ An extremely fast Python linter and code formatter, written in Rust.
|
||||
- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black)
|
||||
- 🐍 Installable via `pip`
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 🤝 Python 3.14 compatibility
|
||||
- 🤝 Python 3.13 compatibility
|
||||
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruffs-linter-compare-to-flake8), isort, and [Black](https://docs.astral.sh/ruff/faq/#how-does-ruffs-formatter-compare-to-black)
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
@@ -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.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.3/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.0/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.3
|
||||
rev: v0.14.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.3"
|
||||
version = "0.14.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use log::{debug, warn};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
|
||||
use ruff_linter::message::create_syntax_error_diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
@@ -102,7 +103,11 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Diagnostic::invalid_syntax(dummy, err, TextRange::default())],
|
||||
vec![create_syntax_error_diagnostic(
|
||||
dummy,
|
||||
err,
|
||||
TextRange::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -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
|
||||
---
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -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
|
||||
---
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/tests/cli/format.rs
|
||||
source: crates/ruff/tests/format.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -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 -----
|
||||
@@ -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 -----
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -7,8 +7,7 @@ use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, DummyFileResolver, FileResolver, Input,
|
||||
ceil_char_boundary,
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::{Db, files::File};
|
||||
@@ -84,14 +83,17 @@ impl Diagnostic {
|
||||
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
|
||||
/// the other way around. And since we want to do this conversion in a couple
|
||||
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
|
||||
///
|
||||
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
|
||||
/// message.
|
||||
pub fn invalid_syntax(
|
||||
span: impl Into<Span>,
|
||||
message: impl IntoDiagnosticMessage,
|
||||
range: impl Ranged,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = span.into().with_range(range.range());
|
||||
diag.annotate(Annotation::primary(span));
|
||||
diag.annotate(Annotation::primary(span).message(message));
|
||||
diag
|
||||
}
|
||||
|
||||
|
||||
@@ -1170,31 +1170,6 @@ pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
/// A stub implementation of [`FileResolver`] intended for testing.
|
||||
pub struct DummyFileResolver;
|
||||
|
||||
impl FileResolver for DummyFileResolver {
|
||||
fn path(&self, _file: File) -> &str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn input(&self, _file: File) -> Input {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn notebook_index(&self, _file: &UnifiedFile) -> Option<NotebookIndex> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_notebook(&self, _file: &UnifiedFile) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn current_directory(&self) -> &Path {
|
||||
Path::new(".")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -193,17 +193,6 @@ impl Files {
|
||||
roots.at(&absolute)
|
||||
}
|
||||
|
||||
/// The same as [`Self::root`] but panics if no root is found.
|
||||
#[track_caller]
|
||||
pub fn expect_root(&self, db: &dyn Db, path: &SystemPath) -> FileRoot {
|
||||
if let Some(root) = self.root(db, path) {
|
||||
return root;
|
||||
}
|
||||
|
||||
let roots = self.inner.roots.read().unwrap();
|
||||
panic!("No root found for path '{path}'. Known roots: {roots:#?}");
|
||||
}
|
||||
|
||||
/// Adds a new root for `path` and returns the root.
|
||||
///
|
||||
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
|
||||
|
||||
@@ -81,8 +81,6 @@ impl FileRoots {
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("Adding new file root '{path}' of kind {kind:?}");
|
||||
|
||||
// normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which matchit uses for routing parameters
|
||||
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -93,39 +93,14 @@ fn generate_markdown() -> String {
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let status_text = match lint.status() {
|
||||
ty_python_semantic::lint::LintStatus::Stable { since } => {
|
||||
format!(
|
||||
r#"Added in <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Preview { since } => {
|
||||
format!(
|
||||
r#"Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Deprecated { since, .. } => {
|
||||
format!(
|
||||
r#"Deprecated (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
ty_python_semantic::lint::LintStatus::Removed { since, .. } => {
|
||||
format!(
|
||||
r#"Removed (since <a href="https://github.com/astral-sh/ty/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
Default level: [`{level}`](../rules.md#rule-levels "This lint has a default level of '{level}'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
|
||||
</small>
|
||||
|
||||
|
||||
{documentation}
|
||||
"#,
|
||||
level = lint.default_level(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -98,10 +98,6 @@ impl Db for ModuleDb {
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.3"
|
||||
version = "0.14.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -11,7 +11,7 @@ from airflow import (
|
||||
)
|
||||
from airflow.api_connexion.security import requires_access
|
||||
from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent, DatasetEvent
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.operators.postgres_operator import Mapping
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.secrets.cache import SecretCache
|
||||
@@ -48,7 +48,6 @@ AWSAthenaHook()
|
||||
|
||||
# airflow.datasets
|
||||
DatasetAliasEvent()
|
||||
DatasetEvent()
|
||||
|
||||
|
||||
# airflow.operators.subdag.*
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}"
|
||||
@@ -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():
|
||||
...
|
||||
|
||||
@@ -33,10 +33,3 @@ class ShellConfig:
|
||||
|
||||
def run(self, username):
|
||||
Popen("true", shell={**self.shell_defaults, **self.fetch_shell_config(username)})
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
Popen("true", shell=(i for i in ()))
|
||||
Popen("true", shell=lambda: 0)
|
||||
Popen("true", shell=f"{b''}")
|
||||
x = 1
|
||||
Popen("true", shell=f"{x=}")
|
||||
|
||||
@@ -6,19 +6,3 @@ foo(shell=True)
|
||||
|
||||
foo(shell={**{}})
|
||||
foo(shell={**{**{}}})
|
||||
|
||||
# Truthy non-bool values for `shell`
|
||||
foo(shell=(i for i in ()))
|
||||
foo(shell=lambda: 0)
|
||||
|
||||
# f-strings guaranteed non-empty
|
||||
foo(shell=f"{b''}")
|
||||
x = 1
|
||||
foo(shell=f"{x=}")
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
foo(shell=(i for i in ()))
|
||||
foo(shell=lambda: 0)
|
||||
foo(shell=f"{b''}")
|
||||
x = 1
|
||||
foo(shell=f"{x=}")
|
||||
|
||||
@@ -9,10 +9,3 @@ os.system("tar cf foo.tar bar/*")
|
||||
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{}})
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{**{}}})
|
||||
|
||||
# Additional truthiness cases for generator, lambda, and f-strings
|
||||
subprocess.Popen("chmod +w foo*", shell=(i for i in ()))
|
||||
subprocess.Popen("chmod +w foo*", shell=lambda: 0)
|
||||
subprocess.Popen("chmod +w foo*", shell=f"{b''}")
|
||||
x = 1
|
||||
subprocess.Popen("chmod +w foo*", shell=f"{x=}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# The lexer emits a string token if it's unterminated
|
||||
# The lexer doesn't emit a string token if it's unterminated
|
||||
"a" "b
|
||||
"a" "b" "c
|
||||
"a" """b
|
||||
c""" "d
|
||||
|
||||
# This is also true for
|
||||
# For f-strings, the `FStringRanges` won't contain the range for
|
||||
# unterminated f-strings.
|
||||
f"a" f"b
|
||||
f"a" f"b" f"c
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/21023
|
||||
'' '
|
||||
"" ""
|
||||
'' '' '
|
||||
"" "" "
|
||||
f"" f"
|
||||
f"" f"" f"
|
||||
@@ -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:
|
||||
...
|
||||
|
||||
@@ -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:
|
||||
...
|
||||
|
||||
@@ -78,11 +78,3 @@ b: None | Literal[None] | None
|
||||
c: (None | Literal[None]) | None
|
||||
d: None | (Literal[None] | None)
|
||||
e: None | ((None | Literal[None]) | None) | None
|
||||
|
||||
# Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
@@ -197,10 +197,3 @@ for x in {**a, **b} or [None]:
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7127
|
||||
def f(a: "'b' or 'c'"): ...
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20703
|
||||
print(f"{b''}" or "bar") # SIM222
|
||||
x = 1
|
||||
print(f"{x=}" or "bar") # SIM222
|
||||
(lambda: 1) or True # SIM222
|
||||
(i for i in range(1)) or "bar" # SIM222
|
||||
|
||||
@@ -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")):...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
a (int): The first number to add.
|
||||
b (int): The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
# DOC102
|
||||
def multiply_list_elements(lst):
|
||||
"""
|
||||
Multiplies each element in a list by a given multiplier.
|
||||
|
||||
Args:
|
||||
lst (list of int): A list of integers.
|
||||
multiplier (int): The multiplier for each element in the list.
|
||||
|
||||
Returns:
|
||||
list of int: A new list with each element multiplied.
|
||||
"""
|
||||
return [x * multiplier for x in lst]
|
||||
|
||||
|
||||
# DOC102
|
||||
def find_max_value():
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
Args:
|
||||
numbers (list of int): A list of integers to search through.
|
||||
|
||||
Returns:
|
||||
int: The maximum value found in the list.
|
||||
"""
|
||||
return max(numbers)
|
||||
|
||||
|
||||
# DOC102
|
||||
def create_user_profile(location="here"):
|
||||
"""
|
||||
Creates a user profile with basic information.
|
||||
|
||||
Args:
|
||||
name (str): The name of the user.
|
||||
age (int): The age of the user.
|
||||
email (str): The user's email address.
|
||||
location (str): The location of the user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the user's profile.
|
||||
"""
|
||||
return {
|
||||
'name': name,
|
||||
'age': age,
|
||||
'email': email,
|
||||
'location': location
|
||||
}
|
||||
|
||||
|
||||
# DOC102
|
||||
def calculate_total_price(item_prices, discount):
|
||||
"""
|
||||
Calculates the total price after applying tax and a discount.
|
||||
|
||||
Args:
|
||||
item_prices (list of float): A list of prices for each item.
|
||||
tax_rate (float): The tax rate to apply.
|
||||
discount (float): The discount to subtract from the total.
|
||||
|
||||
Returns:
|
||||
float: The final total price after tax and discount.
|
||||
"""
|
||||
total = sum(item_prices)
|
||||
total_with_tax = total + (total * tax_rate)
|
||||
final_total = total_with_tax - discount
|
||||
return final_total
|
||||
|
||||
|
||||
# DOC102
|
||||
def send_email(subject, body, bcc_address=None):
|
||||
"""
|
||||
Sends an email to the specified recipients.
|
||||
|
||||
Args:
|
||||
subject (str): The subject of the email.
|
||||
body (str): The content of the email.
|
||||
to_address (str): The recipient's email address.
|
||||
cc_address (str, optional): The email address for CC. Defaults to None.
|
||||
bcc_address (str, optional): The email address for BCC. Defaults to None.
|
||||
|
||||
Returns:
|
||||
bool: True if the email was sent successfully, False otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def concatenate_strings(*args):
|
||||
"""
|
||||
Concatenates multiple strings with a specified separator.
|
||||
|
||||
Args:
|
||||
separator (str): The separator to use between strings.
|
||||
*args (str): Variable length argument list of strings to concatenate.
|
||||
|
||||
Returns:
|
||||
str: A single concatenated string.
|
||||
"""
|
||||
return separator.join(args)
|
||||
|
||||
|
||||
# DOC102
|
||||
def process_order(order_id):
|
||||
"""
|
||||
Processes an order with a list of items and optional order details.
|
||||
|
||||
Args:
|
||||
order_id (int): The unique identifier for the order.
|
||||
*items (str): Variable length argument list of items in the order.
|
||||
**details (dict): Additional details such as shipping method and address.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the order summary.
|
||||
"""
|
||||
return {
|
||||
'order_id': order_id,
|
||||
'items': items,
|
||||
'details': details
|
||||
}
|
||||
|
||||
|
||||
class Calculator:
|
||||
"""
|
||||
A simple calculator class that can perform basic arithmetic operations.
|
||||
"""
|
||||
|
||||
# DOC102
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the calculator with an initial value.
|
||||
|
||||
Args:
|
||||
value (int, optional): The initial value of the calculator. Defaults to 0.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
# DOC102
|
||||
def add(self, number2):
|
||||
"""
|
||||
Adds a number to the current value.
|
||||
|
||||
Args:
|
||||
number (int or float): The number to add to the current value.
|
||||
|
||||
Returns:
|
||||
int or float: The updated value after addition.
|
||||
"""
|
||||
self.value += number + number2
|
||||
return self.value
|
||||
|
||||
# DOC102
|
||||
@classmethod
|
||||
def from_string(cls):
|
||||
"""
|
||||
Creates a Calculator instance from a string representation of a number.
|
||||
|
||||
Args:
|
||||
value_str (str): The string representing the initial value.
|
||||
|
||||
Returns:
|
||||
Calculator: A new instance of Calculator initialized with the value from the string.
|
||||
"""
|
||||
value = float(value_str)
|
||||
return cls(value)
|
||||
|
||||
# DOC102
|
||||
@staticmethod
|
||||
def is_valid_number():
|
||||
"""
|
||||
Checks if a given number is valid (int or float).
|
||||
|
||||
Args:
|
||||
number (any): The value to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the number is valid, False otherwise.
|
||||
"""
|
||||
return isinstance(number, (int, float))
|
||||
|
||||
# OK
|
||||
def foo(param1, param2, *args, **kwargs):
|
||||
"""Foo.
|
||||
|
||||
Args:
|
||||
param1 (int): The first parameter.
|
||||
param2 (:obj:`str`, optional): The second parameter. Defaults to None.
|
||||
Second line of description: should be indented.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
def on_server_unloaded(self, server_context: ServerContext) -> None:
|
||||
''' Execute ``on_server_unloaded`` from ``server_lifecycle.py`` (if
|
||||
it is defined) when the server cleanly exits. (Before stopping the
|
||||
server's ``IOLoop``.)
|
||||
|
||||
Args:
|
||||
server_context (ServerContext) :
|
||||
|
||||
.. warning::
|
||||
In practice this code may not run, since servers are often killed
|
||||
by a signal.
|
||||
|
||||
|
||||
'''
|
||||
return self._lifecycle_handler.on_server_unloaded(server_context)
|
||||
|
||||
# OK
|
||||
def function_with_kwargs(param1, param2, **kwargs):
|
||||
"""Function with **kwargs parameter.
|
||||
|
||||
Args:
|
||||
param1 (int): The first parameter.
|
||||
param2 (str): The second parameter.
|
||||
extra_param (str): An extra parameter that may be passed via **kwargs.
|
||||
another_extra (int): Another extra parameter.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
b: The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return
|
||||
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
a: The first number to add.
|
||||
b: The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
@@ -1,391 +0,0 @@
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : int
|
||||
The first number to add.
|
||||
b : int
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
# DOC102
|
||||
def multiply_list_elements(lst):
|
||||
"""
|
||||
Multiplies each element in a list by a given multiplier.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lst : list of int
|
||||
A list of integers.
|
||||
multiplier : int
|
||||
The multiplier for each element in the list.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
A new list with each element multiplied.
|
||||
"""
|
||||
return [x * multiplier for x in lst]
|
||||
|
||||
|
||||
# DOC102
|
||||
def find_max_value():
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
numbers : list of int
|
||||
A list of integers to search through.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The maximum value found in the list.
|
||||
"""
|
||||
return max(numbers)
|
||||
|
||||
|
||||
# DOC102
|
||||
def create_user_profile(location="here"):
|
||||
"""
|
||||
Creates a user profile with basic information.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the user.
|
||||
age : int
|
||||
The age of the user.
|
||||
email : str
|
||||
The user's email address.
|
||||
location : str, optional
|
||||
The location of the user, by default "here".
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary containing the user's profile.
|
||||
"""
|
||||
return {
|
||||
'name': name,
|
||||
'age': age,
|
||||
'email': email,
|
||||
'location': location
|
||||
}
|
||||
|
||||
|
||||
# DOC102
|
||||
def calculate_total_price(item_prices, discount):
|
||||
"""
|
||||
Calculates the total price after applying tax and a discount.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item_prices : list of float
|
||||
A list of prices for each item.
|
||||
tax_rate : float
|
||||
The tax rate to apply.
|
||||
discount : float
|
||||
The discount to subtract from the total.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The final total price after tax and discount.
|
||||
"""
|
||||
total = sum(item_prices)
|
||||
total_with_tax = total + (total * tax_rate)
|
||||
final_total = total_with_tax - discount
|
||||
return final_total
|
||||
|
||||
|
||||
# DOC102
|
||||
def send_email(subject, body, bcc_address=None):
|
||||
"""
|
||||
Sends an email to the specified recipients.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
The subject of the email.
|
||||
body : str
|
||||
The content of the email.
|
||||
to_address : str
|
||||
The recipient's email address.
|
||||
cc_address : str, optional
|
||||
The email address for CC, by default None.
|
||||
bcc_address : str, optional
|
||||
The email address for BCC, by default None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the email was sent successfully, False otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def concatenate_strings(*args):
|
||||
"""
|
||||
Concatenates multiple strings with a specified separator.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
separator : str
|
||||
The separator to use between strings.
|
||||
*args : str
|
||||
Variable length argument list of strings to concatenate.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A single concatenated string.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def process_order(order_id):
|
||||
"""
|
||||
Processes an order with a list of items and optional order details.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order_id : int
|
||||
The unique identifier for the order.
|
||||
*items : str
|
||||
Variable length argument list of items in the order.
|
||||
**details : dict
|
||||
Additional details such as shipping method and address.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary containing the order summary.
|
||||
"""
|
||||
return {
|
||||
'order_id': order_id,
|
||||
'items': items,
|
||||
'details': details
|
||||
}
|
||||
|
||||
|
||||
class Calculator:
|
||||
"""
|
||||
A simple calculator class that can perform basic arithmetic operations.
|
||||
"""
|
||||
|
||||
# DOC102
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the calculator with an initial value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : int, optional
|
||||
The initial value of the calculator, by default 0.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
# DOC102
|
||||
def add(self, number2):
|
||||
"""
|
||||
Adds two numbers to the current value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
number : int or float
|
||||
The first number to add.
|
||||
number2 : int or float
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int or float
|
||||
The updated value after addition.
|
||||
"""
|
||||
self.value += number + number2
|
||||
return self.value
|
||||
|
||||
# DOC102
|
||||
@classmethod
|
||||
def from_string(cls):
|
||||
"""
|
||||
Creates a Calculator instance from a string representation of a number.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value_str : str
|
||||
The string representing the initial value.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Calculator
|
||||
A new instance of Calculator initialized with the value from the string.
|
||||
"""
|
||||
value = float(value_str)
|
||||
return cls(value)
|
||||
|
||||
# DOC102
|
||||
@staticmethod
|
||||
def is_valid_number():
|
||||
"""
|
||||
Checks if a given number is valid (int or float).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
number : any
|
||||
The value to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the number is valid, False otherwise.
|
||||
"""
|
||||
return isinstance(number, (int, float))
|
||||
|
||||
# OK
|
||||
def function_with_kwargs(param1, param2, **kwargs):
|
||||
"""Function with **kwargs parameter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
param1 : int
|
||||
The first parameter.
|
||||
param2 : str
|
||||
The second parameter.
|
||||
extra_param : str
|
||||
An extra parameter that may be passed via **kwargs.
|
||||
another_extra : int
|
||||
Another extra parameter.
|
||||
"""
|
||||
return True
|
||||
|
||||
# OK
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
b
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a
|
||||
The first number to add.
|
||||
b
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
class Foo:
|
||||
# OK
|
||||
def send_help(self, *args: Any) -> Any:
|
||||
"""|coro|
|
||||
|
||||
Shows the help command for the specified entity if given.
|
||||
The entity can be a command or a cog.
|
||||
|
||||
If no entity is given, then it'll show help for the
|
||||
entire bot.
|
||||
|
||||
If the entity is a string, then it looks up whether it's a
|
||||
:class:`Cog` or a :class:`Command`.
|
||||
|
||||
.. note::
|
||||
|
||||
Due to the way this function works, instead of returning
|
||||
something similar to :meth:`~.commands.HelpCommand.command_not_found`
|
||||
this returns :class:`None` on bad input or no help command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
entity: Optional[Union[:class:`Command`, :class:`Cog`, :class:`str`]]
|
||||
The entity to show help for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The result of the help command, if any.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
@classmethod
|
||||
async def convert(cls, ctx: Context, argument: str) -> Self:
|
||||
"""|coro|
|
||||
|
||||
The method that actually converters an argument to the flag mapping.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cls: Type[:class:`FlagConverter`]
|
||||
The flag converter class.
|
||||
ctx: :class:`Context`
|
||||
The invocation context.
|
||||
argument: :class:`str`
|
||||
The argument to convert from.
|
||||
|
||||
Raises
|
||||
------
|
||||
FlagError
|
||||
A flag related parsing error.
|
||||
CommandError
|
||||
A command related error.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`FlagConverter`
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -18,20 +18,3 @@ def print_third_word(word: Hello.Text) -> None:
|
||||
|
||||
def print_fourth_word(word: Goodbye) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
import typing_extensions
|
||||
import typing_extensions as TypingExt
|
||||
from typing_extensions import Text as TextAlias
|
||||
|
||||
|
||||
def print_fifth_word(word: typing_extensions.Text) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
def print_sixth_word(word: TypingExt.Text) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
def print_seventh_word(word: TextAlias) -> None:
|
||||
print(word)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -43,7 +43,7 @@ class Foo:
|
||||
T = typing.TypeVar(*args)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# `default` was added in Python 3.13
|
||||
# `default` should be skipped for now, added in Python 3.13
|
||||
T = typing.TypeVar("T", default=Any)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
@@ -90,9 +90,9 @@ PositiveList = TypeAliasType(
|
||||
"PositiveList2", list[Annotated[T, Gt(0)]], type_params=(T,)
|
||||
)
|
||||
|
||||
# `default` was added in Python 3.13
|
||||
# `default` should be skipped for now, added in Python 3.13
|
||||
T = typing.TypeVar("T", default=Any)
|
||||
AnyList = TypeAliasType("AnyList", list[T], type_params=(T,))
|
||||
AnyList = TypeAliasType("AnyList", list[T], typep_params=(T,))
|
||||
|
||||
# unsafe fix if comments within the fix
|
||||
T = TypeVar("T")
|
||||
@@ -128,7 +128,3 @@ T: TypeAlias = ( # comment0
|
||||
str # comment6
|
||||
# comment7
|
||||
) # comment8
|
||||
|
||||
# Test case for TypeVar with default - should be converted when preview mode is enabled
|
||||
T_default = TypeVar("T_default", default=int)
|
||||
DefaultList: TypeAlias = list[T_default]
|
||||
|
||||
@@ -122,7 +122,7 @@ class MixedGenerics[U]:
|
||||
return (u, t)
|
||||
|
||||
|
||||
# default requires 3.13
|
||||
# TODO(brent) default requires 3.13
|
||||
V = TypeVar("V", default=Any, bound=str)
|
||||
|
||||
|
||||
@@ -130,14 +130,6 @@ class DefaultTypeVar(Generic[V]): # -> [V: str = Any]
|
||||
var: V
|
||||
|
||||
|
||||
# Test case for TypeVar with default but no bound
|
||||
W = TypeVar("W", default=int)
|
||||
|
||||
|
||||
class DefaultOnlyTypeVar(Generic[W]): # -> [W = int]
|
||||
var: W
|
||||
|
||||
|
||||
# nested classes and functions are skipped
|
||||
class Outer:
|
||||
class Inner(Generic[T]):
|
||||
|
||||
@@ -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
|
||||
@@ -44,7 +44,9 @@ def any_str_param(s: AnyStr) -> AnyStr:
|
||||
return s
|
||||
|
||||
|
||||
# default requires 3.13
|
||||
# these cases are not handled
|
||||
|
||||
# TODO(brent) default requires 3.13
|
||||
V = TypeVar("V", default=Any, bound=str)
|
||||
|
||||
|
||||
@@ -52,8 +54,6 @@ def default_var(v: V) -> V:
|
||||
return v
|
||||
|
||||
|
||||
# these cases are not handled
|
||||
|
||||
def outer():
|
||||
def inner(t: T) -> T:
|
||||
return t
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
match x:
|
||||
case Point(x=1, x=2):
|
||||
pass
|
||||
@@ -1,3 +0,0 @@
|
||||
match x:
|
||||
case {'key': 1, 'key': 2}:
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
class C[T, T]: pass
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,8 +0,0 @@
|
||||
def func():
|
||||
return *x
|
||||
|
||||
for *x in range(10):
|
||||
pass
|
||||
|
||||
def func():
|
||||
yield *x
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user