Compare commits

..

2 Commits

Author SHA1 Message Date
Ibraheem Ahmed
541b2096b6 wip 2025-06-19 21:58:29 -04:00
Ibraheem Ahmed
d1f16703ab clear source texts 2025-06-18 23:10:17 -04:00
738 changed files with 12539 additions and 21327 deletions

View File

@@ -49,7 +49,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build sdist"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
command: sdist
args: --out dist
@@ -79,7 +79,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: x86_64
args: --release --locked --out dist
@@ -121,7 +121,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: aarch64
args: --release --locked --out dist
@@ -177,7 +177,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
@@ -230,7 +230,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -304,7 +304,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: ${{ matrix.platform.target }}
manylinux: auto
@@ -370,7 +370,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -435,7 +435,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2

View File

@@ -38,7 +38,7 @@ jobs:
submodules: recursive
persist-credentials: false
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
@@ -119,7 +119,7 @@ jobs:
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Extract metadata (tags, labels) for Docker
id: meta
@@ -167,7 +167,7 @@ jobs:
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
steps:
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
@@ -262,7 +262,7 @@ jobs:
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Extract metadata (tags, labels) for Docker
id: meta

View File

@@ -238,13 +238,13 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-insta
- name: ty mdtests (GitHub annotations)
@@ -296,13 +296,13 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-insta
- name: "Run tests"
@@ -325,7 +325,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-nextest
- name: "Run tests"
@@ -381,7 +381,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- name: "Build"
run: cargo build --release --locked
@@ -406,13 +406,13 @@ jobs:
MSRV: ${{ steps.msrv.outputs.value }}
run: rustup default "${MSRV}"
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-insta
- name: "Run tests"
@@ -438,7 +438,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -460,7 +460,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -661,7 +661,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -691,7 +691,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
- uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -712,7 +712,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
with:
args: --out dist
- name: "Test wheel"
@@ -731,7 +731,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
@@ -774,7 +774,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -906,13 +906,13 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-codspeed
@@ -939,13 +939,13 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
with:
tool: cargo-codspeed

View File

@@ -34,11 +34,11 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- name: Build ruff
# A debug build means the script runs slower once it gets started,

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
@@ -70,7 +70,7 @@ jobs:
echo "Project selector: $PRIMER_SELECTOR"
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
uvx \
--from="git+https://github.com/hauntsaninja/mypy_primer@e5f55447969d33ae3c7ccdb183e2a37101867270" \
--from="git+https://github.com/hauntsaninja/mypy_primer@01a7ca325f674433c58e02416a867178d1571128" \
mypy_primer \
--repo ruff \
--type-checker ty \

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -1,94 +0,0 @@
name: ty ecosystem-analyzer
permissions: {}
on:
pull_request:
types: [labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }}
jobs:
ty-ecosystem-analyzer:
name: Compute diagnostic diff
runs-on: depot-ubuntu-22.04-32
timeout-minutes: 20
if: contains(github.event.label.name, 'ecosystem-analyzer')
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Compute diagnostic diff
shell: bash
run: |
cd ruff
echo "Enabling configuration overloads (see .github/mypy-primer-ty.toml)"
mkdir -p ~/.config/ty
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
echo "new commit"
git checkout -b new_commit "$GITHUB_SHA"
git rev-list --format=%s --max-count=1 new_commit
cp crates/ty_python_semantic/resources/primer/good.txt projects_new.txt
echo "old commit (merge base)"
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
git rev-list --format=%s --max-count=1 old_commit
cp crates/ty_python_semantic/resources/primer/good.txt projects_old.txt
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
ecosystem-analyzer \
--repository ruff \
analyze \
--projects ruff/projects_old.txt \
--commit old_commit \
--output diagnostics_old.json
ecosystem-analyzer \
--repository ruff \
analyze \
--projects ruff/projects_new.txt \
--commit new_commit \
--output diagnostics_new.json
ecosystem-analyzer \
generate-diff \
diagnostics_old.json \
diagnostics_new.json \
--old-name "main (merge base)" \
--new-name "$REF_NAME" \
--output-html diff.html
- name: Upload HTML diff report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: diff.html
path: diff.html

49
Cargo.lock generated
View File

@@ -930,12 +930,35 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -1640,9 +1663,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.173"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
[[package]]
name = "libcst"
@@ -1671,9 +1694,9 @@ dependencies = [
[[package]]
name = "libmimalloc-sys"
version = "0.1.43"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d"
checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4"
dependencies = [
"cc",
"libc",
@@ -1802,9 +1825,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.47"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40"
checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af"
dependencies = [
"libmimalloc-sys",
]
@@ -3023,6 +3046,16 @@ dependencies = [
"walkdir",
]
[[package]]
name = "ruff_python_resolver"
version = "0.0.0"
dependencies = [
"env_logger",
"insta",
"log",
"tempfile",
]
[[package]]
name = "ruff_python_semantic"
version = "0.0.0"
@@ -3570,9 +3603,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -75,6 +75,7 @@ dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.11.0" }
etcetera = { version = "0.10.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }

View File

@@ -19,7 +19,7 @@ use tempfile::NamedTempFile;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_diagnostics::Fix;
use ruff_linter::message::OldDiagnostic;
use ruff_linter::message::Message;
use ruff_linter::package::PackageRoot;
use ruff_linter::{VERSION, warn_user};
use ruff_macros::CacheKey;
@@ -341,16 +341,16 @@ impl FileCache {
/// Convert the file cache into `Diagnostics`, using `path` as file name.
pub(crate) fn to_diagnostics(&self, path: &Path) -> Option<Diagnostics> {
self.data.lint.as_ref().map(|lint| {
let diagnostics = if lint.messages.is_empty() {
let messages = if lint.messages.is_empty() {
Vec::new()
} else {
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
lint.messages
.iter()
.map(|msg| {
OldDiagnostic::lint(
&msg.body,
msg.suggestion.as_ref(),
Message::diagnostic(
msg.body.clone(),
msg.suggestion.clone(),
msg.range,
msg.fix.clone(),
msg.parent,
@@ -366,7 +366,7 @@ impl FileCache {
} else {
FxHashMap::default()
};
Diagnostics::new(diagnostics, notebook_indexes)
Diagnostics::new(messages, notebook_indexes)
})
}
}
@@ -427,17 +427,17 @@ pub(crate) struct LintCacheData {
}
impl LintCacheData {
pub(crate) fn from_diagnostics(
diagnostics: &[OldDiagnostic],
pub(crate) fn from_messages(
messages: &[Message],
notebook_index: Option<NotebookIndex>,
) -> Self {
let source = if let Some(msg) = diagnostics.first() {
let source = if let Some(msg) = messages.first() {
msg.source_file().source_text().to_owned()
} else {
String::new() // No messages, no need to keep the source!
};
let messages = diagnostics
let messages = messages
.iter()
// Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so
// this also serves to filter them out, but we shouldn't be caching files with syntax
@@ -447,7 +447,7 @@ impl LintCacheData {
// Make sure that all message use the same source file.
assert_eq!(
msg.source_file(),
diagnostics.first().unwrap().source_file(),
messages.first().unwrap().source_file(),
"message uses a different source file"
);
CacheMessage {
@@ -612,7 +612,7 @@ mod tests {
use test_case::test_case;
use ruff_cache::CACHE_DIR_NAME;
use ruff_linter::message::OldDiagnostic;
use ruff_linter::message::Message;
use ruff_linter::package::PackageRoot;
use ruff_linter::settings::flags;
use ruff_linter::settings::types::UnsafeFixes;
@@ -680,7 +680,7 @@ mod tests {
UnsafeFixes::Enabled,
)
.unwrap();
if diagnostics.inner.iter().any(OldDiagnostic::is_syntax_error) {
if diagnostics.messages.iter().any(Message::is_syntax_error) {
parse_errors.push(path.clone());
}
paths.push(path);

View File

@@ -13,6 +13,7 @@ use rustc_hash::FxHashMap;
use ruff_db::panic::catch_unwind;
use ruff_linter::OldDiagnostic;
use ruff_linter::message::Message;
use ruff_linter::package::PackageRoot;
use ruff_linter::registry::Rule;
use ruff_linter::settings::types::UnsafeFixes;
@@ -129,10 +130,9 @@ pub(crate) fn check(
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
Diagnostics::new(
vec![OldDiagnostic::new(
IOError { message },
TextRange::default(),
&dummy,
vec![Message::from_diagnostic(
OldDiagnostic::new(IOError { message }, TextRange::default(), &dummy),
None,
)],
FxHashMap::default(),
)
@@ -166,7 +166,7 @@ pub(crate) fn check(
|a, b| (a.0 + b.0, a.1 + b.1),
);
all_diagnostics.inner.sort();
all_diagnostics.messages.sort();
// Store the caches.
caches.persist()?;
@@ -283,7 +283,7 @@ mod test {
.with_show_fix_status(true)
.emit(
&mut output,
&diagnostics.inner,
&diagnostics.messages,
&EmitterContext::new(&FxHashMap::default()),
)
.unwrap();

View File

@@ -52,6 +52,6 @@ pub(crate) fn check_stdin(
noqa,
fix_mode,
)?;
diagnostics.inner.sort_unstable();
diagnostics.messages.sort_unstable();
Ok(diagnostics)
}

View File

@@ -15,6 +15,7 @@ use rustc_hash::FxHashMap;
use ruff_linter::OldDiagnostic;
use ruff_linter::codes::Rule;
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
use ruff_linter::message::Message;
use ruff_linter::package::PackageRoot;
use ruff_linter::pyproject_toml::lint_pyproject_toml;
use ruff_linter::settings::types::UnsafeFixes;
@@ -31,18 +32,18 @@ use crate::cache::{Cache, FileCacheKey, LintCacheData};
#[derive(Debug, Default, PartialEq)]
pub(crate) struct Diagnostics {
pub(crate) inner: Vec<OldDiagnostic>,
pub(crate) messages: Vec<Message>,
pub(crate) fixed: FixMap,
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
}
impl Diagnostics {
pub(crate) fn new(
diagnostics: Vec<OldDiagnostic>,
messages: Vec<Message>,
notebook_indexes: FxHashMap<String, NotebookIndex>,
) -> Self {
Self {
inner: diagnostics,
messages,
fixed: FixMap::default(),
notebook_indexes,
}
@@ -62,12 +63,15 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let source_file = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![OldDiagnostic::new(
IOError {
message: err.to_string(),
},
TextRange::default(),
&source_file,
vec![Message::from_diagnostic(
OldDiagnostic::new(
IOError {
message: err.to_string(),
},
TextRange::default(),
&source_file,
),
None,
)],
FxHashMap::default(),
)
@@ -98,11 +102,7 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let dummy = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![OldDiagnostic::syntax_error(
err,
TextRange::default(),
dummy,
)],
vec![Message::syntax_error(err, TextRange::default(), dummy)],
FxHashMap::default(),
)
}
@@ -121,7 +121,7 @@ impl Add for Diagnostics {
impl AddAssign for Diagnostics {
fn add_assign(&mut self, other: Self) {
self.inner.extend(other.inner);
self.messages.extend(other.messages);
self.fixed += other.fixed;
self.notebook_indexes.extend(other.notebook_indexes);
}
@@ -202,7 +202,7 @@ pub(crate) fn lint_path(
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.inner.is_empty() && diagnostics.fixed.is_empty()
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
}
} {
return Ok(diagnostics);
@@ -222,7 +222,7 @@ pub(crate) fn lint_path(
Some(source_type) => source_type,
None => match SourceType::from(path) {
SourceType::Toml(TomlSourceType::Pyproject) => {
let diagnostics = if settings
let messages = if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
@@ -240,7 +240,7 @@ pub(crate) fn lint_path(
vec![]
};
return Ok(Diagnostics {
inner: diagnostics,
messages,
..Diagnostics::default()
});
}
@@ -324,7 +324,7 @@ pub(crate) fn lint_path(
};
let has_error = result.has_syntax_errors();
let diagnostics = result.diagnostics;
let messages = result.messages;
if let Some((cache, relative_path, key)) = caching {
// We don't cache parsing errors.
@@ -335,14 +335,14 @@ pub(crate) fn lint_path(
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.is_empty() && fixed.is_empty()
messages.is_empty() && fixed.is_empty()
}
} {
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_diagnostics(
&diagnostics,
LintCacheData::from_messages(
&messages,
transformed.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
@@ -357,7 +357,7 @@ pub(crate) fn lint_path(
};
Ok(Diagnostics {
inner: diagnostics,
messages,
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
notebook_indexes,
})
@@ -396,7 +396,7 @@ pub(crate) fn lint_stdin(
}
return Ok(Diagnostics {
inner: lint_pyproject_toml(&source_file, &settings.linter),
messages: lint_pyproject_toml(&source_file, &settings.linter),
fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]),
notebook_indexes: FxHashMap::default(),
});
@@ -417,7 +417,7 @@ pub(crate) fn lint_stdin(
};
// Lint the inputs.
let (LinterResult { diagnostics, .. }, transformed, fixed) =
let (LinterResult { messages, .. }, transformed, fixed) =
if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
if let Ok(FixerResult {
result,
@@ -501,7 +501,7 @@ pub(crate) fn lint_stdin(
};
Ok(Diagnostics {
inner: diagnostics,
messages,
fixed: FixMap::from_iter([(
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
fixed,

View File

@@ -363,7 +363,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
Printer::clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");
let diagnostics = commands::check::check(
let messages = commands::check::check(
&files,
&pyproject_config,
&config_arguments,
@@ -372,7 +372,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &diagnostics, preview)?;
printer.write_continuously(&mut writer, &messages, preview)?;
// In watch mode, we may need to re-resolve the configuration.
// TODO(charlie): Re-compute other derivative values, like the `printer`.
@@ -392,7 +392,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
Printer::clear_screen()?;
printer.write_to_user("File change detected...\n");
let diagnostics = commands::check::check(
let messages = commands::check::check(
&files,
&pyproject_config,
&config_arguments,
@@ -401,7 +401,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &diagnostics, preview)?;
printer.write_continuously(&mut writer, &messages, preview)?;
}
Err(err) => return Err(err.into()),
}
@@ -463,11 +463,11 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
// there are any violations, unless we're explicitly asked to exit zero on
// fix.
if cli.exit_non_zero_on_fix {
if !diagnostics.fixed.is_empty() || !diagnostics.inner.is_empty() {
if !diagnostics.fixed.is_empty() || !diagnostics.messages.is_empty() {
return Ok(ExitStatus::Failure);
}
} else {
if !diagnostics.inner.is_empty() {
if !diagnostics.messages.is_empty() {
return Ok(ExitStatus::Failure);
}
}

View File

@@ -14,7 +14,7 @@ use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
JsonEmitter, JsonLinesEmitter, JunitEmitter, OldDiagnostic, PylintEmitter, RdjsonEmitter,
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, PylintEmitter, RdjsonEmitter,
SarifEmitter, TextEmitter,
};
use ruff_linter::notify_user;
@@ -85,7 +85,7 @@ impl Printer {
.sum::<usize>();
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
let remaining = diagnostics.inner.len();
let remaining = diagnostics.messages.len();
let total = fixed + remaining;
if fixed > 0 {
let s = if total == 1 { "" } else { "s" };
@@ -229,16 +229,16 @@ impl Printer {
match self.format {
OutputFormat::Json => {
JsonEmitter.emit(writer, &diagnostics.inner, &context)?;
JsonEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Rdjson => {
RdjsonEmitter.emit(writer, &diagnostics.inner, &context)?;
RdjsonEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::JsonLines => {
JsonLinesEmitter.emit(writer, &diagnostics.inner, &context)?;
JsonLinesEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Junit => {
JunitEmitter.emit(writer, &diagnostics.inner, &context)?;
JunitEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Concise | OutputFormat::Full => {
TextEmitter::default()
@@ -246,7 +246,7 @@ impl Printer {
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
.with_show_source(self.format == OutputFormat::Full)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.inner, &context)?;
.emit(writer, &diagnostics.messages, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
if !diagnostics.fixed.is_empty() {
@@ -262,7 +262,7 @@ impl Printer {
GroupedEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.inner, &context)?;
.emit(writer, &diagnostics.messages, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
if !diagnostics.fixed.is_empty() {
@@ -274,19 +274,19 @@ impl Printer {
self.write_summary_text(writer, diagnostics)?;
}
OutputFormat::Github => {
GithubEmitter.emit(writer, &diagnostics.inner, &context)?;
GithubEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Gitlab => {
GitlabEmitter::default().emit(writer, &diagnostics.inner, &context)?;
GitlabEmitter::default().emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Pylint => {
PylintEmitter.emit(writer, &diagnostics.inner, &context)?;
PylintEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Azure => {
AzureEmitter.emit(writer, &diagnostics.inner, &context)?;
AzureEmitter.emit(writer, &diagnostics.messages, &context)?;
}
OutputFormat::Sarif => {
SarifEmitter.emit(writer, &diagnostics.inner, &context)?;
SarifEmitter.emit(writer, &diagnostics.messages, &context)?;
}
}
@@ -301,13 +301,13 @@ impl Printer {
writer: &mut dyn Write,
) -> Result<()> {
let statistics: Vec<ExpandedStatistics> = diagnostics
.inner
.messages
.iter()
.map(|message| (message.noqa_code(), message))
.sorted_by_key(|(code, message)| (*code, message.fixable()))
.fold(
vec![],
|mut acc: Vec<((Option<NoqaCode>, &OldDiagnostic), usize)>, (code, message)| {
|mut acc: Vec<((Option<NoqaCode>, &Message), usize)>, (code, message)| {
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
@@ -416,20 +416,20 @@ impl Printer {
}
if self.log_level >= LogLevel::Default {
let s = if diagnostics.inner.len() == 1 {
let s = if diagnostics.messages.len() == 1 {
""
} else {
"s"
};
notify_user!(
"Found {} error{s}. Watching for file changes.",
diagnostics.inner.len()
diagnostics.messages.len()
);
}
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
if !diagnostics.inner.is_empty() {
if !diagnostics.messages.is_empty() {
if self.log_level >= LogLevel::Default {
writeln!(writer)?;
}
@@ -439,7 +439,7 @@ impl Printer {
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_source(preview)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.inner, &context)?;
.emit(writer, &diagnostics.messages, &context)?;
}
writer.flush()?;
@@ -522,7 +522,7 @@ impl FixableStatistics {
let mut applicable = 0;
let mut inapplicable_unsafe = 0;
for message in &diagnostics.inner {
for message in &diagnostics.messages {
if let Some(fix) = message.fix() {
if fix.applies(unsafe_fixes.required_applicability()) {
applicable += 1;

View File

@@ -17,7 +17,6 @@ fn command() -> Command {
command.arg("analyze");
command.arg("graph");
command.arg("--preview");
command.env_clear();
command
}
@@ -566,8 +565,8 @@ fn venv() -> Result<()> {
----- stderr -----
ruff failed
Cause: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
Cause: No such file or directory (os error 2)
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
");
});

View File

@@ -352,7 +352,7 @@ impl DisplaySet<'_> {
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
// is. For now, just accept that sometimes the code line will be longer than
// desired.
let next = char_width(ch).unwrap_or(1);
let next = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
if taken + next > right - left {
was_cut_right = true;
break;
@@ -377,7 +377,7 @@ impl DisplaySet<'_> {
let left: usize = text
.chars()
.take(left)
.map(|ch| char_width(ch).unwrap_or(1))
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.sum();
let mut annotations = annotations.clone();
@@ -1393,7 +1393,6 @@ fn format_body<'m>(
let line_length: usize = line.len();
let line_range = (current_index, current_index + line_length);
let end_line_size = end_line.len();
body.push(DisplayLine::Source {
lineno: Some(current_line),
inline_marks: vec![],
@@ -1453,12 +1452,12 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
let mut annotation_end_col = line
[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
if annotation_start_col == annotation_end_col {
// At least highlight something
@@ -1500,7 +1499,7 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>();
let annotation_end_col = annotation_start_col + 1;
@@ -1559,7 +1558,7 @@ fn format_body<'m>(
{
let end_mark = line[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| char_width(c).unwrap_or(0))
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.sum::<usize>()
.saturating_sub(1);
// If the annotation ends on a line-end character, we
@@ -1755,11 +1754,3 @@ fn format_inline_marks(
}
Ok(())
}
fn char_width(c: char) -> Option<usize> {
if c == '\t' {
Some(4)
} else {
unicode_width::UnicodeWidthChar::width(c)
}
}

View File

@@ -1,36 +0,0 @@
<svg width="1196px" height="128px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:6</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip</tspan><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,20 +0,0 @@
[message]
level = "Error"
id = "E0308"
title = "mismatched types"
[[message.snippets]]
source = """
s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip]['an
"""
line_start = 4
origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = ""
level = "Error"
range = [5, 11]
[renderer]
# anonymized_line_numbers = true
color = true

View File

@@ -21,7 +21,7 @@
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:238</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = "expected `()`, found integer"
level = "Error"
range = [237, 239]
range = [241, 243]
[[message.snippets.annotations]]
label = "expected due to this"
level = "Error"
range = [232, 234]
range = [236, 238]
[renderer]

View File

@@ -141,7 +141,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
&case.file_path,
format!(
"{}\n# A comment\n",
source_text(&case.db, case.file).as_str()
source_text(&case.db, case.file).load(&case.db).as_str()
),
)
.unwrap();
@@ -348,58 +348,6 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
});
}
fn benchmark_many_attribute_assignments(criterion: &mut Criterion) {
setup_rayon();
criterion.bench_function("ty_micro[many_attribute_assignments]", |b| {
b.iter_batched_ref(
|| {
// This is a regression benchmark for https://github.com/astral-sh/ty/issues/627.
// Before this was fixed, the following sample would take >1s to type check.
setup_micro_case(
r#"
class C:
def f(self: "C"):
if isinstance(self.a, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
"#,
)
},
|case| {
let Case { db, .. } = case;
let result = db.check();
assert!(!result.is_empty());
},
BatchSize::SmallInput,
);
});
}
struct ProjectBenchmark<'a> {
project: InstalledProject<'a>,
fs: MemoryFileSystem,
@@ -429,7 +377,8 @@ impl<'a> ProjectBenchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -534,7 +483,6 @@ criterion_group!(
micro,
benchmark_many_string_assignments,
benchmark_many_tuple_assignments,
benchmark_many_attribute_assignments,
);
criterion_group!(project, anyio, attrs, hydra);
criterion_main!(check_file, micro, project);

View File

@@ -36,7 +36,8 @@ impl<'a> Benchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -203,23 +204,8 @@ static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new
)
});
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: vec![SystemPath::new("tanjun")],
dependencies: vec!["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
)
});
#[track_caller]
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| {
@@ -227,55 +213,41 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| {
check_project(db, benchmark.max_diagnostics);
});
}
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=3)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, benchmark.max_diagnostics);
db
})
.bench_local_refs(|db| {
check_project(db, benchmark.max_diagnostics);
});
}
fn main() {
let filter =
std::env::var("TY_LOG").unwrap_or("ty_walltime=info,ruff_benchmark=info".to_string());
let _logging = setup_logging_with_filter(&filter).expect("Filter to be valid");
// Disable multithreading for now due to
// https://github.com/salsa-rs/salsa/issues/918.
//
// Salsa has a fast-path for the first db when looking up ingredients.
// It seems that this fast-path becomes extremely slow for all db's other
// than the first one, especially when using multithreading (10x slower than the first run).
ThreadPoolBuilder::new()
.num_threads(1)
.use_current_thread()
.build_global()
.unwrap();
let filter =
std::env::var("TY_LOG").unwrap_or("ty_walltime=info,ruff_benchmark=info".to_string());
let _logging = setup_logging_with_filter(&filter).expect("Filter to be valid");
// Salsa uses an optimized lookup for the ingredient index when using only a single database.
// This optimization results in at least a 10% speedup compared to when using multiple databases.
// To reduce noise, run one benchmark so that all benchmarks take the less optimized "not the first db"
// branch when looking up the ingredient index.
{
let db = TANJUN.setup_iteration();
check_project(&db, TANJUN.max_diagnostics);
}
divan::main();
}

View File

@@ -74,17 +74,19 @@ impl<'a> RealWorldProject<'a> {
};
// Install dependencies if specified
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start_install = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start_install.elapsed().as_secs_f64()
);
if !checkout.project().dependencies.is_empty() {
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start.elapsed().as_secs_f64()
);
}
tracing::debug!("Project setup took: {:.2}s", start.elapsed().as_secs_f64());
@@ -279,14 +281,6 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
String::from_utf8_lossy(&output.stderr)
);
if checkout.project().dependencies.is_empty() {
tracing::debug!(
"No dependencies to install for project '{}'",
checkout.project().name
);
return Ok(());
}
// Install dependencies with date constraint in the isolated environment
let mut cmd = Command::new("uv");
cmd.args([

View File

@@ -30,14 +30,14 @@ filetime = { workspace = true }
glob = { workspace = true }
ignore = { workspace = true, optional = true }
matchit = { workspace = true }
path-slash = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
path-slash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
rustc-hash = { workspace = true }
zip = { workspace = true }
[target.'cfg(target_arch="wasm32")'.dependencies]

View File

@@ -735,9 +735,6 @@ pub enum DiagnosticId {
/// # no `[overrides.rules]`
/// ```
UselessOverridesSection,
/// Use of a deprecated setting.
DeprecatedSetting,
}
impl DiagnosticId {
@@ -776,7 +773,6 @@ impl DiagnosticId {
DiagnosticId::EmptyInclude => "empty-include",
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
DiagnosticId::DeprecatedSetting => "deprecated-setting",
}
}

View File

@@ -8,10 +8,11 @@ use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextRange, TextSize};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
use crate::source::SourceTextRef;
use crate::{
Db,
files::File,
source::{SourceText, line_index, source_text},
source::{line_index, source_text},
system::SystemPath,
};
@@ -644,7 +645,7 @@ impl FileResolver for &dyn Db {
fn input(&self, file: File) -> Input {
Input {
text: source_text(*self, file),
text: source_text(*self, file).load(*self),
line_index: line_index(*self, file),
}
}
@@ -657,7 +658,7 @@ impl FileResolver for &dyn Db {
/// line index for efficiently querying its contents.
#[derive(Clone, Debug)]
pub struct Input {
pub(crate) text: SourceText,
pub(crate) text: SourceTextRef,
pub(crate) line_index: LineIndex,
}
@@ -2158,7 +2159,7 @@ watermelon
let span = self.path(path);
let file = span.expect_ty_file();
let text = source_text(&self.db, file);
let text = source_text(&self.db, file).load(&self.db);
let line_index = line_index(&self.db, file);
let source = SourceCode::new(text.as_str(), &line_index);

View File

@@ -30,7 +30,7 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
}
pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed<ModModule> {
let source = source_text(db, file);
let source = source_text(db, file).load(db);
let ty = file.source_type(db);
let target_version = db.python_version();

View File

@@ -1,6 +1,7 @@
use std::ops::Deref;
use std::sync::Arc;
use arc_swap::ArcSwapOption;
use countme::Count;
use ruff_notebook::Notebook;
@@ -11,8 +12,17 @@ use crate::Db;
use crate::files::{File, FilePath};
/// Reads the source text of a python text file (must be valid UTF8) or notebook.
#[salsa::tracked]
#[salsa::tracked(no_eq)]
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
let source_text = source_text_impl(db, file);
SourceText {
file,
inner: Arc::new(ArcSwapOption::new(Some(Arc::new(source_text)))),
}
}
fn source_text_impl(db: &dyn Db, file: File) -> SourceTextInner {
let path = file.path(db);
let _span = tracing::trace_span!("source_text", file = %path).entered();
let mut read_error = None;
@@ -37,12 +47,10 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
.into()
};
SourceText {
inner: Arc::new(SourceTextInner {
kind,
read_error,
count: Count::new(),
}),
SourceTextInner {
kind,
read_error,
_count: Count::new(),
}
}
@@ -65,12 +73,41 @@ fn is_notebook(path: &FilePath) -> bool {
/// The file containing the source text can either be a text file or a notebook.
///
/// Cheap cloneable in `O(1)`.
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone)]
pub struct SourceText {
file: File,
inner: Arc<ArcSwapOption<SourceTextInner>>,
}
impl PartialEq for SourceText {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for SourceText {}
impl SourceText {
pub fn load(&self, db: &dyn Db) -> SourceTextRef {
SourceTextRef {
inner: self
.inner
.load_full()
.unwrap_or_else(|| Arc::new(source_text_impl(db, self.file))),
}
}
pub fn clear(&self) {
self.inner.store(None);
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct SourceTextRef {
inner: Arc<SourceTextInner>,
}
impl SourceText {
impl SourceTextRef {
/// Returns the python code as a `str`.
pub fn as_str(&self) -> &str {
match &self.inner.kind {
@@ -98,7 +135,7 @@ impl SourceText {
}
}
impl Deref for SourceText {
impl Deref for SourceTextRef {
type Target = str;
fn deref(&self) -> &str {
@@ -106,7 +143,7 @@ impl Deref for SourceText {
}
}
impl std::fmt::Debug for SourceText {
impl std::fmt::Debug for SourceTextRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_tuple("SourceText");
@@ -123,11 +160,18 @@ impl std::fmt::Debug for SourceText {
}
}
#[derive(Eq, PartialEq)]
struct SourceTextInner {
count: Count<SourceText>,
kind: SourceTextKind,
read_error: Option<SourceTextError>,
_count: Count<SourceText>,
}
impl Eq for SourceTextInner {}
impl PartialEq for SourceTextInner {
fn eq(&self, other: &Self) -> bool {
self.kind.eq(&other.kind) && self.read_error.eq(&other.read_error)
}
}
#[derive(Eq, PartialEq)]
@@ -161,7 +205,7 @@ pub enum SourceTextError {
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
let _span = tracing::trace_span!("line_index", ?file).entered();
let source = source_text(db, file);
let source = source_text(db, file).load(db);
LineIndex::from_source_text(&source)
}
@@ -188,11 +232,11 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
assert_eq!(source_text(&db, file).as_str(), "x = 10");
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
db.write_file(path, "x = 20").unwrap();
assert_eq!(source_text(&db, file).as_str(), "x = 20");
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 20");
Ok(())
}
@@ -206,13 +250,13 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
assert_eq!(source_text(&db, file).as_str(), "x = 10");
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
// Change the file permission only
file.set_permissions(&mut db).to(Some(0o777));
db.clear_salsa_events();
assert_eq!(source_text(&db, file).as_str(), "x = 10");
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
let events = db.take_salsa_events();
@@ -234,7 +278,7 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
let index = line_index(&db, file);
let source = source_text(&db, file);
let source = source_text(&db, file).load(&db);
assert_eq!(index.line_count(), 2);
assert_eq!(
@@ -276,7 +320,7 @@ mod tests {
)?;
let file = system_path_to_file(&db, path).unwrap();
let source = source_text(&db, file);
let source = source_text(&db, file).load(&db);
assert!(source.is_notebook());
assert_eq!(source.as_str(), "x = 10\n");

View File

@@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::Result;
use std::sync::Arc;
use zip::CompressionMethod;
@@ -9,7 +9,7 @@ use ruff_db::{Db as SourceDb, Upcast};
use ruff_python_ast::PythonVersion;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
Db, Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
};
@@ -35,32 +35,24 @@ impl ModuleDb {
python_version: PythonVersion,
venv_path: Option<SystemPathBuf>,
) -> Result<Self> {
let db = Self::default();
let mut search_paths = SearchPathSettings::new(src_roots);
// TODO: Consider calling `PythonEnvironment::discover` if the `venv_path` is not provided.
if let Some(venv_path) = venv_path {
let environment =
PythonEnvironment::new(venv_path, SysPrefixPathOrigin::PythonCliFlag, db.system())?;
search_paths.site_packages_paths = environment
.site_packages_paths(db.system())
.context("Failed to discover the site-packages directory")?
.into_vec();
search_paths.python_path =
PythonPath::sys_prefix(venv_path, SysPrefixPathOrigin::PythonCliFlag);
}
let search_paths = search_paths
.to_search_paths(db.system(), db.vendored())
.context("Invalid search path settings")?;
let db = Self::default();
Program::from_settings(
&db,
ProgramSettings {
python_version: PythonVersionWithSource {
python_version: Some(PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::default(),
},
}),
python_platform: PythonPlatform::default(),
search_paths,
},
);
)?;
Ok(db)
}

View File

@@ -36,20 +36,14 @@ impl fmt::Display for AnalyzeSettings {
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, CacheKey)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum Direction {
/// Construct a map from module to its dependencies (i.e., the modules that it imports).
#[default]
#[cfg_attr(feature = "serde", serde(alias = "Dependencies"))]
Dependencies,
/// Construct a map from module to its dependents (i.e., the modules that import it).
#[cfg_attr(feature = "serde", serde(alias = "Dependents"))]
Dependents,
}

View File

@@ -165,15 +165,3 @@ async def test_trio_async115_helpers():
await trio.sleep(seconds=0) # ASYNC115
await trio.sleep(delay=0) # OK
# https://github.com/astral-sh/ruff/issues/18740
# The autofix for this is unsafe due to the comments.
async def func():
import trio
await (
trio # comment
.sleep( # comment
0 # comment
)
)

View File

@@ -128,11 +128,3 @@ async def test_trio_async116_helpers():
await trio.sleep(seconds=86401) # ASYNC116
await trio.sleep(delay=86401) # OK
async def _():
import trio
from trio import sleep
await sleep(18446744073709551616)
await trio.sleep(99999999999999999999)

View File

@@ -29,13 +29,3 @@ def this_is_fine():
o = object()
if callable(o):
print("Ooh, this is actually callable.")
# https://github.com/astral-sh/ruff/issues/18741
# The autofix for this is unsafe due to the comments.
hasattr(
# comment 1
obj, # comment 2
# comment 3
"__call__", # comment 4
# comment 5
)

View File

@@ -36,19 +36,9 @@ set(
# some more
)
# t-strings
print(t"Hello {set(f(a) for a in 'abc')} World")
print(t"Hello { set(f(a) for a in 'abc') } World")
small_nums = t"{set(a if a < 6 else 0 for a in range(3))}"
print(t"Hello {set(a for a in range(3))} World")
print(t"{set(a for a in 'abc') - set(a for a in 'ab')}")
print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }")
# Not built-in set.
def set(*args, **kwargs):
return None
set(2 * x for x in range(3))
set(x for x in range(3))

View File

@@ -34,16 +34,4 @@ s = set( # outer set comment
))))
# Test trailing comma case
s = set([x for x in range(3)],)
s = t"{set([x for x in 'ab'])}"
s = t'{set([x for x in "ab"])}'
def f(x):
return x
s = t"{set([f(x) for x in 'ab'])}"
s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }"
s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}"
s = set([x for x in range(3)],)

View File

@@ -24,12 +24,3 @@ f"{set(['a', 'b']) - set(['a'])}"
f"{ set(['a', 'b']) - set(['a']) }"
f"a {set(['a', 'b']) - set(['a'])} b"
f"a { set(['a', 'b']) - set(['a']) } b"
t"{set([1,2,3])}"
t"{set(['a', 'b'])}"
t'{set(["a", "b"])}'
t"{set(['a', 'b']) - set(['a'])}"
t"{ set(['a', 'b']) - set(['a']) }"
t"a {set(['a', 'b']) - set(['a'])} b"
t"a { set(['a', 'b']) - set(['a']) } b"

View File

@@ -27,13 +27,3 @@ dict(
tuple( # comment
)
t"{dict(x='y')}"
t'{dict(x="y")}'
t"{dict()}"
t"a {dict()} b"
t"{dict(x='y') | dict(y='z')}"
t"{ dict(x='y') | dict(y='z') }"
t"a {dict(x='y') | dict(y='z')} b"
t"a { dict(x='y') | dict(y='z') } b"

View File

@@ -70,8 +70,3 @@ list(map(lambda: 1, "xyz"))
list(map(lambda x, y: x, [(1, 2), (3, 4)]))
list(map(lambda: 1, "xyz"))
list(map(lambda x, y: x, [(1, 2), (3, 4)]))
# When inside t-string, then the fix should be surrounded by whitespace
_ = t"{set(map(lambda x: x % 2 == 0, nums))}"
_ = t"{dict(map(lambda v: (v, v**2), nums))}"

View File

@@ -90,14 +90,3 @@ def func():
def func():
{(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK
# https://github.com/astral-sh/ruff/issues/18764
{ # 1
a # 2
: # 3
None # 4
for # 5
a # 6
in # 7
iterable # 8
} # 9

View File

@@ -1,6 +0,0 @@
def f_byte():
raise RuntimeError(b"This is an example exception")
def f_byte_empty():
raise RuntimeError(b"")

View File

@@ -32,12 +32,6 @@ except ...:
### No errors
logging.info("", exc_info=ValueError())
logger.info("", exc_info=ValueError())
logging.info("", exc_info=(exc_type, exc_value, exc_traceback))
logger.info("", exc_info=(exc_type, exc_value, exc_traceback))
logging.info("", exc_info=a)
logger.info("", exc_info=a)

View File

@@ -14,6 +14,3 @@ range(0, 10, 1)
range(0, 10, step=1)
range(start=0, stop=10)
range(0, stop=10)
# regression test for https://github.com/astral-sh/ruff/pull/18805
range((0), 42)

View File

@@ -1,5 +1,4 @@
from typing import (
TYPE_CHECKING,
Union,
)
@@ -91,22 +90,3 @@ class Foo:
def bad5(self, arg: int | (float | complex)) -> None:
...
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None: # PYI041 - no fix
pass
if TYPE_CHECKING:
def f2(self, arg: None | int | None | float = None) -> None: ... # PYI041 - with fix
else:
def f2(self, arg=None) -> None:
pass
def f3(self, arg: None | float | None | int | None = None) -> None: # PYI041 - with fix
pass

View File

@@ -70,11 +70,3 @@ class Foo:
def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041
def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None: ... # PYI041 - with fix
def f3(self, arg: None | float | None | int | None = None) -> None: ... # PYI041 - with fix

View File

@@ -1,8 +0,0 @@
from __future__ import annotations
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None:
pass

View File

@@ -76,20 +76,3 @@ def aliased_parentheses_with_params():
)
def aliased_parentheses_no_params_multiline():
return 42
# https://github.com/astral-sh/ruff/issues/18770
@pytest.fixture(
# TODO: use module scope
# scope="module"
)
def my_fixture(): ...
@(pytest.fixture())
def outer_paren_fixture_no_params():
return 42
@(fixture())
def outer_paren_imported_fixture_no_params():
return 42

View File

@@ -1,8 +1,3 @@
@pytest.mark.foo(scope="module")
def ok_due_to_missing_import():
pass
import pytest
@@ -77,25 +72,3 @@ class TestClass:
@pytest.mark.foo()
def test_something():
pass
# https://github.com/astral-sh/ruff/issues/18770
@pytest.mark.parametrize(
# TODO: fix later
# ("param1", "param2"),
# (
# (1, 2),
# (3, 4),
# ),
)
def test_bar(param1, param2): ...
@(pytest.mark.foo())
def test_outer_paren_mark_function():
pass
class TestClass:
@(pytest.mark.foo())
def test_method_outer_paren():
pass

View File

@@ -105,8 +105,3 @@ if future.exception():
future = executor.submit(float, "a")
if future.exception():
raise future.Exception()
raise TypeError(
# comment
)

View File

@@ -49,13 +49,3 @@ class Baz:
def prop4(self) -> None:
print("I've run out of things to say")
return None
# https://github.com/astral-sh/ruff/issues/18774
class _:
def foo(bar):
if not bar:
return
return (
None # comment
)

View File

@@ -128,16 +128,3 @@ def write_models(directory, Models):
pass; \
\
#
# Regression tests for: https://github.com/astral-sh/ruff/issues/18209
try:
1 / 0
except ():
pass
BaseException = ValueError
try:
int("a")
except BaseException:
pass

View File

@@ -49,9 +49,3 @@ def foo(some_other: object):
# OK
def foo(some_other):
a = some_other.get('a', None)
# https://github.com/astral-sh/ruff/issues/18777
def foo():
dict = {"Tom": 23, "Maria": 23, "Dog": 11}
age = dict.get("Cat", None)

View File

@@ -23,14 +23,3 @@ for k, v in zip(d2.keys(), d2.values()): # SIM911
items = zip(x.keys(), x.values()) # OK
items.bar = zip(x.keys(), x.values()) # OK
# https://github.com/astral-sh/ruff/issues/18777
def foo():
dict = {}
for country, stars in zip(dict.keys(), dict.values()):
...
# https://github.com/astral-sh/ruff/issues/18776
flag_stars = {}
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...

View File

@@ -2,81 +2,13 @@ import os.path
from pathlib import Path
from os.path import getsize
filename = "filename"
filename1 = __file__
filename2 = Path("filename")
os.path.getsize("filename")
os.path.getsize(b"filename")
os.path.getsize(Path("filename"))
os.path.getsize(__file__)
os.path.getsize(filename)
os.path.getsize(filename1)
os.path.getsize(filename2)
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=Path("filename"))
os.path.getsize(filename=__file__)
getsize("filename")
getsize(b"filename")
getsize(Path("filename"))
getsize(__file__)
getsize(filename="filename")
getsize(filename=b"filename")
getsize(filename=Path("filename"))
getsize(filename=__file__)
getsize(filename)
getsize(filename1)
getsize(filename2)
os.path.getsize(
"filename", # comment
)
os.path.getsize(
# comment
"filename"
,
# comment
)
os.path.getsize(
# comment
b"filename"
# comment
)
os.path.getsize( # comment
Path(__file__)
# comment
) # comment
getsize( # comment
"filename")
getsize( # comment
b"filename",
#comment
)
os.path.getsize("file" + "name")
getsize \
\
\
( # comment
"filename",
)
getsize(Path("filename").resolve())
import pathlib
os.path.getsize(pathlib.Path("filename"))

View File

@@ -1,5 +0,0 @@
import os
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=__file__)

View File

@@ -85,19 +85,3 @@ import builtins
for i in builtins.list(nested_tuple): # PERF101
pass
# https://github.com/astral-sh/ruff/issues/18783
items = (1, 2, 3)
for i in(list)(items):
print(i)
# https://github.com/astral-sh/ruff/issues/18784
items = (1, 2, 3)
for i in ( # 1
list # 2
# 3
)( # 4
items # 5
# 6
):
print(i)

View File

@@ -163,10 +163,4 @@ def foo():
for k, v in src:
if lambda: 0:
dst[k] = v
# https://github.com/astral-sh/ruff/issues/18859
def foo():
v = {}
for o,(x,)in():
v[x,]=o
dst[k] = v

View File

@@ -14,6 +14,3 @@ hidden = {"a": "!"}
"" % {
'test1': '',
'test2': '',
}
# https://github.com/astral-sh/ruff/issues/18806

View File

@@ -4,11 +4,3 @@
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
(''
.format(x=2)) # F522
# https://github.com/astral-sh/ruff/issues/18806
# The fix here is unsafe because the unused argument has side effect
"Hello, {name}".format(greeting=print(1), name="World")
# The fix here is safe because the unused argument has no side effect,
# even though the used argument has a side effect
"Hello, {name}".format(greeting="Pikachu", name=print(1))

View File

@@ -35,11 +35,3 @@
# Removing the final argument.
"Hello".format("world")
"Hello".format("world", key="value")
# https://github.com/astral-sh/ruff/issues/18806
# The fix here is unsafe because the unused argument has side effect
"Hello, {0}".format("world", print(1))
# The fix here is safe because the unused argument has no side effect,
# even though the used argument has a side effect
"Hello, {0}".format(print(1), "Pikachu")

View File

@@ -1,5 +1,3 @@
# Mock objects
# ============
# Errors
assert my_mock.not_called()
assert my_mock.called_once_with()
@@ -19,25 +17,3 @@ my_mock.assert_called()
my_mock.assert_called_once_with()
"""like :meth:`Mock.assert_called_once_with`"""
"""like :meth:`MagicMock.assert_called_once_with`"""
# AsyncMock objects
# =================
# Errors
assert my_mock.not_awaited()
assert my_mock.awaited_once_with()
assert my_mock.not_awaited
assert my_mock.awaited_once_with
my_mock.assert_not_awaited
my_mock.assert_awaited
my_mock.assert_awaited_once_with
my_mock.assert_awaited_once_with
MyMock.assert_awaited_once_with
assert my_mock.awaited
# OK
assert my_mock.await_count == 1
my_mock.assert_not_awaited()
my_mock.assert_awaited()
my_mock.assert_awaited_once_with()
"""like :meth:`Mock.assert_awaited_once_with`"""
"""like :meth:`MagicMock.assert_awaited_once_with`"""

View File

@@ -70,9 +70,3 @@ class D(C):
class E(A, C):
...
# https://github.com/astral-sh/ruff/issues/18814
class Bar(Foo, # 1
Foo # 2
):
pass

View File

@@ -1,4 +0,0 @@
import collections as collections
from collections import OrderedDict as OrderedDict
from . import foo as foo
from .foo import bar as bar

View File

@@ -168,7 +168,7 @@ def github_issue_1879():
def function_returning_function(r):
return function_returning_generator(r)
assert len(function_returning_list(z)) # [PLC1802] differs from pylint
assert len(function_returning_list(z)) # [PLC1802] differs from pylint
assert len(function_returning_int(z))
# This should raise a PLC1802 once astroid can infer it
# See https://github.com/pylint-dev/pylint/pull/3821#issuecomment-743771514
@@ -196,7 +196,7 @@ def f(cond:bool):
def g(cond:bool):
x = [1,2,3]
if cond:
x = [4,5,6]
x = [4,5,6]
if len(x): # this should be addressed
print(x)
del x
@@ -236,15 +236,3 @@ def j():
# regression tests for https://github.com/astral-sh/ruff/issues/14690
bool(len(ascii(1)))
bool(len(sorted("")))
# regression tests for https://github.com/astral-sh/ruff/issues/18811
fruits = []
if(len)(fruits):
...
# regression tests for https://github.com/astral-sh/ruff/issues/18812
fruits = []
if len(
fruits # comment
):
...

View File

@@ -92,8 +92,3 @@ if y == np.inf:
# OK
if x == "nan":
pass
# PLW0117
# https://github.com/astral-sh/ruff/issues/18596
assert x == float("-NaN")
assert x == float(" \n+nan \t")

View File

@@ -14,7 +14,7 @@ min(1, min(2, 3, key=test))
# This will still trigger, to merge the calls without keyword args.
min(1, min(2, 3, key=test), min(4, 5))
# The fix is already unsafe, so deleting comments is okay.
# Don't provide a fix if there are comments within the call.
min(
1, # This is a comment.
min(2, 3),
@@ -55,8 +55,3 @@ max_word_len = max(
*(len(word) for word in "blah blah blah".split(" ")),
len("Done!"),
)
# Outer call has a single argument, inner call has multiple arguments; should not trigger.
min(min([2, 3], [4, 1]))
max(max([2, 4], [3, 1]))

View File

@@ -129,6 +129,3 @@ blah = dict[{"a": 1}.__delitem__("a")] # OK
# https://github.com/astral-sh/ruff/issues/14597
assert "abc".__str__() == "abc"
# https://github.com/astral-sh/ruff/issues/18813
three = 1 if 1 else(3.0).__str__()

View File

@@ -71,47 +71,3 @@ if sys.version_info <= (3, 14, 0):
if sys.version_info <= (3, 14, 15):
print()
# https://github.com/astral-sh/ruff/issues/18165
if sys.version_info.major >= 3:
print("3")
else:
print("2")
if sys.version_info.major > 3:
print("3")
else:
print("2")
if sys.version_info.major <= 3:
print("3")
else:
print("2")
if sys.version_info.major < 3:
print("3")
else:
print("2")
if sys.version_info.major == 3:
print("3")
else:
print("2")
# Semantically incorrect, skip fixing
if sys.version_info.major[1] > 3:
print(3)
else:
print(2)
if sys.version_info.major > (3, 13):
print(3)
else:
print(2)
if sys.version_info.major[:2] > (3, 13):
print(3)
else:
print(2)

View File

@@ -105,12 +105,3 @@ with open("furb129.py") as f:
# Test case for issue #17683 (missing space before keyword)
print([line for line in f.readlines()if True])
# https://github.com/astral-sh/ruff/issues/18843
with open("file.txt") as fp:
for line in ( # 1
fp. # 3 # 2
readlines( # 4
) # 5
):
...

View File

@@ -44,13 +44,6 @@ log(1, math.e)
math.log(1, 2.0001)
math.log(1, 10.0001)
# https://github.com/astral-sh/ruff/issues/18747
def log():
yield math.log((yield), math.e)
def log():
yield math.log((yield from x), math.e)
# see: https://github.com/astral-sh/ruff/issues/18639
math.log(1, 10 # comment

View File

@@ -20,12 +20,6 @@ _ = Decimal.from_float(float("-inf"))
_ = Decimal.from_float(float("Infinity"))
_ = Decimal.from_float(float("-Infinity"))
_ = Decimal.from_float(float("nan"))
_ = Decimal.from_float(float("-NaN"))
_ = Decimal.from_float(float(" \n+nan \t"))
_ = Decimal.from_float(float(" iNf\n\t "))
_ = Decimal.from_float(float(" -inF\n \t"))
_ = Decimal.from_float(float(" InfinIty\n\t "))
_ = Decimal.from_float(float(" -InfinIty\n \t"))
# OK
_ = Fraction(0.1)

View File

@@ -110,19 +110,3 @@ class ShouldMatchB008RuleOfImmutableTypeAnnotationIgnored:
# ignored
this_is_fine: int = f()
# Test for:
# https://github.com/astral-sh/ruff/issues/17424
@dataclass(frozen=True)
class C:
foo: int = 1
@dataclass
class D:
c: C = C()
@dataclass
class E:
c: C = C()

View File

@@ -73,55 +73,3 @@ class IntConversionDescriptor:
@frozen
class InventoryItem:
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
# Test for:
# https://github.com/astral-sh/ruff/issues/17424
@frozen
class C:
foo: int = 1
@attr.frozen
class D:
foo: int = 1
@define
class E:
c: C = C()
d: D = D()
@attr.s
class F:
foo: int = 1
@attr.mutable
class G:
foo: int = 1
@attr.attrs
class H:
f: F = F()
g: G = G()
@attr.define
class I:
f: F = F()
g: G = G()
@attr.frozen
class J:
f: F = F()
g: G = G()
@attr.mutable
class K:
f: F = F()
g: G = G()

View File

@@ -36,19 +36,5 @@ f"{ascii(bla)}" # OK
)
# https://github.com/astral-sh/ruff/issues/16325
# OK
f"{str({})}"
f"{str({} | {})}"
import builtins
f"{builtins.repr(1)}"
f"{repr(1)=}"
f"{repr(lambda: 1)}"
f"{repr(x := 2)}"
f"{str(object=3)}"

View File

@@ -1,5 +1,5 @@
import collections
from collections import deque
import collections
def f():
@@ -91,14 +91,3 @@ def f():
def f():
deque([], iterable=[])
# https://github.com/astral-sh/ruff/issues/18854
deque("")
deque(b"")
deque(f"")
deque(f"" "")
deque(f"" f"")
deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK

View File

@@ -23,69 +23,69 @@ value2 = my_dict.get("key2", [1, 2, 3]).append(4)
# Valid
# Using dict.get with a falsy fallback: False
value = my_dict.get("key", False)
value = my_dict.get("key", False)
# Using dict.get with a falsy fallback: empty string
value = my_dict.get("key", "")
value = my_dict.get("key", "")
# Using dict.get with a falsy fallback: empty list
value = my_dict.get("key", [])
value = my_dict.get("key", [])
# Using dict.get with a falsy fallback: empty dict
value = my_dict.get("key", {})
value = my_dict.get("key", {})
# Using dict.get with a falsy fallback: empty set
value = my_dict.get("key", set())
value = my_dict.get("key", set())
# Using dict.get with a falsy fallback: zero integer
value = my_dict.get("key", 0)
value = my_dict.get("key", 0)
# Using dict.get with a falsy fallback: zero float
value = my_dict.get("key", 0.0)
value = my_dict.get("key", 0.0)
# Using dict.get with a falsy fallback: None
value = my_dict.get("key", None)
value = my_dict.get("key", None)
# Using dict.get with a falsy fallback via function call
value = my_dict.get("key", list())
value = my_dict.get("key", dict())
value = my_dict.get("key", set())
value = my_dict.get("key", list())
value = my_dict.get("key", dict())
value = my_dict.get("key", set())
# Reassigning with falsy fallback
def get_value(d):
return d.get("key", False)
return d.get("key", False)
# Multiple dict.get calls with mixed fallbacks
value1 = my_dict.get("key1", "default")
value2 = my_dict.get("key2", 0)
value2 = my_dict.get("key2", 0)
value3 = my_dict.get("key3", "another default")
# Using dict.get in a class with falsy fallback
class MyClass:
def method(self):
return self.data.get("key", {})
return self.data.get("key", {})
# Using dict.get in a nested function with falsy fallback
def outer():
def inner():
return my_dict.get("key", "")
return my_dict.get("key", "")
return inner()
# Using dict.get with variable fallback that is falsy
falsy_value = None
value = my_dict.get("key", falsy_value)
value = my_dict.get("key", falsy_value)
# Using dict.get with variable fallback that is truthy
truthy_value = "exists"
value = my_dict.get("key", truthy_value)
# Using dict.get with complex expressions as fallback
value = my_dict.get("key", 0 or "default")
value = my_dict.get("key", [] if condition else {})
value = my_dict.get("key", 0 or "default")
value = my_dict.get("key", [] if condition else {})
# testing dict.get call using kwargs
value = my_dict.get(key="key", default=False)
value = my_dict.get(default=[], key="key")
value = my_dict.get(key="key", default=False)
value = my_dict.get(default=[], key="key")
# Edge Cases
@@ -93,16 +93,16 @@ value = my_dict.get(default=[], key="key")
dicts = [my_dict, my_dict, my_dict]
# Falsy fallback in a lambda
get_fallback = lambda d: d.get("key", False)
get_fallback = lambda d: d.get("key", False)
# Falsy fallback in a list comprehension
results = [d.get("key", "") for d in dicts]
results = [d.get("key", "") for d in dicts]
# Falsy fallback in a generator expression
results = (d.get("key", None) for d in dicts)
results = (d.get("key", None) for d in dicts)
# Falsy fallback in a ternary expression
value = my_dict.get("key", 0) if True else "default"
value = my_dict.get("key", 0) if True else "default"
# Falsy fallback with inline comment
@@ -185,7 +185,3 @@ not my_dict.get(
# TypeError is raised at runtime before and after the fix, but we still bail
# out for having an unrecognized number of arguments
not my_dict.get("key", False, foo=...)
# https://github.com/astral-sh/ruff/issues/18798
d = {}
not d.get("key", (False))

View File

@@ -1,18 +0,0 @@
# RUF063
# Cases that should trigger the violation
foo.__dict__.get("__annotations__") # RUF063
foo.__dict__.get("__annotations__", None) # RUF063
foo.__dict__.get("__annotations__", {}) # RUF063
foo.__dict__["__annotations__"] # RUF063
# Cases that should NOT trigger the violation
foo.__dict__.get("not__annotations__")
foo.__dict__.get("not__annotations__", None)
foo.__dict__.get("not__annotations__", {})
foo.__dict__["not__annotations__"]
foo.__annotations__
foo.get("__annotations__")
foo.get("__annotations__", None)
foo.get("__annotations__", {})

View File

@@ -1,53 +0,0 @@
import dbm.gnu
import dbm.ndbm
import os
from pathlib import Path
os.chmod("foo", 444) # Error
os.chmod("foo", 0o444) # OK
os.chmod("foo", 7777) # Error
os.chmod("foo", 10000) # Error
os.chmod("foo", 99999) # Error
os.umask(777) # Error
os.umask(0o777) # OK
os.fchmod(0, 400) # Error
os.fchmod(0, 0o400) # OK
os.lchmod("foo", 755) # Error
os.lchmod("foo", 0o755) # OK
os.mkdir("foo", 600) # Error
os.mkdir("foo", 0o600) # OK
os.makedirs("foo", 644) # Error
os.makedirs("foo", 0o644) # OK
os.mkfifo("foo", 640) # Error
os.mkfifo("foo", 0o640) # OK
os.mknod("foo", 660) # Error
os.mknod("foo", 0o660) # OK
os.open("foo", os.O_CREAT, 644) # Error
os.open("foo", os.O_CREAT, 0o644) # OK
Path("bar").chmod(755) # Error
Path("bar").chmod(0o755) # OK
path = Path("bar")
path.chmod(755) # Error
path.chmod(0o755) # OK
dbm.open("db", "r", 600) # Error
dbm.open("db", "r", 0o600) # OK
dbm.gnu.open("db", "r", 600) # Error
dbm.gnu.open("db", "r", 0o600) # OK
dbm.ndbm.open("db", "r", 600) # Error
dbm.ndbm.open("db", "r", 0o600) # OK
os.fchmod(0, 256) # 0o400
os.fchmod(0, 493) # 0o755

View File

@@ -10,7 +10,7 @@ use crate::rules::{
/// Run lint rules over the [`Binding`]s.
pub(crate) fn bindings(checker: &Checker) {
if !checker.any_rule_enabled(&[
if !checker.any_enabled(&[
Rule::AssignmentInAssert,
Rule::InvalidAllFormat,
Rule::InvalidAllObject,
@@ -30,11 +30,11 @@ pub(crate) fn bindings(checker: &Checker) {
}
for (binding_id, binding) in checker.semantic.bindings.iter_enumerated() {
if checker.is_rule_enabled(Rule::UnusedVariable) {
if checker.enabled(Rule::UnusedVariable) {
if binding.kind.is_bound_exception()
&& binding.is_unused()
&& !checker
.settings()
.settings
.dummy_variable_rgx
.is_match(binding.name(checker.source()))
{
@@ -54,47 +54,47 @@ pub(crate) fn bindings(checker: &Checker) {
});
}
}
if checker.is_rule_enabled(Rule::InvalidAllFormat) {
if checker.enabled(Rule::InvalidAllFormat) {
pylint::rules::invalid_all_format(checker, binding);
}
if checker.is_rule_enabled(Rule::InvalidAllObject) {
if checker.enabled(Rule::InvalidAllObject) {
pylint::rules::invalid_all_object(checker, binding);
}
if checker.is_rule_enabled(Rule::NonAsciiName) {
if checker.enabled(Rule::NonAsciiName) {
pylint::rules::non_ascii_name(checker, binding);
}
if checker.is_rule_enabled(Rule::UnconventionalImportAlias) {
if checker.enabled(Rule::UnconventionalImportAlias) {
flake8_import_conventions::rules::unconventional_import_alias(
checker,
binding,
&checker.settings().flake8_import_conventions.aliases,
&checker.settings.flake8_import_conventions.aliases,
);
}
if checker.is_rule_enabled(Rule::UnaliasedCollectionsAbcSetImport) {
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding);
}
if !checker.source_type.is_stub() && checker.is_rule_enabled(Rule::UnquotedTypeAlias) {
if !checker.source_type.is_stub() && checker.enabled(Rule::UnquotedTypeAlias) {
flake8_type_checking::rules::unquoted_type_alias(checker, binding);
}
if checker.is_rule_enabled(Rule::UnsortedDunderSlots) {
if checker.enabled(Rule::UnsortedDunderSlots) {
ruff::rules::sort_dunder_slots(checker, binding);
}
if checker.is_rule_enabled(Rule::UsedDummyVariable) {
if checker.enabled(Rule::UsedDummyVariable) {
ruff::rules::used_dummy_variable(checker, binding, binding_id);
}
if checker.is_rule_enabled(Rule::AssignmentInAssert) {
if checker.enabled(Rule::AssignmentInAssert) {
ruff::rules::assignment_in_assert(checker, binding);
}
if checker.is_rule_enabled(Rule::PytestUnittestRaisesAssertion) {
if checker.enabled(Rule::PytestUnittestRaisesAssertion) {
flake8_pytest_style::rules::unittest_raises_assertion_binding(checker, binding);
}
if checker.is_rule_enabled(Rule::ForLoopWrites) {
if checker.enabled(Rule::ForLoopWrites) {
refurb::rules::for_loop_writes_binding(checker, binding);
}
if checker.is_rule_enabled(Rule::CustomTypeVarForSelf) {
if checker.enabled(Rule::CustomTypeVarForSelf) {
flake8_pyi::rules::custom_type_var_instead_of_self(checker, binding);
}
if checker.is_rule_enabled(Rule::PrivateTypeParameter) {
if checker.enabled(Rule::PrivateTypeParameter) {
pyupgrade::rules::private_type_parameter(checker, binding);
}
}

View File

@@ -6,10 +6,10 @@ use crate::rules::{flake8_simplify, refurb};
/// Run lint rules over a [`Comprehension`] syntax nodes.
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &Checker) {
if checker.is_rule_enabled(Rule::InDictKeys) {
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
}
if checker.is_rule_enabled(Rule::ReadlinesInFor) {
if checker.enabled(Rule::ReadlinesInFor) {
refurb::rules::readlines_in_comprehension(checker, comprehension);
}
}

View File

@@ -14,31 +14,31 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
let Stmt::For(stmt_for) = checker.semantic.current_statement() else {
unreachable!("Expected Stmt::For");
};
if checker.is_rule_enabled(Rule::UnusedLoopControlVariable) {
if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::IncorrectDictIterator) {
if checker.enabled(Rule::IncorrectDictIterator) {
perflint::rules::incorrect_dict_iterator(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::YieldInForLoop) {
if checker.enabled(Rule::YieldInForLoop) {
pyupgrade::rules::yield_in_for_loop(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::UnnecessaryEnumerate) {
if checker.enabled(Rule::UnnecessaryEnumerate) {
refurb::rules::unnecessary_enumerate(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::EnumerateForLoop) {
if checker.enabled(Rule::EnumerateForLoop) {
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::LoopIteratorMutation) {
if checker.enabled(Rule::LoopIteratorMutation) {
flake8_bugbear::rules::loop_iterator_mutation(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::DictIndexMissingItems) {
if checker.enabled(Rule::DictIndexMissingItems) {
pylint::rules::dict_index_missing_items(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::ManualDictComprehension) {
if checker.enabled(Rule::ManualDictComprehension) {
perflint::rules::manual_dict_comprehension(checker, stmt_for);
}
if checker.is_rule_enabled(Rule::ManualListComprehension) {
if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, stmt_for);
}
}

View File

@@ -15,13 +15,13 @@ pub(crate) fn deferred_lambdas(checker: &mut Checker) {
unreachable!("Expected Expr::Lambda");
};
if checker.is_rule_enabled(Rule::UnnecessaryLambda) {
if checker.enabled(Rule::UnnecessaryLambda) {
pylint::rules::unnecessary_lambda(checker, lambda);
}
if checker.is_rule_enabled(Rule::ReimplementedContainerBuiltin) {
if checker.enabled(Rule::ReimplementedContainerBuiltin) {
flake8_pie::rules::reimplemented_container_builtin(checker, lambda);
}
if checker.is_rule_enabled(Rule::BuiltinLambdaArgumentShadowing) {
if checker.enabled(Rule::BuiltinLambdaArgumentShadowing) {
flake8_builtins::rules::builtin_lambda_argument_shadowing(checker, lambda);
}
}

View File

@@ -1,7 +1,12 @@
use ruff_python_semantic::{Binding, ScopeKind};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind};
use ruff_text_size::Ranged;
use rustc_hash::FxHashMap;
use crate::Fix;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::fix;
use crate::rules::{
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
pyflakes, pylint, ruff,
@@ -9,7 +14,7 @@ use crate::rules::{
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
pub(crate) fn deferred_scopes(checker: &Checker) {
if !checker.any_rule_enabled(&[
if !checker.any_enabled(&[
Rule::AsyncioDanglingTask,
Rule::BadStaticmethodArgument,
Rule::BuiltinAttributeShadowing,
@@ -53,7 +58,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
// used at runtime, then by default, we avoid flagging any other
// imports from that model as typing-only.
let enforce_typing_only_imports = !checker.source_type.is_stub()
&& checker.any_rule_enabled(&[
&& checker.any_enabled(&[
Rule::TypingOnlyFirstPartyImport,
Rule::TypingOnlyStandardLibraryImport,
Rule::TypingOnlyThirdPartyImport,
@@ -71,7 +76,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
flake8_type_checking::helpers::is_valid_runtime_import(
binding,
&checker.semantic,
&checker.settings().flake8_type_checking,
&checker.settings.flake8_type_checking,
)
})
.collect()
@@ -84,66 +89,307 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
for scope_id in checker.analyze.scopes.iter().rev().copied() {
let scope = &checker.semantic.scopes[scope_id];
if checker.is_rule_enabled(Rule::UndefinedLocal) {
if checker.enabled(Rule::UndefinedLocal) {
pyflakes::rules::undefined_local(checker, scope_id, scope);
}
if checker.is_rule_enabled(Rule::GlobalVariableNotAssigned) {
pylint::rules::global_variable_not_assigned(checker, scope);
if checker.enabled(Rule::GlobalVariableNotAssigned) {
for (name, binding_id) in scope.bindings() {
let binding = checker.semantic.binding(binding_id);
// If the binding is a `global`, then it's a top-level `global` that was never
// assigned in the current scope. If it were assigned, the `global` would be
// shadowed by the assignment.
if binding.kind.is_global() {
// If the binding was conditionally deleted, it will include a reference within
// a `Del` context, but won't be shadowed by a `BindingKind::Deletion`, as in:
// ```python
// if condition:
// del var
// ```
if binding
.references
.iter()
.map(|id| checker.semantic.reference(*id))
.all(ResolvedReference::is_load)
{
checker.report_diagnostic(
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
binding.range(),
);
}
}
}
}
if checker.is_rule_enabled(Rule::RedefinedArgumentFromLocal) {
pylint::rules::redefined_argument_from_local(checker, scope_id, scope);
if checker.enabled(Rule::RedefinedArgumentFromLocal) {
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
let binding = &checker.semantic.bindings[shadow.binding_id()];
if !matches!(
binding.kind,
BindingKind::LoopVar
| BindingKind::BoundException
| BindingKind::WithItemVar
) {
continue;
}
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if !shadowed.kind.is_argument() {
continue;
}
if checker.settings.dummy_variable_rgx.is_match(name) {
continue;
}
let scope = &checker.semantic.scopes[binding.scope];
if scope.kind.is_generator() {
continue;
}
checker.report_diagnostic(
pylint::rules::RedefinedArgumentFromLocal {
name: name.to_string(),
},
binding.range(),
);
}
}
}
if checker.is_rule_enabled(Rule::ImportShadowedByLoopVar) {
pyflakes::rules::import_shadowed_by_loop_var(checker, scope_id, scope);
if checker.enabled(Rule::ImportShadowedByLoopVar) {
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding isn't a loop variable, abort.
let binding = &checker.semantic.bindings[shadow.binding_id()];
if !binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding isn't an import, abort.
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic.same_branch(left, right))
}) {
continue;
}
checker.report_diagnostic(
pyflakes::rules::ImportShadowedByLoopVar {
name: name.to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
}
}
}
if checker.is_rule_enabled(Rule::RedefinedWhileUnused) {
pyflakes::rules::redefined_while_unused(checker, scope_id, scope);
if checker.enabled(Rule::RedefinedWhileUnused) {
// Index the redefined bindings by statement.
let mut redefinitions = FxHashMap::default();
for (name, binding_id) in scope.bindings() {
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
// If the shadowing binding is a loop variable, abort, to avoid overlap
// with F402.
let binding = &checker.semantic.bindings[shadow.binding_id()];
if binding.kind.is_loop_var() {
continue;
}
// If the shadowed binding is used, abort.
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
if shadowed.is_used() {
continue;
}
// If the shadowing binding isn't considered a "redefinition" of the
// shadowed binding, abort.
if !binding.redefines(shadowed) {
continue;
}
if shadow.same_scope() {
// If the symbol is a dummy variable, abort, unless the shadowed
// binding is an import.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) && checker.settings.dummy_variable_rgx.is_match(name)
{
continue;
}
let Some(node_id) = shadowed.source else {
continue;
};
// If this is an overloaded function, abort.
if shadowed.kind.is_function_definition() {
if checker
.semantic
.statement(node_id)
.as_function_def_stmt()
.is_some_and(|function| {
visibility::is_overload(
&function.decorator_list,
&checker.semantic,
)
})
{
continue;
}
}
} else {
// Only enforce cross-scope shadowing for imports.
if !matches!(
shadowed.kind,
BindingKind::Import(..)
| BindingKind::FromImport(..)
| BindingKind::SubmoduleImport(..)
| BindingKind::FutureImport
) {
continue;
}
}
// If the bindings are in different forks, abort.
if shadowed.source.is_none_or(|left| {
binding
.source
.is_none_or(|right| !checker.semantic.same_branch(left, right))
}) {
continue;
}
redefinitions
.entry(binding.source)
.or_insert_with(Vec::new)
.push((shadowed, binding));
}
}
// Create a fix for each source statement.
let mut fixes = FxHashMap::default();
for (source, entries) in &redefinitions {
let Some(source) = source else {
continue;
};
let member_names = entries
.iter()
.filter_map(|(shadowed, binding)| {
if let Some(shadowed_import) = shadowed.as_any_import() {
if let Some(import) = binding.as_any_import() {
if shadowed_import.qualified_name() == import.qualified_name() {
return Some(import.member_name());
}
}
}
None
})
.collect::<Vec<_>>();
if !member_names.is_empty() {
let statement = checker.semantic.statement(*source);
let parent = checker.semantic.parent_statement(*source);
let Ok(edit) = fix::edits::remove_unused_imports(
member_names.iter().map(std::convert::AsRef::as_ref),
statement,
parent,
checker.locator(),
checker.stylist(),
checker.indexer(),
) else {
continue;
};
fixes.insert(
*source,
Fix::safe_edit(edit).isolate(Checker::isolation(
checker.semantic().parent_statement_id(*source),
)),
);
}
}
// Create diagnostics for each statement.
for (source, entries) in &redefinitions {
for (shadowed, binding) in entries {
let mut diagnostic = checker.report_diagnostic(
pyflakes::rules::RedefinedWhileUnused {
name: binding.name(checker.source()).to_string(),
row: checker.compute_source_row(shadowed.start()),
},
binding.range(),
);
if let Some(range) = binding.parent_range(&checker.semantic) {
diagnostic.set_parent(range.start());
}
if let Some(fix) = source.as_ref().and_then(|source| fixes.get(source)) {
diagnostic.set_fix(fix.clone());
}
}
}
}
if checker.source_type.is_stub()
|| matches!(scope.kind, ScopeKind::Module | ScopeKind::Function(_))
{
if checker.is_rule_enabled(Rule::UnusedPrivateTypeVar) {
if checker.enabled(Rule::UnusedPrivateTypeVar) {
flake8_pyi::rules::unused_private_type_var(checker, scope);
}
if checker.is_rule_enabled(Rule::UnusedPrivateProtocol) {
if checker.enabled(Rule::UnusedPrivateProtocol) {
flake8_pyi::rules::unused_private_protocol(checker, scope);
}
if checker.is_rule_enabled(Rule::UnusedPrivateTypeAlias) {
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
flake8_pyi::rules::unused_private_type_alias(checker, scope);
}
if checker.is_rule_enabled(Rule::UnusedPrivateTypedDict) {
if checker.enabled(Rule::UnusedPrivateTypedDict) {
flake8_pyi::rules::unused_private_typed_dict(checker, scope);
}
}
if checker.is_rule_enabled(Rule::AsyncioDanglingTask) {
if checker.enabled(Rule::AsyncioDanglingTask) {
ruff::rules::asyncio_dangling_binding(scope, checker);
}
if let Some(class_def) = scope.kind.as_class() {
if checker.is_rule_enabled(Rule::BuiltinAttributeShadowing) {
if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_attribute_shadowing(
checker, scope_id, scope, class_def,
);
}
if checker.is_rule_enabled(Rule::FunctionCallInDataclassDefaultArgument) {
if checker.enabled(Rule::FunctionCallInDataclassDefaultArgument) {
ruff::rules::function_call_in_dataclass_default(checker, class_def);
}
if checker.is_rule_enabled(Rule::MutableClassDefault) {
if checker.enabled(Rule::MutableClassDefault) {
ruff::rules::mutable_class_default(checker, class_def);
}
if checker.is_rule_enabled(Rule::MutableDataclassDefault) {
if checker.enabled(Rule::MutableDataclassDefault) {
ruff::rules::mutable_dataclass_default(checker, class_def);
}
}
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
if checker.any_rule_enabled(&[Rule::UnusedVariable, Rule::UnusedUnpackedVariable])
if checker.any_enabled(&[Rule::UnusedVariable, Rule::UnusedUnpackedVariable])
&& !(scope.uses_locals() && scope.kind.is_function())
{
let unused_bindings = scope
@@ -156,7 +402,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
&& binding.is_unused()
&& !binding.is_nonlocal()
&& !binding.is_global()
&& !checker.settings().dummy_variable_rgx.is_match(name)
&& !checker.settings.dummy_variable_rgx.is_match(name)
&& !matches!(
name,
"__tracebackhide__"
@@ -172,22 +418,22 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
});
for (unused_name, unused_binding) in unused_bindings {
if checker.is_rule_enabled(Rule::UnusedVariable) {
if checker.enabled(Rule::UnusedVariable) {
pyflakes::rules::unused_variable(checker, unused_name, unused_binding);
}
if checker.is_rule_enabled(Rule::UnusedUnpackedVariable) {
if checker.enabled(Rule::UnusedUnpackedVariable) {
ruff::rules::unused_unpacked_variable(checker, unused_name, unused_binding);
}
}
}
if checker.is_rule_enabled(Rule::UnusedAnnotation) {
if checker.enabled(Rule::UnusedAnnotation) {
pyflakes::rules::unused_annotation(checker, scope);
}
if !checker.source_type.is_stub() {
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::UnusedClassMethodArgument,
Rule::UnusedFunctionArgument,
Rule::UnusedLambdaArgument,
@@ -201,7 +447,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if !checker.source_type.is_stub()
&& checker.is_rule_enabled(Rule::RuntimeImportInTypeCheckingBlock)
&& checker.enabled(Rule::RuntimeImportInTypeCheckingBlock)
{
flake8_type_checking::rules::runtime_import_in_type_checking_block(checker, scope);
}
@@ -221,37 +467,37 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
);
}
if checker.is_rule_enabled(Rule::UnusedImport) {
if checker.enabled(Rule::UnusedImport) {
pyflakes::rules::unused_import(checker, scope);
}
if checker.is_rule_enabled(Rule::ImportPrivateName) {
if checker.enabled(Rule::ImportPrivateName) {
pylint::rules::import_private_name(checker, scope);
}
}
if scope.kind.is_function() {
if checker.is_rule_enabled(Rule::NoSelfUse) {
if checker.enabled(Rule::NoSelfUse) {
pylint::rules::no_self_use(checker, scope_id, scope);
}
if checker.is_rule_enabled(Rule::TooManyLocals) {
if checker.enabled(Rule::TooManyLocals) {
pylint::rules::too_many_locals(checker, scope);
}
if checker.is_rule_enabled(Rule::SingledispatchMethod) {
if checker.enabled(Rule::SingledispatchMethod) {
pylint::rules::singledispatch_method(checker, scope);
}
if checker.is_rule_enabled(Rule::SingledispatchmethodFunction) {
if checker.enabled(Rule::SingledispatchmethodFunction) {
pylint::rules::singledispatchmethod_function(checker, scope);
}
if checker.is_rule_enabled(Rule::BadStaticmethodArgument) {
if checker.enabled(Rule::BadStaticmethodArgument) {
pylint::rules::bad_staticmethod_argument(checker, scope);
}
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::InvalidFirstArgumentNameForClassMethod,
Rule::InvalidFirstArgumentNameForMethod,
]) {

View File

@@ -17,7 +17,7 @@ use crate::{docstrings, warn_user};
/// it is expected that all [`Definition`] nodes have been visited by the time, and that this
/// method will not recurse into any other nodes.
pub(crate) fn definitions(checker: &mut Checker) {
let enforce_annotations = checker.any_rule_enabled(&[
let enforce_annotations = checker.any_enabled(&[
Rule::AnyType,
Rule::MissingReturnTypeClassMethod,
Rule::MissingReturnTypePrivateFunction,
@@ -28,11 +28,10 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::MissingTypeFunctionArgument,
Rule::MissingTypeKwargs,
]);
let enforce_stubs =
checker.source_type.is_stub() && checker.is_rule_enabled(Rule::DocstringInStub);
let enforce_stubs_and_runtime = checker.is_rule_enabled(Rule::IterMethodReturnIterable);
let enforce_dunder_method = checker.is_rule_enabled(Rule::BadDunderMethodName);
let enforce_docstrings = checker.any_rule_enabled(&[
let enforce_stubs = checker.source_type.is_stub() && checker.enabled(Rule::DocstringInStub);
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
let enforce_dunder_method = checker.enabled(Rule::BadDunderMethodName);
let enforce_docstrings = checker.any_enabled(&[
Rule::MissingBlankLineAfterLastSection,
Rule::MissingBlankLineAfterSummary,
Rule::BlankLineBeforeClass,
@@ -80,7 +79,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::UndocumentedPublicNestedClass,
Rule::UndocumentedPublicPackage,
]);
let enforce_pydoclint = checker.any_rule_enabled(&[
let enforce_pydoclint = checker.any_enabled(&[
Rule::DocstringMissingReturns,
Rule::DocstringExtraneousReturns,
Rule::DocstringMissingYields,
@@ -167,7 +166,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
if enforce_docstrings || enforce_pydoclint {
if pydocstyle::helpers::should_ignore_definition(
definition,
&checker.settings().pydocstyle,
&checker.settings.pydocstyle,
&checker.semantic,
) {
continue;
@@ -203,76 +202,74 @@ pub(crate) fn definitions(checker: &mut Checker) {
if !pydocstyle::rules::not_empty(checker, &docstring) {
continue;
}
if checker.is_rule_enabled(Rule::UnnecessaryMultilineDocstring) {
if checker.enabled(Rule::UnnecessaryMultilineDocstring) {
pydocstyle::rules::one_liner(checker, &docstring);
}
if checker
.any_rule_enabled(&[Rule::BlankLineAfterFunction, Rule::BlankLineBeforeFunction])
{
if checker.any_enabled(&[Rule::BlankLineAfterFunction, Rule::BlankLineBeforeFunction]) {
pydocstyle::rules::blank_before_after_function(checker, &docstring);
}
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::BlankLineBeforeClass,
Rule::IncorrectBlankLineAfterClass,
Rule::IncorrectBlankLineBeforeClass,
]) {
pydocstyle::rules::blank_before_after_class(checker, &docstring);
}
if checker.is_rule_enabled(Rule::MissingBlankLineAfterSummary) {
if checker.enabled(Rule::MissingBlankLineAfterSummary) {
pydocstyle::rules::blank_after_summary(checker, &docstring);
}
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::DocstringTabIndentation,
Rule::OverIndentation,
Rule::UnderIndentation,
]) {
pydocstyle::rules::indent(checker, &docstring);
}
if checker.is_rule_enabled(Rule::NewLineAfterLastParagraph) {
if checker.enabled(Rule::NewLineAfterLastParagraph) {
pydocstyle::rules::newline_after_last_paragraph(checker, &docstring);
}
if checker.is_rule_enabled(Rule::SurroundingWhitespace) {
if checker.enabled(Rule::SurroundingWhitespace) {
pydocstyle::rules::no_surrounding_whitespace(checker, &docstring);
}
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::MultiLineSummaryFirstLine,
Rule::MultiLineSummarySecondLine,
]) {
pydocstyle::rules::multi_line_summary_start(checker, &docstring);
}
if checker.is_rule_enabled(Rule::TripleSingleQuotes) {
if checker.enabled(Rule::TripleSingleQuotes) {
pydocstyle::rules::triple_quotes(checker, &docstring);
}
if checker.is_rule_enabled(Rule::EscapeSequenceInDocstring) {
if checker.enabled(Rule::EscapeSequenceInDocstring) {
pydocstyle::rules::backslashes(checker, &docstring);
}
if checker.is_rule_enabled(Rule::MissingTrailingPeriod) {
if checker.enabled(Rule::MissingTrailingPeriod) {
pydocstyle::rules::ends_with_period(checker, &docstring);
}
if checker.is_rule_enabled(Rule::NonImperativeMood) {
if checker.enabled(Rule::NonImperativeMood) {
pydocstyle::rules::non_imperative_mood(
checker,
&docstring,
&checker.settings().pydocstyle,
&checker.settings.pydocstyle,
);
}
if checker.is_rule_enabled(Rule::SignatureInDocstring) {
if checker.enabled(Rule::SignatureInDocstring) {
pydocstyle::rules::no_signature(checker, &docstring);
}
if checker.is_rule_enabled(Rule::FirstWordUncapitalized) {
if checker.enabled(Rule::FirstWordUncapitalized) {
pydocstyle::rules::capitalized(checker, &docstring);
}
if checker.is_rule_enabled(Rule::DocstringStartsWithThis) {
if checker.enabled(Rule::DocstringStartsWithThis) {
pydocstyle::rules::starts_with_this(checker, &docstring);
}
if checker.is_rule_enabled(Rule::MissingTerminalPunctuation) {
if checker.enabled(Rule::MissingTerminalPunctuation) {
pydocstyle::rules::ends_with_punctuation(checker, &docstring);
}
if checker.is_rule_enabled(Rule::OverloadWithDocstring) {
if checker.enabled(Rule::OverloadWithDocstring) {
pydocstyle::rules::if_needed(checker, &docstring);
}
let enforce_sections = checker.any_rule_enabled(&[
let enforce_sections = checker.any_enabled(&[
Rule::MissingBlankLineAfterLastSection,
Rule::BlankLinesBetweenHeaderAndContent,
Rule::NonCapitalizedSectionName,
@@ -292,7 +289,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
if enforce_sections || enforce_pydoclint {
let section_contexts = pydocstyle::helpers::get_section_contexts(
&docstring,
checker.settings().pydocstyle.convention(),
checker.settings.pydocstyle.convention(),
);
if enforce_sections {
@@ -300,7 +297,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
checker,
&docstring,
&section_contexts,
checker.settings().pydocstyle.convention(),
checker.settings.pydocstyle.convention(),
);
}
@@ -310,7 +307,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
definition,
&docstring,
&section_contexts,
checker.settings().pydocstyle.convention(),
checker.settings.pydocstyle.convention(),
);
}
}

View File

@@ -17,17 +17,17 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker)
range: _,
node_index: _,
}) => {
if checker.is_rule_enabled(Rule::BareExcept) {
if checker.enabled(Rule::BareExcept) {
pycodestyle::rules::bare_except(checker, type_.as_deref(), body, except_handler);
}
if checker.is_rule_enabled(Rule::RaiseWithoutFromInsideExcept) {
if checker.enabled(Rule::RaiseWithoutFromInsideExcept) {
flake8_bugbear::rules::raise_without_from_inside_except(
checker,
name.as_deref(),
body,
);
}
if checker.is_rule_enabled(Rule::BlindExcept) {
if checker.enabled(Rule::BlindExcept) {
flake8_blind_except::rules::blind_except(
checker,
type_.as_deref(),
@@ -35,42 +35,42 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker)
body,
);
}
if checker.is_rule_enabled(Rule::TryExceptPass) {
if checker.enabled(Rule::TryExceptPass) {
flake8_bandit::rules::try_except_pass(
checker,
except_handler,
type_.as_deref(),
body,
checker.settings().flake8_bandit.check_typed_exception,
checker.settings.flake8_bandit.check_typed_exception,
);
}
if checker.is_rule_enabled(Rule::TryExceptContinue) {
if checker.enabled(Rule::TryExceptContinue) {
flake8_bandit::rules::try_except_continue(
checker,
except_handler,
type_.as_deref(),
body,
checker.settings().flake8_bandit.check_typed_exception,
checker.settings.flake8_bandit.check_typed_exception,
);
}
if checker.is_rule_enabled(Rule::ExceptWithEmptyTuple) {
if checker.enabled(Rule::ExceptWithEmptyTuple) {
flake8_bugbear::rules::except_with_empty_tuple(checker, except_handler);
}
if checker.is_rule_enabled(Rule::ExceptWithNonExceptionClasses) {
if checker.enabled(Rule::ExceptWithNonExceptionClasses) {
flake8_bugbear::rules::except_with_non_exception_classes(checker, except_handler);
}
if checker.is_rule_enabled(Rule::BinaryOpException) {
if checker.enabled(Rule::BinaryOpException) {
pylint::rules::binary_op_exception(checker, except_handler);
}
if let Some(name) = name {
if checker.is_rule_enabled(Rule::AmbiguousVariableName) {
if checker.enabled(Rule::AmbiguousVariableName) {
pycodestyle::rules::ambiguous_variable_name(
checker,
name.as_str(),
name.range(),
);
}
if checker.is_rule_enabled(Rule::BuiltinVariableShadowing) {
if checker.enabled(Rule::BuiltinVariableShadowing) {
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@ use crate::rules::{flake8_bugbear, ruff};
/// Run lint rules over a module.
pub(crate) fn module(suite: &Suite, checker: &Checker) {
if checker.is_rule_enabled(Rule::FStringDocstring) {
if checker.enabled(Rule::FStringDocstring) {
flake8_bugbear::rules::f_string_docstring(checker, suite);
}
if checker.is_rule_enabled(Rule::InvalidFormatterSuppressionComment) {
if checker.enabled(Rule::InvalidFormatterSuppressionComment) {
ruff::rules::ignored_formatter_suppression_comment(checker, suite);
}
}

View File

@@ -7,14 +7,14 @@ use crate::rules::{flake8_builtins, pycodestyle};
/// Run lint rules over a [`Parameter`] syntax node.
pub(crate) fn parameter(parameter: &Parameter, checker: &Checker) {
if checker.is_rule_enabled(Rule::AmbiguousVariableName) {
if checker.enabled(Rule::AmbiguousVariableName) {
pycodestyle::rules::ambiguous_variable_name(
checker,
&parameter.name,
parameter.name.range(),
);
}
if checker.is_rule_enabled(Rule::BuiltinArgumentShadowing) {
if checker.enabled(Rule::BuiltinArgumentShadowing) {
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
}
}

View File

@@ -6,17 +6,17 @@ use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
/// Run lint rules over a [`Parameters`] syntax node.
pub(crate) fn parameters(parameters: &Parameters, checker: &Checker) {
if checker.is_rule_enabled(Rule::FunctionCallInDefaultArgument) {
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
}
if checker.is_rule_enabled(Rule::ImplicitOptional) {
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
ruff::rules::implicit_optional(checker, parameters);
}
if checker.source_type.is_stub() {
if checker.is_rule_enabled(Rule::TypedArgumentDefaultInStub) {
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
}
if checker.is_rule_enabled(Rule::ArgumentDefaultInStub) {
if checker.enabled(Rule::ArgumentDefaultInStub) {
flake8_pyi::rules::argument_simple_defaults(checker, parameters);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,39 +6,37 @@ use crate::rules::{flake8_bandit, flake8_pyi, flake8_quotes, pycodestyle, ruff};
/// Run lint rules over a [`StringLike`] syntax nodes.
pub(crate) fn string_like(string_like: StringLike, checker: &Checker) {
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
]) {
ruff::rules::ambiguous_unicode_character_string(checker, string_like);
}
if checker.is_rule_enabled(Rule::HardcodedBindAllInterfaces) {
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
flake8_bandit::rules::hardcoded_bind_all_interfaces(checker, string_like);
}
if checker.is_rule_enabled(Rule::HardcodedTempFile) {
if checker.enabled(Rule::HardcodedTempFile) {
flake8_bandit::rules::hardcoded_tmp_directory(checker, string_like);
}
if checker.source_type.is_stub() {
if checker.is_rule_enabled(Rule::StringOrBytesTooLong) {
if checker.enabled(Rule::StringOrBytesTooLong) {
flake8_pyi::rules::string_or_bytes_too_long(checker, string_like);
}
}
if checker.any_rule_enabled(&[
if checker.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
]) {
flake8_quotes::rules::check_string_quotes(checker, string_like);
}
if checker.is_rule_enabled(Rule::UnnecessaryEscapedQuote) {
if checker.enabled(Rule::UnnecessaryEscapedQuote) {
flake8_quotes::rules::unnecessary_escaped_quote(checker, string_like);
}
if checker.is_rule_enabled(Rule::AvoidableEscapedQuote)
&& checker.settings().flake8_quotes.avoid_escape
{
if checker.enabled(Rule::AvoidableEscapedQuote) && checker.settings.flake8_quotes.avoid_escape {
flake8_quotes::rules::avoidable_escaped_quote(checker, string_like);
}
if checker.is_rule_enabled(Rule::InvalidEscapeSequence) {
if checker.enabled(Rule::InvalidEscapeSequence) {
pycodestyle::rules::invalid_escape_sequence(checker, string_like);
}
}

View File

@@ -7,10 +7,10 @@ use crate::rules::refurb;
/// Run lint rules over a suite of [`Stmt`] syntax nodes.
pub(crate) fn suite(suite: &[Stmt], checker: &Checker) {
if checker.is_rule_enabled(Rule::UnnecessaryPlaceholder) {
if checker.enabled(Rule::UnnecessaryPlaceholder) {
flake8_pie::rules::unnecessary_placeholder(checker, suite);
}
if checker.is_rule_enabled(Rule::RepeatedGlobal) {
if checker.enabled(Rule::RepeatedGlobal) {
refurb::rules::repeated_global(checker, suite);
}
}

View File

@@ -7,22 +7,22 @@ use crate::rules::pyflakes;
/// Run lint rules over all [`UnresolvedReference`] entities in the [`SemanticModel`].
pub(crate) fn unresolved_references(checker: &Checker) {
if !checker.any_rule_enabled(&[Rule::UndefinedLocalWithImportStarUsage, Rule::UndefinedName]) {
if !checker.any_enabled(&[Rule::UndefinedLocalWithImportStarUsage, Rule::UndefinedName]) {
return;
}
for reference in checker.semantic.unresolved_references() {
if reference.is_wildcard_import() {
// F406
checker.report_diagnostic_if_enabled(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
},
reference.range(),
);
if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) {
checker.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
},
reference.range(),
);
}
} else {
// F821
if checker.is_rule_enabled(Rule::UndefinedName) {
if checker.enabled(Rule::UndefinedName) {
if checker.semantic.in_no_type_check() {
continue;
}

View File

@@ -21,6 +21,7 @@
//! represents the lint-rule analysis phase. In the future, these steps may be separated into
//! distinct passes over the AST.
use std::borrow::Cow;
use std::cell::RefCell;
use std::path::Path;
@@ -28,7 +29,7 @@ use itertools::Itertools;
use log::debug;
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Applicability, Fix, IsolationLevel};
use ruff_diagnostics::IsolationLevel;
use ruff_notebook::{CellOffsets, NotebookIndex};
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
use ruff_python_ast::identifier::Identifier;
@@ -37,8 +38,8 @@ use ruff_python_ast::str::Quote;
use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern};
use ruff_python_ast::{
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
ExprContext, ExprFString, ExprTString, InterpolatedStringElement, Keyword, MatchCase,
ModModule, Parameter, Parameters, Pattern, PythonVersion, Stmt, Suite, UnaryOp,
ExprContext, InterpolatedStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters,
Pattern, PythonVersion, Stmt, Suite, UnaryOp,
};
use ruff_python_ast::{PySourceType, helpers, str, visitor};
use ruff_python_codegen::{Generator, Stylist};
@@ -57,7 +58,7 @@ use ruff_python_semantic::{
};
use ruff_python_stdlib::builtins::{MAGIC_GLOBALS, python_builtins};
use ruff_python_trivia::CommentRanges;
use ruff_source_file::{OneIndexed, SourceFile, SourceFileBuilder, SourceRow};
use ruff_source_file::{OneIndexed, SourceFile, SourceRow};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::annotation::AnnotationContext;
@@ -66,13 +67,12 @@ use crate::importer::{ImportRequest, Importer, ResolutionError};
use crate::noqa::NoqaMapping;
use crate::package::PackageRoot;
use crate::preview::is_undefined_export_in_dunder_init_enabled;
use crate::registry::Rule;
use crate::registry::{AsRule, Rule};
use crate::rules::pyflakes::rules::{
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
};
use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration};
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
use crate::settings::rule_table::RuleTable;
use crate::settings::{LinterSettings, TargetVersion, flags};
use crate::{Edit, OldDiagnostic, Violation};
use crate::{Locator, docstrings, noqa};
@@ -207,6 +207,8 @@ pub(crate) struct Checker<'a> {
/// The [`NoqaMapping`] for the current analysis (i.e., the mapping from line number to
/// suppression commented line number).
noqa_line_for: &'a NoqaMapping,
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
pub(crate) settings: &'a LinterSettings,
/// The [`Locator`] for the current file, which enables extraction of source code from byte
/// offsets.
locator: &'a Locator<'a>,
@@ -258,12 +260,13 @@ impl<'a> Checker<'a> {
notebook_index: Option<&'a NotebookIndex>,
target_version: TargetVersion,
context: &'a LintContext<'a>,
) -> Self {
) -> Checker<'a> {
let semantic = SemanticModel::new(&settings.typing_modules, path, module);
Self {
parsed,
parsed_type_annotation: None,
parsed_annotations_cache: ParsedAnnotationsCache::new(parsed_annotations_arena),
settings,
noqa_line_for,
noqa,
path,
@@ -321,8 +324,7 @@ impl<'a> Checker<'a> {
/// Return the preferred quote for a generated `StringLiteral` node, given where we are in the
/// AST.
fn preferred_quote(&self) -> Quote {
self.interpolated_string_quote_style()
.unwrap_or(self.stylist.quote())
self.f_string_quote_style().unwrap_or(self.stylist.quote())
}
/// Return the default string flags a generated `StringLiteral` node should use, given where we
@@ -344,27 +346,21 @@ impl<'a> Checker<'a> {
ast::FStringFlags::empty().with_quote_style(self.preferred_quote())
}
/// Returns the appropriate quoting for interpolated strings by reversing the one used outside of
/// the interpolated string.
/// Returns the appropriate quoting for f-string by reversing the one used outside of
/// the f-string.
///
/// If the current expression in the context is not an interpolated string, returns ``None``.
pub(crate) fn interpolated_string_quote_style(&self) -> Option<Quote> {
if !self.semantic.in_interpolated_string() {
/// If the current expression in the context is not an f-string, returns ``None``.
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
if !self.semantic.in_f_string() {
return None;
}
// Find the quote character used to start the containing interpolated string.
self.semantic
// Find the quote character used to start the containing f-string.
let ast::ExprFString { value, .. } = self
.semantic
.current_expressions()
.find_map(|expr| match expr {
Expr::FString(ExprFString { value, .. }) => {
Some(value.iter().next()?.quote_style().opposite())
}
Expr::TString(ExprTString { value, .. }) => {
Some(value.iter().next()?.quote_style().opposite())
}
_ => None,
})
.find_map(|expr| expr.as_f_string_expr())?;
Some(value.iter().next()?.quote_style().opposite())
}
/// Returns the [`SourceRow`] for the given offset.
@@ -388,7 +384,7 @@ impl<'a> Checker<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the checker on `Drop`.
pub(crate) fn report_diagnostic<'chk, T: Violation>(
&'chk self,
@@ -401,8 +397,8 @@ impl<'a> Checker<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
/// enabled.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the checker on `Drop`.
/// Prefer [`Checker::report_diagnostic`] in general because the conversion from a `Diagnostic`
/// to a `Rule` is somewhat expensive.
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
&'chk self,
kind: T,
@@ -461,11 +457,6 @@ impl<'a> Checker<'a> {
&self.semantic
}
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
pub(crate) const fn settings(&self) -> &'a LinterSettings {
self.context.settings
}
/// The [`Path`] to the file under analysis.
pub(crate) const fn path(&self) -> &'a Path {
self.path
@@ -483,14 +474,14 @@ impl<'a> Checker<'a> {
/// Returns whether the given rule should be checked.
#[inline]
pub(crate) const fn is_rule_enabled(&self, rule: Rule) -> bool {
self.context.is_rule_enabled(rule)
pub(crate) const fn enabled(&self, rule: Rule) -> bool {
self.settings.rules.enabled(rule)
}
/// Returns whether any of the given rules should be checked.
#[inline]
pub(crate) const fn any_rule_enabled(&self, rules: &[Rule]) -> bool {
self.context.any_rule_enabled(rules)
pub(crate) const fn any_enabled(&self, rules: &[Rule]) -> bool {
self.settings.rules.any_enabled(rules)
}
/// Returns the [`IsolationLevel`] to isolate fixes for a given node.
@@ -578,7 +569,7 @@ impl<'a> Checker<'a> {
) -> Option<TypingImporter<'b, 'a>> {
let source_module = if self.target_version() >= version_added_to_typing {
"typing"
} else if !self.settings().typing_extensions {
} else if !self.settings.typing_extensions {
return None;
} else {
"typing_extensions"
@@ -625,13 +616,16 @@ impl SemanticSyntaxContext for Checker<'_> {
fn report_semantic_error(&self, error: SemanticSyntaxError) {
match error.kind {
SemanticSyntaxErrorKind::LateFutureImport => {
// F404
if self.is_rule_enabled(Rule::LateFutureImport) {
if self.settings.rules.enabled(Rule::LateFutureImport) {
self.report_diagnostic(LateFutureImport, error.range);
}
}
SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { name, start } => {
if self.is_rule_enabled(Rule::LoadBeforeGlobalDeclaration) {
if self
.settings
.rules
.enabled(Rule::LoadBeforeGlobalDeclaration)
{
self.report_diagnostic(
LoadBeforeGlobalDeclaration {
name,
@@ -642,18 +636,17 @@ impl SemanticSyntaxContext for Checker<'_> {
}
}
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
if self.is_rule_enabled(Rule::YieldOutsideFunction) {
if self.settings.rules.enabled(Rule::YieldOutsideFunction) {
self.report_diagnostic(YieldOutsideFunction::new(kind), error.range);
}
}
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
// F706
if self.is_rule_enabled(Rule::ReturnOutsideFunction) {
if self.settings.rules.enabled(Rule::ReturnOutsideFunction) {
self.report_diagnostic(ReturnOutsideFunction, error.range);
}
}
SemanticSyntaxErrorKind::AwaitOutsideAsyncFunction(_) => {
if self.is_rule_enabled(Rule::AwaitOutsideAsync) {
if self.settings.rules.enabled(Rule::AwaitOutsideAsync) {
self.report_diagnostic(AwaitOutsideAsync, error.range);
}
}
@@ -675,8 +668,8 @@ impl SemanticSyntaxContext for Checker<'_> {
}
}
fn source(&self) -> &str {
self.source()
fn source(&self) -> Cow<'_, str> {
Cow::Borrowed(self.source())
}
fn future_annotations_or_stub(&self) -> bool {
@@ -1058,7 +1051,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
let annotation = AnnotationContext::from_function(
function_def,
&self.semantic,
self.settings(),
self.settings,
self.target_version(),
);
@@ -1260,7 +1253,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
}) => {
match AnnotationContext::from_model(
&self.semantic,
self.settings(),
self.settings,
self.target_version(),
) {
AnnotationContext::RuntimeRequired => {
@@ -1872,8 +1865,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
match typing::match_annotated_subscript(
value,
&self.semantic,
self.settings().typing_modules.iter().map(String::as_str),
&self.settings().pyflakes.extend_generics,
self.settings.typing_modules.iter().map(String::as_str),
&self.settings.pyflakes.extend_generics,
) {
// Ex) Literal["Class"]
Some(typing::SubscriptKind::Literal) => {
@@ -2365,7 +2358,7 @@ impl<'a> Checker<'a> {
fn visit_cast_type_argument(&mut self, arg: &'a Expr) {
self.visit_type_definition(arg);
if !self.source_type.is_stub() && self.is_rule_enabled(Rule::RuntimeCastValue) {
if !self.source_type.is_stub() && self.enabled(Rule::RuntimeCastValue) {
flake8_type_checking::rules::runtime_cast_value(self, arg);
}
}
@@ -2480,7 +2473,6 @@ impl<'a> Checker<'a> {
fn bind_builtins(&mut self) {
let target_version = self.target_version();
let settings = self.settings();
let mut bind_builtin = |builtin| {
// Add the builtin to the scope.
let binding_id = self.semantic.push_builtin();
@@ -2494,7 +2486,7 @@ impl<'a> Checker<'a> {
for builtin in MAGIC_GLOBALS {
bind_builtin(builtin);
}
for builtin in &settings.builtins {
for builtin in &self.settings.builtins {
bind_builtin(builtin);
}
}
@@ -2768,12 +2760,12 @@ impl<'a> Checker<'a> {
if self.semantic.in_annotation()
&& self.semantic.in_typing_only_annotation()
{
if self.is_rule_enabled(Rule::QuotedAnnotation) {
if self.enabled(Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, annotation, range);
}
}
if self.source_type.is_stub() {
if self.is_rule_enabled(Rule::QuotedAnnotationInStub) {
if self.enabled(Rule::QuotedAnnotationInStub) {
flake8_pyi::rules::quoted_annotation_in_stub(
self, annotation, range,
);
@@ -2795,9 +2787,7 @@ impl<'a> Checker<'a> {
self.visit_expr(parsed_expr);
if self.semantic.in_type_alias_value() {
// stub files are covered by PYI020
if !self.source_type.is_stub()
&& self.is_rule_enabled(Rule::QuotedTypeAlias)
{
if !self.source_type.is_stub() && self.enabled(Rule::QuotedTypeAlias) {
flake8_type_checking::rules::quoted_type_alias(
self,
parsed_expr,
@@ -2810,8 +2800,7 @@ impl<'a> Checker<'a> {
Err(parse_error) => {
self.semantic.restore(snapshot);
// F722
if self.is_rule_enabled(Rule::ForwardAnnotationSyntaxError) {
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
self.report_type_diagnostic(
pyflakes::rules::ForwardAnnotationSyntaxError {
parse_error: parse_error.error.to_string(),
@@ -2958,8 +2947,7 @@ impl<'a> Checker<'a> {
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
} else {
if self.semantic.global_scope().uses_star_imports() {
// F405
if self.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: name.to_string(),
@@ -2969,9 +2957,8 @@ impl<'a> Checker<'a> {
.set_parent(definition.start());
}
} else {
// F822
if self.is_rule_enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings())
if self.enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings)
|| !self.path.ends_with("__init__.py")
{
self.report_diagnostic(
@@ -3121,28 +3108,17 @@ pub(crate) fn check_ast(
/// a [`Violation`] to the contained [`OldDiagnostic`] collection on `Drop`.
pub(crate) struct LintContext<'a> {
diagnostics: RefCell<Vec<OldDiagnostic>>,
source_file: SourceFile,
rules: RuleTable,
source_file: &'a SourceFile,
settings: &'a LinterSettings,
}
impl<'a> LintContext<'a> {
/// Create a new collector with the given `source_file` and an empty collection of
/// `OldDiagnostic`s.
pub(crate) fn new(path: &Path, contents: &str, settings: &'a LinterSettings) -> Self {
let source_file =
SourceFileBuilder::new(path.to_string_lossy().as_ref(), contents).finish();
// Ignore diagnostics based on per-file-ignores.
let mut rules = settings.rules.clone();
for ignore in crate::fs::ignores_from_path(path, &settings.per_file_ignores) {
rules.disable(ignore);
}
pub(crate) fn new(source_file: &'a SourceFile, settings: &'a LinterSettings) -> Self {
Self {
diagnostics: RefCell::default(),
source_file,
rules,
settings,
}
}
@@ -3150,7 +3126,7 @@ impl<'a> LintContext<'a> {
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the context on `Drop`.
/// before it is added to the collection in the collector on `Drop`.
pub(crate) fn report_diagnostic<'chk, T: Violation>(
&'chk self,
kind: T,
@@ -3158,59 +3134,43 @@ impl<'a> LintContext<'a> {
) -> DiagnosticGuard<'chk, 'a> {
DiagnosticGuard {
context: self,
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
rule: T::rule(),
diagnostic: Some(OldDiagnostic::new(kind, range, self.source_file)),
}
}
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
/// enabled.
///
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
/// before it is added to the collection in the context on `Drop`.
/// Prefer [`DiagnosticsCollector::report_diagnostic`] in general because the conversion from an
/// `OldDiagnostic` to a `Rule` is somewhat expensive.
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
&'chk self,
kind: T,
range: TextRange,
) -> Option<DiagnosticGuard<'chk, 'a>> {
let rule = T::rule();
if self.is_rule_enabled(rule) {
let diagnostic = OldDiagnostic::new(kind, range, self.source_file);
if self.settings.rules.enabled(diagnostic.rule()) {
Some(DiagnosticGuard {
context: self,
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
rule,
diagnostic: Some(diagnostic),
})
} else {
None
}
}
#[inline]
pub(crate) const fn is_rule_enabled(&self, rule: Rule) -> bool {
self.rules.enabled(rule)
pub(crate) fn into_diagnostics(self) -> Vec<OldDiagnostic> {
self.diagnostics.into_inner()
}
#[inline]
pub(crate) const fn any_rule_enabled(&self, rules: &[Rule]) -> bool {
self.rules.any_enabled(rules)
pub(crate) fn is_empty(&self) -> bool {
self.diagnostics.borrow().is_empty()
}
#[inline]
pub(crate) fn iter_enabled_rules(&self) -> impl Iterator<Item = Rule> + '_ {
self.rules.iter_enabled()
}
#[inline]
pub(crate) fn into_parts(self) -> (Vec<OldDiagnostic>, SourceFile) {
(self.diagnostics.into_inner(), self.source_file)
}
#[inline]
pub(crate) fn as_mut_vec(&mut self) -> &mut Vec<OldDiagnostic> {
self.diagnostics.get_mut()
}
#[inline]
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &OldDiagnostic> {
self.diagnostics.get_mut().iter()
}
@@ -3230,7 +3190,6 @@ pub(crate) struct DiagnosticGuard<'a, 'b> {
///
/// This is always `Some` until the `Drop` (or `defuse`) call.
diagnostic: Option<OldDiagnostic>,
rule: Rule,
}
impl DiagnosticGuard<'_, '_> {
@@ -3243,50 +3202,6 @@ impl DiagnosticGuard<'_, '_> {
}
}
impl DiagnosticGuard<'_, '_> {
fn resolve_applicability(&self, fix: &Fix) -> Applicability {
self.context
.settings
.fix_safety
.resolve_applicability(self.rule, fix.applicability())
}
/// Set the [`Fix`] used to fix the diagnostic.
#[inline]
pub(crate) fn set_fix(&mut self, fix: Fix) {
if !self.context.rules.should_fix(self.rule) {
self.fix = None;
return;
}
let applicability = self.resolve_applicability(&fix);
self.fix = Some(fix.with_applicability(applicability));
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub(crate) fn try_set_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Fix>) {
match func() {
Ok(fix) => self.set_fix(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub(crate) fn try_set_optional_fix(
&mut self,
func: impl FnOnce() -> anyhow::Result<Option<Fix>>,
) {
match func() {
Ok(None) => {}
Ok(Some(fix)) => self.set_fix(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
}
impl std::ops::Deref for DiagnosticGuard<'_, '_> {
type Target = OldDiagnostic;

View File

@@ -23,7 +23,7 @@ pub(crate) fn check_file_path(
context: &LintContext,
) {
// flake8-no-pep420
if context.is_rule_enabled(Rule::ImplicitNamespacePackage) {
if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
let allow_nested_roots = is_allow_nested_roots_enabled(settings);
implicit_namespace_package(
path,
@@ -38,12 +38,12 @@ pub(crate) fn check_file_path(
}
// pep8-naming
if context.is_rule_enabled(Rule::InvalidModuleName) {
if settings.rules.enabled(Rule::InvalidModuleName) {
invalid_module_name(path, package, &settings.pep8_naming.ignore_names, context);
}
// flake8-builtins
if context.is_rule_enabled(Rule::StdlibModuleShadowing) {
if settings.rules.enabled(Rule::StdlibModuleShadowing) {
stdlib_module_shadowing(path, settings, target_version, context);
}
}

View File

@@ -42,7 +42,7 @@ pub(crate) fn check_imports(
let blocks: Vec<&Block> = tracker.iter().collect();
// Enforce import rules.
if context.is_rule_enabled(Rule::UnsortedImports) {
if settings.rules.enabled(Rule::UnsortedImports) {
for block in &blocks {
if !block.imports.is_empty() {
isort::rules::organize_imports(
@@ -60,7 +60,7 @@ pub(crate) fn check_imports(
}
}
}
if context.is_rule_enabled(Rule::MissingRequiredImport) {
if settings.rules.enabled(Rule::MissingRequiredImport) {
isort::rules::add_required_imports(
parsed,
locator,

View File

@@ -47,48 +47,49 @@ pub(crate) fn check_logical_lines(
let mut prev_indent_level = None;
let indent_char = stylist.indentation().as_char();
let enforce_space_around_operator = context.any_rule_enabled(&[
let enforce_space_around_operator = settings.rules.any_enabled(&[
Rule::MultipleSpacesBeforeOperator,
Rule::MultipleSpacesAfterOperator,
Rule::TabBeforeOperator,
Rule::TabAfterOperator,
]);
let enforce_whitespace_around_named_parameter_equals = context.any_rule_enabled(&[
let enforce_whitespace_around_named_parameter_equals = settings.rules.any_enabled(&[
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
Rule::MissingWhitespaceAroundParameterEquals,
]);
let enforce_missing_whitespace_around_operator = context.any_rule_enabled(&[
let enforce_missing_whitespace_around_operator = settings.rules.any_enabled(&[
Rule::MissingWhitespaceAroundOperator,
Rule::MissingWhitespaceAroundArithmeticOperator,
Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
Rule::MissingWhitespaceAroundModuloOperator,
]);
let enforce_missing_whitespace = context.is_rule_enabled(Rule::MissingWhitespace);
let enforce_space_after_comma =
context.any_rule_enabled(&[Rule::MultipleSpacesAfterComma, Rule::TabAfterComma]);
let enforce_extraneous_whitespace = context.any_rule_enabled(&[
let enforce_missing_whitespace = settings.rules.enabled(Rule::MissingWhitespace);
let enforce_space_after_comma = settings
.rules
.any_enabled(&[Rule::MultipleSpacesAfterComma, Rule::TabAfterComma]);
let enforce_extraneous_whitespace = settings.rules.any_enabled(&[
Rule::WhitespaceAfterOpenBracket,
Rule::WhitespaceBeforeCloseBracket,
Rule::WhitespaceBeforePunctuation,
]);
let enforce_whitespace_around_keywords = context.any_rule_enabled(&[
let enforce_whitespace_around_keywords = settings.rules.any_enabled(&[
Rule::MultipleSpacesAfterKeyword,
Rule::MultipleSpacesBeforeKeyword,
Rule::TabAfterKeyword,
Rule::TabBeforeKeyword,
]);
let enforce_missing_whitespace_after_keyword =
context.is_rule_enabled(Rule::MissingWhitespaceAfterKeyword);
let enforce_whitespace_before_comment = context.any_rule_enabled(&[
settings.rules.enabled(Rule::MissingWhitespaceAfterKeyword);
let enforce_whitespace_before_comment = settings.rules.any_enabled(&[
Rule::TooFewSpacesBeforeInlineComment,
Rule::NoSpaceAfterInlineComment,
Rule::NoSpaceAfterBlockComment,
Rule::MultipleLeadingHashesForBlockComment,
]);
let enforce_whitespace_before_parameters =
context.is_rule_enabled(Rule::WhitespaceBeforeParameters);
let enforce_redundant_backslash = context.is_rule_enabled(Rule::RedundantBackslash);
let enforce_indentation = context.any_rule_enabled(&[
settings.rules.enabled(Rule::WhitespaceBeforeParameters);
let enforce_redundant_backslash = settings.rules.enabled(Rule::RedundantBackslash);
let enforce_indentation = settings.rules.any_enabled(&[
Rule::IndentationWithInvalidMultiple,
Rule::NoIndentedBlock,
Rule::UnexpectedIndentation,

View File

@@ -12,7 +12,7 @@ use crate::fix::edits::delete_comment;
use crate::noqa::{
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
};
use crate::registry::Rule;
use crate::registry::{AsRule, Rule, RuleSet};
use crate::rule_redirects::get_redirect_target;
use crate::rules::pygrep_hooks;
use crate::rules::ruff;
@@ -22,7 +22,7 @@ use crate::{Edit, Fix, Locator};
use super::ast::LintContext;
/// RUF100
#[expect(clippy::too_many_arguments)]
pub(crate) fn check_noqa(
context: &mut LintContext,
path: &Path,
@@ -30,6 +30,7 @@ pub(crate) fn check_noqa(
comment_ranges: &CommentRanges,
noqa_line_for: &NoqaMapping,
analyze_directives: bool,
per_file_ignores: &RuleSet,
settings: &LinterSettings,
) -> Vec<usize> {
// Identify any codes that are globally exempted (within the current file).
@@ -46,15 +47,14 @@ pub(crate) fn check_noqa(
// Remove any ignored diagnostics.
'outer: for (index, diagnostic) in context.iter().enumerate() {
// Can't ignore syntax errors.
let Some(code) = diagnostic.noqa_code() else {
continue;
};
let rule = diagnostic.rule();
if code == Rule::BlanketNOQA.noqa_code() {
if matches!(rule, Rule::BlanketNOQA) {
continue;
}
let code = rule.noqa_code();
match &exemption {
FileExemption::All(_) => {
// If the file is exempted, ignore all diagnostics.
@@ -106,9 +106,10 @@ pub(crate) fn check_noqa(
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
// suppressed.
if context.is_rule_enabled(Rule::UnusedNOQA)
if settings.rules.enabled(Rule::UnusedNOQA)
&& analyze_directives
&& !exemption.includes(Rule::UnusedNOQA)
&& !per_file_ignores.contains(Rule::UnusedNOQA)
{
let directives = noqa_directives
.lines()
@@ -147,9 +148,7 @@ pub(crate) fn check_noqa(
if seen_codes.insert(original_code) {
let is_code_used = if is_file_level {
context
.iter()
.any(|diag| diag.noqa_code().is_some_and(|noqa| noqa == code))
context.iter().any(|diag| diag.rule().noqa_code() == code)
} else {
matches.iter().any(|match_| *match_ == code)
} || settings
@@ -160,7 +159,7 @@ pub(crate) fn check_noqa(
if is_code_used {
valid_codes.push(original_code);
} else if let Ok(rule) = Rule::from_code(code) {
if context.is_rule_enabled(rule) {
if settings.rules.enabled(rule) {
unmatched_codes.push(original_code);
} else {
disabled_codes.push(original_code);
@@ -230,12 +229,18 @@ pub(crate) fn check_noqa(
}
}
if context.is_rule_enabled(Rule::RedirectedNOQA) && !exemption.includes(Rule::RedirectedNOQA) {
if settings.rules.enabled(Rule::RedirectedNOQA)
&& !per_file_ignores.contains(Rule::RedirectedNOQA)
&& !exemption.includes(Rule::RedirectedNOQA)
{
ruff::rules::redirected_noqa(context, &noqa_directives);
ruff::rules::redirected_file_noqa(context, &file_noqa_directives);
}
if context.is_rule_enabled(Rule::BlanketNOQA) && !exemption.enumerates(Rule::BlanketNOQA) {
if settings.rules.enabled(Rule::BlanketNOQA)
&& !per_file_ignores.contains(Rule::BlanketNOQA)
&& !exemption.enumerates(Rule::BlanketNOQA)
{
pygrep_hooks::rules::blanket_noqa(
context,
&noqa_directives,
@@ -244,7 +249,8 @@ pub(crate) fn check_noqa(
);
}
if context.is_rule_enabled(Rule::InvalidRuleCode)
if settings.rules.enabled(Rule::InvalidRuleCode)
&& !per_file_ignores.contains(Rule::InvalidRuleCode)
&& !exemption.enumerates(Rule::InvalidRuleCode)
{
ruff::rules::invalid_noqa_code(context, &noqa_directives, locator, &settings.external);

View File

@@ -26,16 +26,15 @@ pub(crate) fn check_physical_lines(
settings: &LinterSettings,
context: &LintContext,
) {
let enforce_doc_line_too_long = context.is_rule_enabled(Rule::DocLineTooLong);
let enforce_line_too_long = context.is_rule_enabled(Rule::LineTooLong);
let enforce_no_newline_at_end_of_file =
context.is_rule_enabled(Rule::MissingNewlineAtEndOfFile);
let enforce_mixed_spaces_and_tabs = context.is_rule_enabled(Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = context.is_rule_enabled(Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = context.is_rule_enabled(Rule::TrailingWhitespace);
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
context.is_rule_enabled(Rule::BlankLineWithWhitespace);
let enforce_copyright_notice = context.is_rule_enabled(Rule::MissingCopyrightNotice);
settings.rules.enabled(Rule::BlankLineWithWhitespace);
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
let mut doc_lines_iter = doc_lines.iter().peekable();
let comment_ranges = indexer.comment_ranges();
@@ -63,10 +62,10 @@ pub(crate) fn check_physical_lines(
}
if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace {
trailing_whitespace(&line, locator, indexer, context);
trailing_whitespace(&line, locator, indexer, settings, context);
}
if context.is_rule_enabled(Rule::IndentedFormFeed) {
if settings.rules.enabled(Rule::IndentedFormFeed) {
indented_form_feed(&line, context);
}
}
@@ -82,11 +81,10 @@ pub(crate) fn check_physical_lines(
#[cfg(test)]
mod tests {
use std::path::Path;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::parse_module;
use ruff_source_file::SourceFileBuilder;
use crate::Locator;
use crate::checkers::ast::LintContext;
@@ -106,6 +104,7 @@ mod tests {
let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents());
let check_with_max_line_length = |line_length: LineLength| {
let source_file = SourceFileBuilder::new("<filename>", line).finish();
let settings = LinterSettings {
pycodestyle: pycodestyle::settings::Settings {
max_line_length: line_length,
@@ -113,9 +112,9 @@ mod tests {
},
..LinterSettings::for_rule(Rule::LineTooLong)
};
let diagnostics = LintContext::new(Path::new("<filename>"), line, &settings);
let diagnostics = LintContext::new(&source_file, &settings);
check_physical_lines(&locator, &stylist, &indexer, &[], &settings, &diagnostics);
diagnostics.into_parts().0
diagnostics.into_diagnostics()
};
let line_length = LineLength::try_from(8).unwrap();
assert_eq!(check_with_max_line_length(line_length), vec![]);

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