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
448 changed files with 4132 additions and 10119 deletions

View File

@@ -16,7 +16,7 @@
pep621: {
// The default for this package manager is to only search for `pyproject.toml` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
managerFilePatterns: ["/^(python|scripts)/.*pyproject\\.toml$/"],
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
},
pip_requirements: {
// The default for this package manager is to run on all requirements.txt files:
@@ -34,32 +34,12 @@
npm: {
// The default for this package manager is to only search for `package.json` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
managerFilePatterns: ["/^playground/.*package\\.json$/"],
fileMatch: ["^playground/.*package\\.json$"],
},
customManagers: [
{
customType: "regex",
managerFilePatterns: ["/^dist-workspace\\.toml$/"],
matchStrings: [
'"(?<depName>actions/[^"]+)" = "(?<currentDigest>[a-f0-9]{40})"\\s*#\\s*(?<currentValue>v[\\d\\.]+).*'
],
datasourceTemplate: "github-tags",
autoReplaceStringTemplate: '"{{depName}}" = "{{newDigest}}" # {{newValue}}"',
extractVersionTemplate: "^(?<version>v[\\d\\.]+)$",
versioningTemplate: "semver"
}
],
"pre-commit": {
enabled: true,
},
packageRules: [
// Ignore GitHub Actions in generated release.yml (managed by cargo-dist)
{
matchManagers: ["github-actions"],
matchFileNames: [".github/workflows/release.yml"],
enabled: false,
description: "Ignore GitHub Actions in release.yml as it's generated by cargo-dist",
},
// Pin GitHub Actions to immutable SHAs.
{
matchDepTypes: ["action"],
@@ -126,11 +106,6 @@
matchManagers: ["cargo"],
matchPackageNames: ["strum"],
description: "Weekly update of strum dependencies",
},
{
groupName: "cargo-dist GitHub Actions",
matchManagers: ["custom.regex"],
description: "Weekly update of GitHub Actions dependencies managed by cargo-dist",
}
],
vulnerabilityAlerts: {

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:

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

@@ -61,7 +61,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -69,9 +69,9 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.3/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -87,7 +87,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -124,7 +124,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -154,7 +154,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: artifacts-build-global
path: |
@@ -175,7 +175,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive
@@ -201,7 +201,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -251,7 +251,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
with:
persist-credentials: false
submodules: recursive

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

16
Cargo.lock generated
View File

@@ -1663,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"
@@ -1694,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",
@@ -1825,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",
]
@@ -3603,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

@@ -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,27 +427,27 @@ 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
// errors anyway.
.filter_map(|msg| Some((msg.noqa_code().and_then(|code| code.rule())?, msg)))
.filter_map(|msg| Some((msg.name().parse().ok()?, msg)))
.map(|(rule, msg)| {
// 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

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

View File

@@ -204,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| {
@@ -228,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

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

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

@@ -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,10 +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(): ...

View File

@@ -1,8 +1,3 @@
@pytest.mark.foo(scope="module")
def ok_due_to_missing_import():
pass
import pytest
@@ -77,14 +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): ...

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

@@ -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,9 +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()):
...

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

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

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

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

@@ -14,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,
@@ -58,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,
@@ -89,11 +89,11 @@ 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) {
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
@@ -123,7 +123,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
}
}
if checker.is_rule_enabled(Rule::RedefinedArgumentFromLocal) {
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()];
@@ -156,7 +156,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
}
}
if checker.is_rule_enabled(Rule::ImportShadowedByLoopVar) {
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.
@@ -197,7 +197,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
}
}
if checker.is_rule_enabled(Rule::RedefinedWhileUnused) {
if checker.enabled(Rule::RedefinedWhileUnused) {
// Index the redefined bindings by statement.
let mut redefinitions = FxHashMap::default();
@@ -353,43 +353,43 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
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
@@ -418,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,
@@ -447,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);
}
@@ -467,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,
@@ -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,
);
}
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,

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,7 +35,7 @@ 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,
@@ -44,7 +44,7 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker)
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,
@@ -53,24 +53,24 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker)
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,13 +7,13 @@ 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() {
if checker.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) {
checker.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
@@ -22,7 +22,7 @@ pub(crate) fn unresolved_references(checker: &Checker) {
);
}
} else {
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;
@@ -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};
@@ -324,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
@@ -347,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.
@@ -481,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.
@@ -623,12 +616,16 @@ impl SemanticSyntaxContext for Checker<'_> {
fn report_semantic_error(&self, error: SemanticSyntaxError) {
match error.kind {
SemanticSyntaxErrorKind::LateFutureImport => {
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,
@@ -639,17 +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 => {
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);
}
}
@@ -671,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 {
@@ -2361,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);
}
}
@@ -2763,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,
);
@@ -2790,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,
@@ -2805,7 +2800,7 @@ impl<'a> Checker<'a> {
Err(parse_error) => {
self.semantic.restore(snapshot);
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(),
@@ -2952,7 +2947,7 @@ impl<'a> Checker<'a> {
self.semantic.flags -= SemanticModelFlags::DUNDER_ALL_DEFINITION;
} else {
if self.semantic.global_scope().uses_star_imports() {
if self.is_rule_enabled(Rule::UndefinedLocalWithImportStarUsage) {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.report_diagnostic(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: name.to_string(),
@@ -2962,7 +2957,7 @@ impl<'a> Checker<'a> {
.set_parent(definition.start());
}
} else {
if self.is_rule_enabled(Rule::UndefinedExport) {
if self.enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings)
|| !self.path.ends_with("__init__.py")
{
@@ -3113,29 +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,
#[expect(unused, reason = "TODO(brent) use this instead of Checker::settings")]
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,
}
}
@@ -3151,7 +3134,7 @@ impl<'a> LintContext<'a> {
) -> DiagnosticGuard<'chk, 'a> {
DiagnosticGuard {
context: self,
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
diagnostic: Some(OldDiagnostic::new(kind, range, self.source_file)),
}
}
@@ -3165,42 +3148,29 @@ impl<'a> LintContext<'a> {
kind: T,
range: TextRange,
) -> Option<DiagnosticGuard<'chk, 'a>> {
if self.is_rule_enabled(T::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)),
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()
}

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,6 +22,7 @@ use crate::{Edit, Fix, Locator};
use super::ast::LintContext;
#[expect(clippy::too_many_arguments)]
pub(crate) fn check_noqa(
context: &mut LintContext,
path: &Path,
@@ -29,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).
@@ -45,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.
@@ -105,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()
@@ -146,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
@@ -159,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);
@@ -229,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,
@@ -243,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![]);

View File

@@ -34,7 +34,7 @@ pub(crate) fn check_tokens(
) {
let comment_ranges = indexer.comment_ranges();
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::BlankLineBetweenMethods,
Rule::BlankLinesTopLevel,
Rule::TooManyBlankLines,
@@ -53,33 +53,36 @@ pub(crate) fn check_tokens(
.check_lines(tokens);
}
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
if settings.rules.enabled(Rule::BlanketTypeIgnore) {
pygrep_hooks::rules::blanket_type_ignore(context, comment_ranges, locator);
}
if context.is_rule_enabled(Rule::EmptyComment) {
if settings.rules.enabled(Rule::EmptyComment) {
pylint::rules::empty_comments(context, comment_ranges, locator);
}
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
if settings
.rules
.enabled(Rule::AmbiguousUnicodeCharacterComment)
{
for range in comment_ranges {
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
}
}
if context.is_rule_enabled(Rule::CommentedOutCode) {
if settings.rules.enabled(Rule::CommentedOutCode) {
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
}
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
if settings.rules.enabled(Rule::UTF8EncodingDeclaration) {
pyupgrade::rules::unnecessary_coding_comment(context, locator, comment_ranges);
}
if context.is_rule_enabled(Rule::TabIndentation) {
if settings.rules.enabled(Rule::TabIndentation) {
pycodestyle::rules::tab_indentation(context, locator, indexer);
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::InvalidCharacterBackspace,
Rule::InvalidCharacterSub,
Rule::InvalidCharacterEsc,
@@ -91,7 +94,7 @@ pub(crate) fn check_tokens(
}
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::MultipleStatementsOnOneLineColon,
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
@@ -106,14 +109,14 @@ pub(crate) fn check_tokens(
);
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]) {
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
@@ -121,25 +124,25 @@ pub(crate) fn check_tokens(
flake8_commas::rules::trailing_commas(context, tokens, locator, indexer);
}
if context.is_rule_enabled(Rule::ExtraneousParentheses) {
if settings.rules.enabled(Rule::ExtraneousParentheses) {
pyupgrade::rules::extraneous_parentheses(context, tokens, locator);
}
if source_type.is_stub() && context.is_rule_enabled(Rule::TypeCommentInStub) {
if source_type.is_stub() && settings.rules.enabled(Rule::TypeCommentInStub) {
flake8_pyi::rules::type_comment_in_stub(context, locator, comment_ranges);
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::ShebangNotExecutable,
Rule::ShebangMissingExecutableFile,
Rule::ShebangLeadingWhitespace,
Rule::ShebangNotFirstLine,
Rule::ShebangMissingPython,
]) {
flake8_executable::rules::from_tokens(context, path, locator, comment_ranges);
flake8_executable::rules::from_tokens(context, path, locator, comment_ranges, settings);
}
if context.any_rule_enabled(&[
if settings.rules.any_enabled(&[
Rule::InvalidTodoTag,
Rule::MissingTodoAuthor,
Rule::MissingTodoLink,
@@ -164,7 +167,7 @@ pub(crate) fn check_tokens(
flake8_fixme::rules::todos(context, &todo_comments);
}
if context.is_rule_enabled(Rule::TooManyNewlinesAtEndOfFile) {
if settings.rules.enabled(Rule::TooManyNewlinesAtEndOfFile) {
pycodestyle::rules::too_many_newlines_at_end_of_file(context, tokens, cell_offsets);
}
}

View File

@@ -1028,8 +1028,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
(Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),

View File

@@ -0,0 +1,104 @@
use anyhow::Result;
use log::debug;
use ruff_source_file::SourceFile;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::registry::AsRule;
use crate::violation::Violation;
use crate::{Fix, codes::Rule};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct OldDiagnostic {
/// The message body to display to the user, to explain the diagnostic.
pub body: String,
/// The message to display to the user, to explain the suggested fix.
pub suggestion: Option<String>,
pub range: TextRange,
pub fix: Option<Fix>,
pub parent: Option<TextSize>,
pub(crate) rule: Rule,
pub(crate) file: SourceFile,
}
impl OldDiagnostic {
// TODO(brent) We temporarily allow this to avoid updating all of the call sites to add
// references. I expect this method to go away or change significantly with the rest of the
// diagnostic refactor, but if it still exists in this form at the end of the refactor, we
// should just update the call sites.
#[expect(clippy::needless_pass_by_value)]
pub fn new<T: Violation>(kind: T, range: TextRange, file: &SourceFile) -> Self {
Self {
body: Violation::message(&kind),
suggestion: Violation::fix_title(&kind),
range,
fix: None,
parent: None,
rule: T::rule(),
file: file.clone(),
}
}
/// Consumes `self` and returns a new `Diagnostic` with the given `fix`.
#[inline]
#[must_use]
pub fn with_fix(mut self, fix: Fix) -> Self {
self.set_fix(fix);
self
}
/// Set the [`Fix`] used to fix the diagnostic.
#[inline]
pub fn set_fix(&mut self, fix: Fix) {
self.fix = Some(fix);
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub fn try_set_fix(&mut self, func: impl FnOnce() -> Result<Fix>) {
match func() {
Ok(fix) => self.fix = Some(fix),
Err(err) => debug!("Failed to create fix for {}: {}", self.rule, err),
}
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub fn try_set_optional_fix(&mut self, func: impl FnOnce() -> Result<Option<Fix>>) {
match func() {
Ok(None) => {}
Ok(Some(fix)) => self.fix = Some(fix),
Err(err) => debug!("Failed to create fix for {}: {}", self.rule, err),
}
}
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
#[inline]
#[must_use]
pub fn with_parent(mut self, parent: TextSize) -> Self {
self.set_parent(parent);
self
}
/// Set the location of the diagnostic's parent node.
#[inline]
pub fn set_parent(&mut self, parent: TextSize) {
self.parent = Some(parent);
}
}
impl AsRule for OldDiagnostic {
fn rule(&self) -> Rule {
self.rule
}
}
impl Ranged for OldDiagnostic {
fn range(&self) -> TextRange {
self.range
}
}

View File

@@ -209,7 +209,6 @@ pub(crate) fn remove_argument<T: Ranged>(
arguments: &Arguments,
parentheses: Parentheses,
source: &str,
comment_ranges: &CommentRanges,
) -> Result<Edit> {
// Partition into arguments before and after the argument to remove.
let (before, after): (Vec<_>, Vec<_>) = arguments
@@ -218,15 +217,6 @@ pub(crate) fn remove_argument<T: Ranged>(
.filter(|range| argument.range() != *range)
.partition(|range| range.start() < argument.start());
let arg = arguments
.arguments_source_order()
.find(|arg| arg.range() == argument.range())
.context("Unable to find argument")?;
let parenthesized_range =
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
.unwrap_or(arg.range());
if !after.is_empty() {
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
@@ -244,7 +234,7 @@ pub(crate) fn remove_argument<T: Ranged>(
})
.context("Unable to find next token")?;
Ok(Edit::deletion(parenthesized_range.start(), next.start()))
Ok(Edit::deletion(argument.start(), next.start()))
} else if let Some(previous) = before.iter().map(Ranged::end).max() {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
@@ -255,7 +245,7 @@ pub(crate) fn remove_argument<T: Ranged>(
.find(|token| token.kind == SimpleTokenKind::Comma)
.context("Unable to find trailing comma")?;
Ok(Edit::deletion(comma.start(), parenthesized_range.end()))
Ok(Edit::deletion(comma.start(), argument.end()))
} else {
// Case 3: argument or keyword is the only node, so delete the arguments (but preserve
// parentheses, if needed).
@@ -618,6 +608,7 @@ mod tests {
use crate::fix::edits::{
add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon,
};
use crate::message::Message;
use crate::{Edit, Fix, Locator, OldDiagnostic};
/// Parse the given source using [`Mode::Module`] and return the first statement.
@@ -749,7 +740,7 @@ x = 1 \
let diag = {
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
let mut iter = edits.into_iter();
OldDiagnostic::new(
let diag = OldDiagnostic::new(
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
TextRange::default(),
&SourceFileBuilder::new("<filename>", "<code>").finish(),
@@ -757,7 +748,8 @@ x = 1 \
.with_fix(Fix::safe_edits(
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
iter,
))
));
Message::from_diagnostic(diag, None)
};
assert_eq!(apply_fixes([diag].iter(), &locator).code, expect);
Ok(())

View File

@@ -8,7 +8,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::Locator;
use crate::linter::FixTable;
use crate::message::OldDiagnostic;
use crate::message::Message;
use crate::registry::Rule;
use crate::settings::types::UnsafeFixes;
use crate::{Edit, Fix};
@@ -28,13 +28,13 @@ pub(crate) struct FixResult {
/// Fix errors in a file, and write the fixed source code to disk.
pub(crate) fn fix_file(
diagnostics: &[OldDiagnostic],
messages: &[Message],
locator: &Locator,
unsafe_fixes: UnsafeFixes,
) -> Option<FixResult> {
let required_applicability = unsafe_fixes.required_applicability();
let mut with_fixes = diagnostics
let mut with_fixes = messages
.iter()
.filter(|message| {
message
@@ -52,7 +52,7 @@ pub(crate) fn fix_file(
/// Apply a series of fixes.
fn apply_fixes<'a>(
diagnostics: impl Iterator<Item = &'a OldDiagnostic>,
diagnostics: impl Iterator<Item = &'a Message>,
locator: &'a Locator<'a>,
) -> FixResult {
let mut output = String::with_capacity(locator.len());
@@ -173,8 +173,9 @@ mod tests {
use ruff_text_size::{Ranged, TextSize};
use crate::Locator;
use crate::OldDiagnostic;
use crate::diagnostic::OldDiagnostic;
use crate::fix::{FixResult, apply_fixes};
use crate::message::Message;
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
use crate::{Edit, Fix};
@@ -182,7 +183,7 @@ mod tests {
filename: &str,
source: &str,
edit: impl IntoIterator<Item = Edit>,
) -> Vec<OldDiagnostic> {
) -> Vec<Message> {
edit.into_iter()
.map(|edit| {
// The choice of rule here is arbitrary.
@@ -191,7 +192,7 @@ mod tests {
edit.range(),
&SourceFileBuilder::new(filename, source).finish(),
);
diagnostic.with_fix(Fix::safe_edit(edit))
Message::from_diagnostic(diagnostic.with_fix(Fix::safe_edit(edit)), None)
})
.collect()
}

View File

@@ -20,9 +20,6 @@ pub fn get_cwd() -> &'static Path {
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path(path: &Path, ignore_list: &CompiledPerFileIgnoreList) -> RuleSet {
if ignore_list.is_empty() {
return RuleSet::empty();
}
ignore_list
.iter_matches(path, "Adding per-file ignores")
.flatten()

View File

@@ -14,7 +14,7 @@ pub use rule_selector::RuleSelector;
pub use rule_selector::clap_completion::RuleSelectorParser;
pub use rules::pycodestyle::rules::IOError;
pub use message::OldDiagnostic;
pub use diagnostic::OldDiagnostic;
pub(crate) use ruff_diagnostics::{Applicability, Edit, Fix};
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata};
@@ -24,6 +24,7 @@ mod checkers;
pub mod codes;
mod comments;
mod cst;
mod diagnostic;
pub mod directives;
mod doc_lines;
mod docstrings;

View File

@@ -13,7 +13,7 @@ use ruff_python_ast::{ModModule, PySourceType, PythonVersion};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::{ParseError, ParseOptions, Parsed, UnsupportedSyntaxError};
use ruff_source_file::SourceFile;
use ruff_source_file::{SourceFile, SourceFileBuilder};
use ruff_text_size::Ranged;
use crate::OldDiagnostic;
@@ -27,10 +27,11 @@ use crate::codes::NoqaCode;
use crate::directives::Directives;
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
use crate::fix::{FixResult, fix_file};
use crate::message::Message;
use crate::noqa::add_noqa;
use crate::package::PackageRoot;
use crate::preview::is_py314_support_enabled;
use crate::registry::Rule;
use crate::registry::{AsRule, Rule, RuleSet};
#[cfg(any(feature = "test-rules", test))]
use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
use crate::settings::types::UnsafeFixes;
@@ -38,11 +39,9 @@ use crate::settings::{LinterSettings, TargetVersion, flags};
use crate::source_kind::SourceKind;
use crate::{Locator, directives, fs, warn_user_once};
pub(crate) mod float;
pub struct LinterResult {
/// A collection of diagnostic messages generated by the linter.
pub diagnostics: Vec<OldDiagnostic>,
pub messages: Vec<Message>,
/// Flag indicating that the parsed source code does not contain any
/// [`ParseError`]s
has_valid_syntax: bool,
@@ -144,7 +143,7 @@ pub struct FixerResult<'a> {
pub fixed: FixTable,
}
/// Generate [`OldDiagnostic`]s from the source code contents at the given `Path`.
/// Generate [`Message`]s from the source code contents at the given `Path`.
#[expect(clippy::too_many_arguments)]
pub fn check_path(
path: &Path,
@@ -159,9 +158,12 @@ pub fn check_path(
source_type: PySourceType,
parsed: &Parsed<ModModule>,
target_version: TargetVersion,
) -> Vec<OldDiagnostic> {
) -> Vec<Message> {
let source_file =
SourceFileBuilder::new(path.to_string_lossy().as_ref(), locator.contents()).finish();
// Aggregate all diagnostics.
let mut context = LintContext::new(path, locator.contents(), settings);
let mut diagnostics = LintContext::new(&source_file, settings);
// Aggregate all semantic syntax errors.
let mut semantic_syntax_errors = vec![];
@@ -171,15 +173,16 @@ pub fn check_path(
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
// (for docstrings), which demands special-casing at this level.
let use_doc_lines = context.is_rule_enabled(Rule::DocLineTooLong);
let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong);
let mut doc_lines = vec![];
if use_doc_lines {
doc_lines.extend(doc_lines_from_tokens(tokens));
}
// Run the token-based rules.
if context
.iter_enabled_rules()
if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_tokens())
{
check_tokens(
@@ -191,13 +194,14 @@ pub fn check_path(
settings,
source_type,
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
&mut context,
&mut diagnostics,
);
}
// Run the filesystem-based rules.
if context
.iter_enabled_rules()
if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_filesystem())
{
check_file_path(
@@ -207,17 +211,23 @@ pub fn check_path(
comment_ranges,
settings,
target_version.linter_version(),
&context,
&diagnostics,
);
}
// Run the logical line-based rules.
if context
.iter_enabled_rules()
if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_logical_lines())
{
crate::checkers::logical_lines::check_logical_lines(
tokens, locator, indexer, stylist, settings, &context,
tokens,
locator,
indexer,
stylist,
settings,
&diagnostics,
);
}
@@ -240,12 +250,13 @@ pub fn check_path(
cell_offsets,
notebook_index,
target_version,
&context,
&diagnostics,
));
let use_imports = !directives.isort.skip_file
&& context
.iter_enabled_rules()
&& settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_imports());
if use_imports || use_doc_lines {
if use_imports {
@@ -260,7 +271,7 @@ pub fn check_path(
source_type,
cell_offsets,
target_version.linter_version(),
&context,
&diagnostics,
);
}
if use_doc_lines {
@@ -276,77 +287,89 @@ pub fn check_path(
}
// Run the lines-based rules.
if context
.iter_enabled_rules()
if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_physical_lines())
{
check_physical_lines(locator, stylist, indexer, &doc_lines, settings, &context);
check_physical_lines(
locator,
stylist,
indexer,
&doc_lines,
settings,
&diagnostics,
);
}
// Raise violations for internal test rules
#[cfg(any(feature = "test-rules", test))]
{
for test_rule in TEST_RULES {
if !context.is_rule_enabled(*test_rule) {
if !settings.rules.enabled(*test_rule) {
continue;
}
match test_rule {
Rule::StableTestRule => {
test_rules::StableTestRule::diagnostic(locator, comment_ranges, &context);
}
Rule::StableTestRuleSafeFix => {
test_rules::StableTestRuleSafeFix::diagnostic(
locator,
comment_ranges,
&context,
);
test_rules::StableTestRule::diagnostic(locator, comment_ranges, &diagnostics);
}
Rule::StableTestRuleSafeFix => test_rules::StableTestRuleSafeFix::diagnostic(
locator,
comment_ranges,
&diagnostics,
),
Rule::StableTestRuleUnsafeFix => test_rules::StableTestRuleUnsafeFix::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
),
Rule::StableTestRuleDisplayOnlyFix => {
test_rules::StableTestRuleDisplayOnlyFix::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
);
}
Rule::PreviewTestRule => {
test_rules::PreviewTestRule::diagnostic(locator, comment_ranges, &context);
test_rules::PreviewTestRule::diagnostic(locator, comment_ranges, &diagnostics);
}
Rule::DeprecatedTestRule => {
test_rules::DeprecatedTestRule::diagnostic(locator, comment_ranges, &context);
test_rules::DeprecatedTestRule::diagnostic(
locator,
comment_ranges,
&diagnostics,
);
}
Rule::AnotherDeprecatedTestRule => {
test_rules::AnotherDeprecatedTestRule::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
);
}
Rule::RemovedTestRule => {
test_rules::RemovedTestRule::diagnostic(locator, comment_ranges, &context);
test_rules::RemovedTestRule::diagnostic(locator, comment_ranges, &diagnostics);
}
Rule::AnotherRemovedTestRule => test_rules::AnotherRemovedTestRule::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
),
Rule::RedirectedToTestRule => test_rules::RedirectedToTestRule::diagnostic(
locator,
comment_ranges,
&diagnostics,
),
Rule::RedirectedToTestRule => {
test_rules::RedirectedToTestRule::diagnostic(locator, comment_ranges, &context);
}
Rule::RedirectedFromTestRule => test_rules::RedirectedFromTestRule::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
),
Rule::RedirectedFromPrefixTestRule => {
test_rules::RedirectedFromPrefixTestRule::diagnostic(
locator,
comment_ranges,
&context,
&diagnostics,
);
}
_ => unreachable!("All test rules must have an implementation"),
@@ -354,38 +377,54 @@ pub fn check_path(
}
}
// Ignore diagnostics based on per-file-ignores.
let per_file_ignores = if (!diagnostics.is_empty()
|| settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_noqa()))
&& !settings.per_file_ignores.is_empty()
{
fs::ignores_from_path(path, &settings.per_file_ignores)
} else {
RuleSet::empty()
};
if !per_file_ignores.is_empty() {
diagnostics
.as_mut_vec()
.retain(|diagnostic| !per_file_ignores.contains(diagnostic.rule()));
}
// Enforce `noqa` directives.
if noqa.is_enabled()
|| context
.iter_enabled_rules()
|| settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_noqa())
{
let ignored = check_noqa(
&mut context,
&mut diagnostics,
path,
locator,
comment_ranges,
&directives.noqa_line_for,
parsed.has_valid_syntax(),
&per_file_ignores,
settings,
);
if noqa.is_enabled() {
for index in ignored.iter().rev() {
context.as_mut_vec().swap_remove(*index);
diagnostics.as_mut_vec().swap_remove(*index);
}
}
}
let (mut diagnostics, source_file) = context.into_parts();
let mut diagnostics = diagnostics.into_diagnostics();
if parsed.has_valid_syntax() {
// Remove fixes for any rules marked as unfixable.
for diagnostic in &mut diagnostics {
if diagnostic
.noqa_code()
.and_then(|code| code.rule())
.is_none_or(|rule| !settings.rules.should_fix(rule))
{
if !settings.rules.should_fix(diagnostic.rule()) {
diagnostic.fix = None;
}
}
@@ -394,12 +433,10 @@ pub fn check_path(
if !settings.fix_safety.is_empty() {
for diagnostic in &mut diagnostics {
if let Some(fix) = diagnostic.fix.take() {
if let Some(rule) = diagnostic.noqa_code().and_then(|code| code.rule()) {
let fixed_applicability = settings
.fix_safety
.resolve_applicability(rule, fix.applicability());
diagnostic.set_fix(fix.with_applicability(fixed_applicability));
}
let fixed_applicability = settings
.fix_safety
.resolve_applicability(diagnostic.rule(), fix.applicability());
diagnostic.set_fix(fix.with_applicability(fixed_applicability));
}
}
}
@@ -455,7 +492,7 @@ pub fn add_noqa_to_path(
);
// Generate diagnostics, ignoring any existing `noqa` directives.
let diagnostics = check_path(
let messages = check_path(
path,
package,
&locator,
@@ -474,7 +511,7 @@ pub fn add_noqa_to_path(
// TODO(dhruvmanila): Add support for Jupyter Notebooks
add_noqa(
path,
&diagnostics,
&messages,
&locator,
indexer.comment_ranges(),
&settings.external,
@@ -483,7 +520,8 @@ pub fn add_noqa_to_path(
)
}
/// Generate an [`OldDiagnostic`] for each diagnostic triggered by the given source code.
/// Generate a [`Message`] for each [`OldDiagnostic`] triggered by the given source
/// code.
pub fn lint_only(
path: &Path,
package: Option<PackageRoot<'_>>,
@@ -523,7 +561,7 @@ pub fn lint_only(
);
// Generate diagnostics.
let diagnostics = check_path(
let messages = check_path(
path,
package,
&locator,
@@ -540,14 +578,12 @@ pub fn lint_only(
LinterResult {
has_valid_syntax: parsed.has_valid_syntax(),
has_no_syntax_errors: !diagnostics.iter().any(OldDiagnostic::is_syntax_error),
diagnostics,
has_no_syntax_errors: !messages.iter().any(Message::is_syntax_error),
messages,
}
}
/// Convert various error types into a single collection of diagnostics.
///
/// Also use `directives` to attach noqa offsets to lint diagnostics.
/// Convert from diagnostics to messages.
fn diagnostics_to_messages(
diagnostics: Vec<OldDiagnostic>,
parse_errors: &[ParseError],
@@ -556,23 +592,21 @@ fn diagnostics_to_messages(
locator: &Locator,
directives: &Directives,
source_file: &SourceFile,
) -> Vec<OldDiagnostic> {
) -> Vec<Message> {
parse_errors
.iter()
.map(|parse_error| {
OldDiagnostic::from_parse_error(parse_error, locator, source_file.clone())
})
.map(|parse_error| Message::from_parse_error(parse_error, locator, source_file.clone()))
.chain(unsupported_syntax_errors.iter().map(|syntax_error| {
OldDiagnostic::from_unsupported_syntax_error(syntax_error, source_file.clone())
Message::from_unsupported_syntax_error(syntax_error, source_file.clone())
}))
.chain(
semantic_syntax_errors
.iter()
.map(|error| OldDiagnostic::from_semantic_syntax_error(error, source_file.clone())),
.map(|error| Message::from_semantic_syntax_error(error, source_file.clone())),
)
.chain(diagnostics.into_iter().map(|diagnostic| {
let noqa_offset = directives.noqa_line_for.resolve(diagnostic.start());
diagnostic.with_noqa_offset(noqa_offset)
Message::from_diagnostic(diagnostic, Some(noqa_offset))
}))
.collect()
}
@@ -636,7 +670,7 @@ pub fn lint_fix<'a>(
);
// Generate diagnostics.
let diagnostics = check_path(
let messages = check_path(
path,
package,
&locator,
@@ -653,7 +687,7 @@ pub fn lint_fix<'a>(
if iterations == 0 {
has_valid_syntax = parsed.has_valid_syntax();
has_no_syntax_errors = !diagnostics.iter().any(OldDiagnostic::is_syntax_error);
has_no_syntax_errors = !messages.iter().any(Message::is_syntax_error);
} else {
// If the source code had no syntax errors on the first pass, but
// does on a subsequent pass, then we've introduced a
@@ -671,7 +705,7 @@ pub fn lint_fix<'a>(
code: fixed_contents,
fixes: applied,
source_map,
}) = fix_file(&diagnostics, &locator, unsafe_fixes)
}) = fix_file(&messages, &locator, unsafe_fixes)
{
if iterations < MAX_ITERATIONS {
// Count the number of fixed errors.
@@ -688,12 +722,12 @@ pub fn lint_fix<'a>(
continue;
}
report_failed_to_converge_error(path, transformed.source_code(), &diagnostics);
report_failed_to_converge_error(path, transformed.source_code(), &messages);
}
return Ok(FixerResult {
result: LinterResult {
diagnostics,
messages,
has_valid_syntax,
has_no_syntax_errors,
},
@@ -713,8 +747,8 @@ fn collect_rule_codes(rules: impl IntoIterator<Item = NoqaCode>) -> String {
}
#[expect(clippy::print_stderr)]
fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[OldDiagnostic]) {
let codes = collect_rule_codes(diagnostics.iter().filter_map(OldDiagnostic::noqa_code));
fn report_failed_to_converge_error(path: &Path, transformed: &str, messages: &[Message]) {
let codes = collect_rule_codes(messages.iter().filter_map(Message::noqa_code));
if cfg!(debug_assertions) {
eprintln!(
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
@@ -838,12 +872,12 @@ mod tests {
use ruff_notebook::{Notebook, NotebookError};
use crate::linter::check_path;
use crate::message::OldDiagnostic;
use crate::message::Message;
use crate::registry::Rule;
use crate::settings::LinterSettings;
use crate::source_kind::SourceKind;
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
use crate::{Locator, assert_diagnostics, directives, settings};
use crate::{Locator, assert_messages, directives, settings};
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
fn notebook_path(path: impl AsRef<Path>) -> std::path::PathBuf {
@@ -855,7 +889,7 @@ mod tests {
let actual = notebook_path("isort.ipynb");
let expected = notebook_path("isort_expected.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -863,7 +897,7 @@ mod tests {
expected,
&LinterSettings::for_rule(Rule::UnsortedImports),
)?;
assert_diagnostics!(diagnostics, actual, source_notebook);
assert_messages!(messages, actual, source_notebook);
Ok(())
}
@@ -872,7 +906,7 @@ mod tests {
let actual = notebook_path("ipy_escape_command.ipynb");
let expected = notebook_path("ipy_escape_command_expected.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -880,7 +914,7 @@ mod tests {
expected,
&LinterSettings::for_rule(Rule::UnusedImport),
)?;
assert_diagnostics!(diagnostics, actual, source_notebook);
assert_messages!(messages, actual, source_notebook);
Ok(())
}
@@ -889,7 +923,7 @@ mod tests {
let actual = notebook_path("unused_variable.ipynb");
let expected = notebook_path("unused_variable_expected.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -897,7 +931,7 @@ mod tests {
expected,
&LinterSettings::for_rule(Rule::UnusedVariable),
)?;
assert_diagnostics!(diagnostics, actual, source_notebook);
assert_messages!(messages, actual, source_notebook);
Ok(())
}
@@ -906,7 +940,7 @@ mod tests {
let actual = notebook_path("undefined_name.ipynb");
let expected = notebook_path("undefined_name.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -914,7 +948,7 @@ mod tests {
expected,
&LinterSettings::for_rule(Rule::UndefinedName),
)?;
assert_diagnostics!(diagnostics, actual, source_notebook);
assert_messages!(messages, actual, source_notebook);
Ok(())
}
@@ -944,7 +978,7 @@ mod tests {
let actual = notebook_path("vscode_language_id.ipynb");
let expected = notebook_path("vscode_language_id_expected.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -952,7 +986,7 @@ mod tests {
expected,
&LinterSettings::for_rule(Rule::UnusedImport),
)?;
assert_diagnostics!(diagnostics, actual, source_notebook);
assert_messages!(messages, actual, source_notebook);
Ok(())
}
@@ -996,7 +1030,7 @@ mod tests {
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
/// file.
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<OldDiagnostic> {
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Message> {
let contents = dedent(contents);
test_contents_syntax_errors(
&SourceKind::Python(contents.to_string()),
@@ -1011,7 +1045,7 @@ mod tests {
source_kind: &SourceKind,
path: &Path,
settings: &LinterSettings,
) -> Vec<OldDiagnostic> {
) -> Vec<Message> {
let source_type = PySourceType::from(path);
let target_version = settings.resolve_target_version(path);
let options =
@@ -1028,7 +1062,7 @@ mod tests {
&locator,
&indexer,
);
let mut diagnostics = check_path(
let mut messages = check_path(
path,
None,
&locator,
@@ -1042,8 +1076,8 @@ mod tests {
&parsed,
target_version,
);
diagnostics.sort_by_key(Ranged::start);
diagnostics
messages.sort_by_key(Ranged::start);
messages
}
#[test_case(
@@ -1248,7 +1282,7 @@ mod tests {
error_type: &str,
) {
let snapshot = format!("semantic_syntax_error_{error_type}_{name}_{python_version}");
let diagnostics = test_snippet_syntax_errors(
let messages = test_snippet_syntax_errors(
contents,
&LinterSettings {
rules: settings::rule_table::RuleTable::empty(),
@@ -1257,7 +1291,7 @@ mod tests {
..Default::default()
},
);
assert_diagnostics!(snapshot, diagnostics);
assert_messages!(snapshot, messages);
}
#[test_case(PythonVersion::PY310)]
@@ -1266,7 +1300,7 @@ mod tests {
let snapshot =
format!("async_comprehension_in_sync_comprehension_notebook_{python_version}");
let path = Path::new("resources/test/fixtures/syntax_errors/async_comprehension.ipynb");
let diagnostics = test_contents_syntax_errors(
let messages = test_contents_syntax_errors(
&SourceKind::ipy_notebook(Notebook::from_path(path)?),
path,
&LinterSettings {
@@ -1276,7 +1310,7 @@ mod tests {
..Default::default()
},
);
assert_diagnostics!(snapshot, diagnostics);
assert_messages!(snapshot, messages);
Ok(())
}
@@ -1293,13 +1327,13 @@ mod tests {
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().to_string();
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
let diagnostics = test_contents_syntax_errors(
let messages = test_contents_syntax_errors(
&SourceKind::Python(std::fs::read_to_string(&path)?),
&path,
&LinterSettings::for_rule(rule),
);
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
assert_diagnostics!(snapshot, diagnostics);
assert_messages!(snapshot, messages);
});
Ok(())
@@ -1309,7 +1343,7 @@ mod tests {
fn test_await_scope_notebook() -> Result<()> {
let path = Path::new("resources/test/fixtures/syntax_errors/await_scope.ipynb");
let TestedNotebook {
diagnostics,
messages,
source_notebook,
..
} = assert_notebook_path(
@@ -1317,7 +1351,7 @@ mod tests {
path,
&LinterSettings::for_rule(Rule::YieldOutsideFunction),
)?;
assert_diagnostics!(diagnostics, path, source_notebook);
assert_messages!(messages, path, source_notebook);
Ok(())
}
@@ -1399,8 +1433,8 @@ mod tests {
)]
fn test_disabled_typing_extensions(name: &str, contents: &str, settings: &LinterSettings) {
let snapshot = format!("disabled_typing_extensions_{name}");
let diagnostics = test_snippet(contents, settings);
assert_diagnostics!(snapshot, diagnostics);
let messages = test_snippet(contents, settings);
assert_messages!(snapshot, messages);
}
#[test_case(
@@ -1416,8 +1450,7 @@ mod tests {
let snapshot = format!("disabled_typing_extensions_pyi_{name}");
let path = Path::new("<filename>.pyi");
let contents = dedent(contents);
let diagnostics =
test_contents(&SourceKind::Python(contents.into_owned()), path, settings).0;
assert_diagnostics!(snapshot, diagnostics);
let messages = test_contents(&SourceKind::Python(contents.into_owned()), path, settings).0;
assert_messages!(snapshot, messages);
}
}

View File

@@ -1,39 +0,0 @@
use ruff_python_ast as ast;
/// Checks if `expr` is a string literal that represents NaN.
/// E.g., `"NaN"`, `"-nAn"`, `"+nan"`, or even `" -NaN\n \t"`
/// Returns `None` if it's not. Else `Some("nan")`, `Some("-nan")`, or `Some("+nan")`.
pub(crate) fn as_nan_float_string_literal(expr: &ast::Expr) -> Option<&'static str> {
find_any_ignore_ascii_case(expr, &["nan", "+nan", "-nan"])
}
/// Returns `true` if `expr` is a string literal that represents a non-finite float.
/// E.g., `"NaN"`, "-inf", `"Infinity"`, or even `" +Inf\n \t"`.
/// Return `None` if it's not. Else the lowercased, trimmed string literal,
/// e.g., `Some("nan")`, `Some("-inf")`, or `Some("+infinity")`.
pub(crate) fn as_non_finite_float_string_literal(expr: &ast::Expr) -> Option<&'static str> {
find_any_ignore_ascii_case(
expr,
&[
"nan",
"+nan",
"-nan",
"inf",
"+inf",
"-inf",
"infinity",
"+infinity",
"-infinity",
],
)
}
fn find_any_ignore_ascii_case(expr: &ast::Expr, patterns: &[&'static str]) -> Option<&'static str> {
let value = &expr.as_string_literal_expr()?.value;
let value = value.to_str().trim();
patterns
.iter()
.find(|other| value.eq_ignore_ascii_case(other))
.copied()
}

View File

@@ -2,7 +2,7 @@ use std::io::Write;
use ruff_source_file::LineColumn;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
/// Generate error logging commands for Azure Pipelines format.
/// See [documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logissue-log-an-error-or-warning)
@@ -13,29 +13,29 @@ impl Emitter for AzureEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
let location = if context.is_notebook(&diagnostic.filename()) {
for message in messages {
let location = if context.is_notebook(&message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()
} else {
diagnostic.compute_start_location()
message.compute_start_location()
};
writeln!(
writer,
"##vso[task.logissue type=error\
;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}",
filename = diagnostic.filename(),
filename = message.filename(),
line = location.line,
col = location.column,
code = diagnostic
code = message
.noqa_code()
.map_or_else(String::new, |code| format!("code={code};")),
body = diagnostic.body(),
body = message.body(),
)?;
}
@@ -49,13 +49,13 @@ mod tests {
use crate::message::AzureEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = AzureEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -63,7 +63,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = AzureEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -8,7 +8,7 @@ use similar::{ChangeTag, TextDiff};
use ruff_source_file::{OneIndexed, SourceFile};
use crate::message::OldDiagnostic;
use crate::message::Message;
use crate::text_helpers::ShowNonprinting;
use crate::{Applicability, Fix};
@@ -26,7 +26,7 @@ pub(super) struct Diff<'a> {
}
impl<'a> Diff<'a> {
pub(crate) fn from_message(message: &'a OldDiagnostic) -> Option<Diff<'a>> {
pub(crate) fn from_message(message: &'a Message) -> Option<Diff<'a>> {
message.fix().map(|fix| Diff {
source_code: message.source_file(),
fix,

View File

@@ -3,7 +3,7 @@ use std::io::Write;
use ruff_source_file::LineColumn;
use crate::fs::relativize_path;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
/// Generate error workflow command in GitHub Actions format.
/// See: [GitHub documentation](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message)
@@ -14,12 +14,12 @@ impl Emitter for GithubEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
let source_location = diagnostic.compute_start_location();
let location = if context.is_notebook(&diagnostic.filename()) {
for message in messages {
let source_location = message.compute_start_location();
let location = if context.is_notebook(&message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
LineColumn::default()
@@ -27,15 +27,15 @@ impl Emitter for GithubEmitter {
source_location
};
let end_location = diagnostic.compute_end_location();
let end_location = message.compute_end_location();
write!(
writer,
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
code = diagnostic
code = message
.noqa_code()
.map_or_else(String::new, |code| format!(" ({code})")),
file = diagnostic.filename(),
file = message.filename(),
row = source_location.line,
column = source_location.column,
end_row = end_location.line,
@@ -45,16 +45,16 @@ impl Emitter for GithubEmitter {
write!(
writer,
"{path}:{row}:{column}:",
path = relativize_path(&*diagnostic.filename()),
path = relativize_path(&*message.filename()),
row = location.line,
column = location.column,
)?;
if let Some(code) = diagnostic.noqa_code() {
if let Some(code) = message.noqa_code() {
write!(writer, " {code}")?;
}
writeln!(writer, " {}", diagnostic.body())?;
writeln!(writer, " {}", message.body())?;
}
Ok(())
@@ -67,13 +67,13 @@ mod tests {
use crate::message::GithubEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = GithubEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -81,7 +81,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = GithubEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -8,7 +8,7 @@ use serde::{Serialize, Serializer};
use serde_json::json;
use crate::fs::{relativize_path, relativize_path_to};
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
/// Generate JSON with violations in GitLab CI format
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
@@ -28,13 +28,13 @@ impl Emitter for GitlabEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(
writer,
&SerializedMessages {
diagnostics,
messages,
context,
project_dir: self.project_dir.as_deref(),
},
@@ -45,7 +45,7 @@ impl Emitter for GitlabEmitter {
}
struct SerializedMessages<'a> {
diagnostics: &'a [OldDiagnostic],
messages: &'a [Message],
context: &'a EmitterContext<'a>,
project_dir: Option<&'a str>,
}
@@ -55,14 +55,14 @@ impl Serialize for SerializedMessages<'_> {
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
let mut fingerprints = HashSet::<u64>::with_capacity(self.diagnostics.len());
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
let mut fingerprints = HashSet::<u64>::with_capacity(self.messages.len());
for diagnostic in self.diagnostics {
let start_location = diagnostic.compute_start_location();
let end_location = diagnostic.compute_end_location();
for message in self.messages {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let lines = if self.context.is_notebook(&diagnostic.filename()) {
let lines = if self.context.is_notebook(&message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
json!({
@@ -77,23 +77,23 @@ impl Serialize for SerializedMessages<'_> {
};
let path = self.project_dir.as_ref().map_or_else(
|| relativize_path(&*diagnostic.filename()),
|project_dir| relativize_path_to(&*diagnostic.filename(), project_dir),
|| relativize_path(&*message.filename()),
|project_dir| relativize_path_to(&*message.filename(), project_dir),
);
let mut message_fingerprint = fingerprint(diagnostic, &path, 0);
let mut message_fingerprint = fingerprint(message, &path, 0);
// Make sure that we do not get a fingerprint that is already in use
// by adding in the previously generated one.
while fingerprints.contains(&message_fingerprint) {
message_fingerprint = fingerprint(diagnostic, &path, message_fingerprint);
message_fingerprint = fingerprint(message, &path, message_fingerprint);
}
fingerprints.insert(message_fingerprint);
let (description, check_name) = if let Some(code) = diagnostic.noqa_code() {
(diagnostic.body().to_string(), code.to_string())
let (description, check_name) = if let Some(code) = message.noqa_code() {
(message.body().to_string(), code.to_string())
} else {
let description = diagnostic.body();
let description = message.body();
let description_without_prefix = description
.strip_prefix("SyntaxError: ")
.unwrap_or(description);
@@ -123,7 +123,7 @@ impl Serialize for SerializedMessages<'_> {
}
/// Generate a unique fingerprint to identify a violation.
fn fingerprint(message: &OldDiagnostic, project_path: &str, salt: u64) -> u64 {
fn fingerprint(message: &Message, project_path: &str, salt: u64) -> u64 {
let mut hasher = DefaultHasher::new();
salt.hash(&mut hasher);
@@ -139,13 +139,13 @@ mod tests {
use crate::message::GitlabEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = GitlabEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(redact_fingerprint(&content));
}
@@ -153,7 +153,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = GitlabEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(redact_fingerprint(&content));
}

View File

@@ -11,7 +11,7 @@ use crate::fs::relativize_path;
use crate::message::diff::calculate_print_width;
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
use crate::message::{
Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename,
Emitter, EmitterContext, Message, MessageWithLocation, group_messages_by_filename,
};
use crate::settings::types::UnsafeFixes;
@@ -46,10 +46,10 @@ impl Emitter for GroupedEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for (filename, messages) in group_diagnostics_by_filename(diagnostics) {
for (filename, messages) in group_messages_by_filename(messages) {
// Compute the maximum number of digits in the row and column, for messages in
// this file.
@@ -207,14 +207,14 @@ mod tests {
use crate::message::GroupedEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
use crate::settings::types::UnsafeFixes;
#[test]
fn default() {
let mut emitter = GroupedEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -222,7 +222,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = GroupedEmitter::default();
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}
@@ -230,7 +230,7 @@ mod tests {
#[test]
fn show_source() {
let mut emitter = GroupedEmitter::default().with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -240,7 +240,7 @@ mod tests {
let mut emitter = GroupedEmitter::default()
.with_show_fix_status(true)
.with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -251,7 +251,7 @@ mod tests {
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}

View File

@@ -9,7 +9,7 @@ use ruff_source_file::{LineColumn, OneIndexed, SourceCode};
use ruff_text_size::Ranged;
use crate::Edit;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
#[derive(Default)]
pub struct JsonEmitter;
@@ -18,23 +18,17 @@ impl Emitter for JsonEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(
writer,
&ExpandedMessages {
diagnostics,
context,
},
)?;
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages, context })?;
Ok(())
}
}
struct ExpandedMessages<'a> {
diagnostics: &'a [OldDiagnostic],
messages: &'a [Message],
context: &'a EmitterContext<'a>,
}
@@ -43,9 +37,9 @@ impl Serialize for ExpandedMessages<'_> {
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
for message in self.diagnostics {
for message in self.messages {
let value = message_to_json_value(message, self.context);
s.serialize_element(&value)?;
}
@@ -54,7 +48,7 @@ impl Serialize for ExpandedMessages<'_> {
}
}
pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterContext) -> Value {
pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) -> Value {
let source_file = message.source_file();
let source_code = source_file.to_source_code();
let notebook_index = context.notebook_index(&message.filename());
@@ -186,14 +180,14 @@ mod tests {
use crate::message::JsonEmitter;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_diagnostics,
create_notebook_diagnostics, create_syntax_error_diagnostics,
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = JsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -201,7 +195,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = JsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}
@@ -209,9 +203,8 @@ mod tests {
#[test]
fn notebook_output() {
let mut emitter = JsonEmitter;
let (diagnostics, notebook_indexes) = create_notebook_diagnostics();
let content =
capture_emitter_notebook_output(&mut emitter, &diagnostics, &notebook_indexes);
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);
}

View File

@@ -1,7 +1,7 @@
use std::io::Write;
use crate::message::json::message_to_json_value;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
#[derive(Default)]
pub struct JsonLinesEmitter;
@@ -10,11 +10,11 @@ impl Emitter for JsonLinesEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
serde_json::to_writer(&mut *writer, &message_to_json_value(diagnostic, context))?;
for message in messages {
serde_json::to_writer(&mut *writer, &message_to_json_value(message, context))?;
writer.write_all(b"\n")?;
}
Ok(())
@@ -27,14 +27,14 @@ mod tests {
use crate::message::json_lines::JsonLinesEmitter;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_diagnostics,
create_notebook_diagnostics, create_syntax_error_diagnostics,
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = JsonLinesEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -42,7 +42,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = JsonLinesEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}
@@ -50,7 +50,7 @@ mod tests {
#[test]
fn notebook_output() {
let mut emitter = JsonLinesEmitter;
let (messages, notebook_indexes) = create_notebook_diagnostics();
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);

View File

@@ -6,7 +6,7 @@ use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, X
use ruff_source_file::LineColumn;
use crate::message::{
Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename,
Emitter, EmitterContext, Message, MessageWithLocation, group_messages_by_filename,
};
#[derive(Default)]
@@ -16,12 +16,12 @@ impl Emitter for JunitEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
let mut report = Report::new("ruff");
if diagnostics.is_empty() {
if messages.is_empty() {
let mut test_suite = TestSuite::new("ruff");
test_suite
.extra
@@ -31,7 +31,7 @@ impl Emitter for JunitEmitter {
test_suite.add_test_case(case);
report.add_test_suite(test_suite);
} else {
for (filename, messages) in group_diagnostics_by_filename(diagnostics) {
for (filename, messages) in group_messages_by_filename(messages) {
let mut test_suite = TestSuite::new(&filename);
test_suite
.extra
@@ -97,13 +97,13 @@ mod tests {
use crate::message::JunitEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = JunitEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -111,7 +111,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = JunitEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -1,6 +1,5 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::io::Write;
use std::ops::Deref;
@@ -24,11 +23,11 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
pub use sarif::SarifEmitter;
pub use text::TextEmitter;
use crate::Fix;
use crate::Locator;
use crate::codes::NoqaCode;
use crate::logging::DisplayParseErrorType;
use crate::registry::Rule;
use crate::{Locator, Violation};
use crate::{Fix, OldDiagnostic};
mod azure;
mod diff;
@@ -43,34 +42,33 @@ mod rdjson;
mod sarif;
mod text;
/// `OldDiagnostic` represents either a diagnostic message corresponding to a rule violation or a
/// syntax error message.
/// Message represents either a diagnostic message corresponding to a rule violation or a syntax
/// error message.
///
/// All of the information for syntax errors is captured in the underlying [`db::Diagnostic`], while
/// rule violations can have the additional optional fields like fixes, suggestions, and (parent)
/// `noqa` offsets.
///
/// For diagnostic messages, the [`db::Diagnostic`]'s primary message contains the
/// [`OldDiagnostic::body`], and the primary annotation optionally contains the suggestion
/// accompanying a fix. The `db::Diagnostic::id` field contains the kebab-case lint name derived
/// from the `Rule`.
/// [`OldDiagnostic::body`], and the primary annotation optionally contains the suggestion accompanying
/// a fix. The `db::Diagnostic::id` field contains the kebab-case lint name derived from the `Rule`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OldDiagnostic {
pub struct Message {
pub diagnostic: db::Diagnostic,
// these fields are specific to rule violations
pub fix: Option<Fix>,
pub parent: Option<TextSize>,
pub(crate) noqa_offset: Option<TextSize>,
pub(crate) noqa_code: Option<NoqaCode>,
noqa_code: Option<NoqaCode>,
}
impl OldDiagnostic {
impl Message {
pub fn syntax_error(
message: impl Display,
message: impl std::fmt::Display,
range: TextRange,
file: SourceFile,
) -> OldDiagnostic {
) -> Message {
let mut diag = db::Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
let span = Span::from(file).with_range(range);
diag.annotate(Annotation::primary(span));
@@ -84,20 +82,16 @@ impl OldDiagnostic {
}
#[expect(clippy::too_many_arguments)]
pub fn lint<B, S>(
body: B,
suggestion: Option<S>,
pub fn diagnostic(
body: String,
suggestion: Option<String>,
range: TextRange,
fix: Option<Fix>,
parent: Option<TextSize>,
file: SourceFile,
noqa_offset: Option<TextSize>,
rule: Rule,
) -> OldDiagnostic
where
B: Display,
S: Display,
{
) -> Message {
let mut diagnostic = db::Diagnostic::new(
DiagnosticId::Lint(LintName::of(rule.into())),
Severity::Error,
@@ -110,7 +104,7 @@ impl OldDiagnostic {
}
diagnostic.annotate(annotation);
OldDiagnostic {
Message {
diagnostic,
fix,
parent,
@@ -119,12 +113,35 @@ impl OldDiagnostic {
}
}
/// Create an [`OldDiagnostic`] from the given [`ParseError`].
/// Create a [`Message`] from the given [`OldDiagnostic`] corresponding to a rule violation.
pub fn from_diagnostic(diagnostic: OldDiagnostic, noqa_offset: Option<TextSize>) -> Message {
let OldDiagnostic {
body,
suggestion,
range,
fix,
parent,
rule,
file,
} = diagnostic;
Self::diagnostic(
body,
suggestion,
range,
fix,
parent,
file,
noqa_offset,
rule,
)
}
/// Create a [`Message`] from the given [`ParseError`].
pub fn from_parse_error(
parse_error: &ParseError,
locator: &Locator,
file: SourceFile,
) -> OldDiagnostic {
) -> Message {
// Try to create a non-empty range so that the diagnostic can print a caret at the right
// position. This requires that we retrieve the next character, if any, and take its length
// to maintain char-boundaries.
@@ -134,7 +151,7 @@ impl OldDiagnostic {
.next()
.map_or(TextSize::new(0), TextLen::text_len);
OldDiagnostic::syntax_error(
Message::syntax_error(
format_args!(
"SyntaxError: {}",
DisplayParseErrorType::new(&parse_error.error)
@@ -144,105 +161,30 @@ impl OldDiagnostic {
)
}
/// Create an [`OldDiagnostic`] from the given [`UnsupportedSyntaxError`].
/// Create a [`Message`] from the given [`UnsupportedSyntaxError`].
pub fn from_unsupported_syntax_error(
unsupported_syntax_error: &UnsupportedSyntaxError,
file: SourceFile,
) -> OldDiagnostic {
OldDiagnostic::syntax_error(
) -> Message {
Message::syntax_error(
format_args!("SyntaxError: {unsupported_syntax_error}"),
unsupported_syntax_error.range,
file,
)
}
/// Create an [`OldDiagnostic`] from the given [`SemanticSyntaxError`].
/// Create a [`Message`] from the given [`SemanticSyntaxError`].
pub fn from_semantic_syntax_error(
semantic_syntax_error: &SemanticSyntaxError,
file: SourceFile,
) -> OldDiagnostic {
OldDiagnostic::syntax_error(
) -> Message {
Message::syntax_error(
format_args!("SyntaxError: {semantic_syntax_error}"),
semantic_syntax_error.range,
file,
)
}
// TODO(brent) We temporarily allow this to avoid updating all of the call sites to add
// references. I expect this method to go away or change significantly with the rest of the
// diagnostic refactor, but if it still exists in this form at the end of the refactor, we
// should just update the call sites.
#[expect(clippy::needless_pass_by_value)]
pub fn new<T: Violation>(kind: T, range: TextRange, file: &SourceFile) -> Self {
Self::lint(
Violation::message(&kind),
Violation::fix_title(&kind),
range,
None,
None,
file.clone(),
None,
T::rule(),
)
}
/// Consumes `self` and returns a new `Diagnostic` with the given `fix`.
#[inline]
#[must_use]
pub fn with_fix(mut self, fix: Fix) -> Self {
self.set_fix(fix);
self
}
/// Set the [`Fix`] used to fix the diagnostic.
#[inline]
pub fn set_fix(&mut self, fix: Fix) {
self.fix = Some(fix);
}
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
/// Otherwise, log the error.
#[inline]
pub fn try_set_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Fix>) {
match func() {
Ok(fix) => self.fix = Some(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 fn try_set_optional_fix(&mut self, func: impl FnOnce() -> anyhow::Result<Option<Fix>>) {
match func() {
Ok(None) => {}
Ok(Some(fix)) => self.fix = Some(fix),
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Consumes `self` and returns a new `Diagnostic` with the given parent node.
#[inline]
#[must_use]
pub fn with_parent(mut self, parent: TextSize) -> Self {
self.set_parent(parent);
self
}
/// Set the location of the diagnostic's parent node.
#[inline]
pub fn set_parent(&mut self, parent: TextSize) {
self.parent = Some(parent);
}
/// Consumes `self` and returns a new `Diagnostic` with the given noqa offset.
#[inline]
#[must_use]
pub fn with_noqa_offset(mut self, noqa_offset: TextSize) -> Self {
self.noqa_offset = Some(noqa_offset);
self
}
/// Returns `true` if `self` is a syntax error message.
pub fn is_syntax_error(&self) -> bool {
self.diagnostic.id().is_invalid_syntax()
@@ -272,12 +214,12 @@ impl OldDiagnostic {
self.noqa_offset
}
/// Returns the [`Fix`] for the diagnostic, if there is any.
/// Returns the [`Fix`] for the message, if there is any.
pub fn fix(&self) -> Option<&Fix> {
self.fix.as_ref()
}
/// Returns `true` if the diagnostic contains a [`Fix`].
/// Returns `true` if the message contains a [`Fix`].
pub fn fixable(&self) -> bool {
self.fix().is_some()
}
@@ -336,19 +278,19 @@ impl OldDiagnostic {
}
}
impl Ord for OldDiagnostic {
impl Ord for Message {
fn cmp(&self, other: &Self) -> Ordering {
(self.source_file(), self.start()).cmp(&(other.source_file(), other.start()))
}
}
impl PartialOrd for OldDiagnostic {
impl PartialOrd for Message {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ranged for OldDiagnostic {
impl Ranged for Message {
fn range(&self) -> TextRange {
self.diagnostic
.expect_primary_span()
@@ -358,43 +300,41 @@ impl Ranged for OldDiagnostic {
}
struct MessageWithLocation<'a> {
message: &'a OldDiagnostic,
message: &'a Message,
start_location: LineColumn,
}
impl Deref for MessageWithLocation<'_> {
type Target = OldDiagnostic;
type Target = Message;
fn deref(&self) -> &Self::Target {
self.message
}
}
fn group_diagnostics_by_filename(
diagnostics: &[OldDiagnostic],
) -> BTreeMap<String, Vec<MessageWithLocation>> {
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<String, Vec<MessageWithLocation>> {
let mut grouped_messages = BTreeMap::default();
for diagnostic in diagnostics {
for message in messages {
grouped_messages
.entry(diagnostic.filename().to_string())
.entry(message.filename().to_string())
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message: diagnostic,
start_location: diagnostic.compute_start_location(),
message,
start_location: message.compute_start_location(),
});
}
grouped_messages
}
/// Display format for [`OldDiagnostic`]s.
/// Display format for a [`Message`]s.
///
/// The emitter serializes a slice of [`OldDiagnostic`]s and writes them to a [`Write`].
/// The emitter serializes a slice of [`Message`]'s and writes them to a [`Write`].
pub trait Emitter {
/// Serializes the `diagnostics` and writes the output to `writer`.
/// Serializes the `messages` and writes the output to `writer`.
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()>;
}
@@ -431,9 +371,9 @@ mod tests {
use ruff_text_size::{TextRange, TextSize};
use crate::Locator;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
pub(super) fn create_syntax_error_diagnostics() -> Vec<OldDiagnostic> {
pub(super) fn create_syntax_error_messages() -> Vec<Message> {
let source = r"from os import
if call(foo
@@ -446,12 +386,12 @@ if call(foo
.errors()
.iter()
.map(|parse_error| {
OldDiagnostic::from_parse_error(parse_error, &locator, source_file.clone())
Message::from_parse_error(parse_error, &locator, source_file.clone())
})
.collect()
}
pub(super) fn create_diagnostics() -> Vec<OldDiagnostic> {
pub(super) fn create_messages() -> Vec<Message> {
let fib = r#"import os
@@ -469,9 +409,9 @@ def fibonacci(n):
let fib_source = SourceFileBuilder::new("fib.py", fib).finish();
let unused_import_start = TextSize::from(7);
let unused_import = OldDiagnostic::lint(
"`os` imported but unused",
Some("Remove unused import: `os`"),
let unused_import = Message::diagnostic(
"`os` imported but unused".to_string(),
Some("Remove unused import: `os`".to_string()),
TextRange::new(unused_import_start, TextSize::from(9)),
Some(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(0),
@@ -484,9 +424,9 @@ def fibonacci(n):
);
let unused_variable_start = TextSize::from(94);
let unused_variable = OldDiagnostic::lint(
"Local variable `x` is assigned to but never used",
Some("Remove assignment to unused variable `x`"),
let unused_variable = Message::diagnostic(
"Local variable `x` is assigned to but never used".to_string(),
Some("Remove assignment to unused variable `x`".to_string()),
TextRange::new(unused_variable_start, TextSize::from(95)),
Some(Fix::unsafe_edit(Edit::deletion(
TextSize::from(94),
@@ -501,9 +441,9 @@ def fibonacci(n):
let file_2 = r"if a == 1: pass";
let undefined_name_start = TextSize::from(3);
let undefined_name = OldDiagnostic::lint(
"Undefined name `a`",
Option::<&'static str>::None,
let undefined_name = Message::diagnostic(
"Undefined name `a`".to_string(),
None,
TextRange::new(undefined_name_start, TextSize::from(4)),
None,
None,
@@ -515,8 +455,7 @@ def fibonacci(n):
vec![unused_import, unused_variable, undefined_name]
}
pub(super) fn create_notebook_diagnostics()
-> (Vec<OldDiagnostic>, FxHashMap<String, NotebookIndex>) {
pub(super) fn create_notebook_messages() -> (Vec<Message>, FxHashMap<String, NotebookIndex>) {
let notebook = r"# cell 1
import os
# cell 2
@@ -532,9 +471,9 @@ def foo():
let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish();
let unused_import_os_start = TextSize::from(16);
let unused_import_os = OldDiagnostic::lint(
"`os` imported but unused",
Some("Remove unused import: `os`"),
let unused_import_os = Message::diagnostic(
"`os` imported but unused".to_string(),
Some("Remove unused import: `os`".to_string()),
TextRange::new(unused_import_os_start, TextSize::from(18)),
Some(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(9),
@@ -547,9 +486,9 @@ def foo():
);
let unused_import_math_start = TextSize::from(35);
let unused_import_math = OldDiagnostic::lint(
"`math` imported but unused",
Some("Remove unused import: `math`"),
let unused_import_math = Message::diagnostic(
"`math` imported but unused".to_string(),
Some("Remove unused import: `math`".to_string()),
TextRange::new(unused_import_math_start, TextSize::from(39)),
Some(Fix::safe_edit(Edit::range_deletion(TextRange::new(
TextSize::from(28),
@@ -562,9 +501,9 @@ def foo():
);
let unused_variable_start = TextSize::from(98);
let unused_variable = OldDiagnostic::lint(
"Local variable `x` is assigned to but never used",
Some("Remove assignment to unused variable `x`"),
let unused_variable = Message::diagnostic(
"Local variable `x` is assigned to but never used".to_string(),
Some("Remove assignment to unused variable `x`".to_string()),
TextRange::new(unused_variable_start, TextSize::from(99)),
Some(Fix::unsafe_edit(Edit::deletion(
TextSize::from(94),
@@ -615,24 +554,24 @@ def foo():
pub(super) fn capture_emitter_output(
emitter: &mut dyn Emitter,
diagnostics: &[OldDiagnostic],
messages: &[Message],
) -> String {
let notebook_indexes = FxHashMap::default();
let context = EmitterContext::new(&notebook_indexes);
let mut output: Vec<u8> = Vec::new();
emitter.emit(&mut output, diagnostics, &context).unwrap();
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
}
pub(super) fn capture_emitter_notebook_output(
emitter: &mut dyn Emitter,
diagnostics: &[OldDiagnostic],
messages: &[Message],
notebook_indexes: &FxHashMap<String, NotebookIndex>,
) -> String {
let context = EmitterContext::new(notebook_indexes);
let mut output: Vec<u8> = Vec::new();
emitter.emit(&mut output, diagnostics, &context).unwrap();
emitter.emit(&mut output, messages, &context).unwrap();
String::from_utf8(output).expect("Output to be valid UTF-8")
}

View File

@@ -3,7 +3,7 @@ use std::io::Write;
use ruff_source_file::OneIndexed;
use crate::fs::relativize_path;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
/// Generate violations in Pylint format.
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
@@ -14,28 +14,28 @@ impl Emitter for PylintEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for diagnostic in diagnostics {
let row = if context.is_notebook(&diagnostic.filename()) {
for message in messages {
let row = if context.is_notebook(&message.filename()) {
// We can't give a reasonable location for the structured formats,
// so we show one that's clearly a fallback
OneIndexed::from_zero_indexed(0)
} else {
diagnostic.compute_start_location().line
message.compute_start_location().line
};
let body = if let Some(code) = diagnostic.noqa_code() {
format!("[{code}] {body}", body = diagnostic.body())
let body = if let Some(code) = message.noqa_code() {
format!("[{code}] {body}", body = message.body())
} else {
diagnostic.body().to_string()
message.body().to_string()
};
writeln!(
writer,
"{path}:{row}: {body}",
path = relativize_path(&*diagnostic.filename()),
path = relativize_path(&*message.filename()),
)?;
}
@@ -49,13 +49,13 @@ mod tests {
use crate::message::PylintEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = PylintEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -63,7 +63,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = PylintEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -8,7 +8,7 @@ use ruff_source_file::SourceCode;
use ruff_text_size::Ranged;
use crate::Edit;
use crate::message::{Emitter, EmitterContext, LineColumn, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, LineColumn, Message};
#[derive(Default)]
pub struct RdjsonEmitter;
@@ -17,7 +17,7 @@ impl Emitter for RdjsonEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
_context: &EmitterContext,
) -> anyhow::Result<()> {
serde_json::to_writer_pretty(
@@ -28,7 +28,7 @@ impl Emitter for RdjsonEmitter {
"url": "https://docs.astral.sh/ruff",
},
"severity": "warning",
"diagnostics": &ExpandedMessages{ diagnostics }
"diagnostics": &ExpandedMessages{ messages }
}),
)?;
@@ -37,7 +37,7 @@ impl Emitter for RdjsonEmitter {
}
struct ExpandedMessages<'a> {
diagnostics: &'a [OldDiagnostic],
messages: &'a [Message],
}
impl Serialize for ExpandedMessages<'_> {
@@ -45,9 +45,9 @@ impl Serialize for ExpandedMessages<'_> {
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
for message in self.diagnostics {
for message in self.messages {
let value = message_to_rdjson_value(message);
s.serialize_element(&value)?;
}
@@ -56,7 +56,7 @@ impl Serialize for ExpandedMessages<'_> {
}
}
fn message_to_rdjson_value(message: &OldDiagnostic) -> Value {
fn message_to_rdjson_value(message: &Message) -> Value {
let source_file = message.source_file();
let source_code = source_file.to_source_code();
@@ -121,13 +121,13 @@ mod tests {
use crate::message::RdjsonEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
#[test]
fn output() {
let mut emitter = RdjsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -135,7 +135,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = RdjsonEmitter;
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -10,7 +10,7 @@ use ruff_source_file::OneIndexed;
use crate::VERSION;
use crate::codes::NoqaCode;
use crate::fs::normalize_path;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
use crate::registry::{Linter, RuleNamespace};
pub struct SarifEmitter;
@@ -19,10 +19,10 @@ impl Emitter for SarifEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
_context: &EmitterContext,
) -> Result<()> {
let results = diagnostics
let results = messages
.iter()
.map(SarifResult::from_message)
.collect::<Result<Vec<_>>>()?;
@@ -124,7 +124,7 @@ struct SarifResult {
impl SarifResult {
#[cfg(not(target_arch = "wasm32"))]
fn from_message(message: &OldDiagnostic) -> Result<Self> {
fn from_message(message: &Message) -> Result<Self> {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let path = normalize_path(&*message.filename());
@@ -144,7 +144,7 @@ impl SarifResult {
#[cfg(target_arch = "wasm32")]
#[expect(clippy::unnecessary_wraps)]
fn from_message(message: &OldDiagnostic) -> Result<Self> {
fn from_message(message: &Message) -> Result<Self> {
let start_location = message.compute_start_location();
let end_location = message.compute_end_location();
let path = normalize_path(&*message.filename());
@@ -194,12 +194,12 @@ impl Serialize for SarifResult {
mod tests {
use crate::message::SarifEmitter;
use crate::message::tests::{
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
capture_emitter_output, create_messages, create_syntax_error_messages,
};
fn get_output() -> String {
let mut emitter = SarifEmitter {};
capture_emitter_output(&mut emitter, &create_diagnostics())
capture_emitter_output(&mut emitter, &create_messages())
}
#[test]
@@ -211,7 +211,7 @@ mod tests {
#[test]
fn valid_syntax_error_json() {
let mut emitter = SarifEmitter {};
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
serde_json::from_str::<serde_json::Value>(&content).unwrap();
}

View File

@@ -14,7 +14,7 @@ use crate::Locator;
use crate::fs::relativize_path;
use crate::line_width::{IndentWidth, LineWidthBuilder};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
use crate::message::{Emitter, EmitterContext, Message};
use crate::settings::types::UnsafeFixes;
bitflags! {
@@ -66,10 +66,10 @@ impl Emitter for TextEmitter {
fn emit(
&mut self,
writer: &mut dyn Write,
diagnostics: &[OldDiagnostic],
messages: &[Message],
context: &EmitterContext,
) -> anyhow::Result<()> {
for message in diagnostics {
for message in messages {
write!(
writer,
"{path}{sep}",
@@ -140,7 +140,7 @@ impl Emitter for TextEmitter {
}
pub(super) struct RuleCodeAndBody<'a> {
pub(crate) message: &'a OldDiagnostic,
pub(crate) message: &'a Message,
pub(crate) show_fix_status: bool,
pub(crate) unsafe_fixes: UnsafeFixes,
}
@@ -178,7 +178,7 @@ impl Display for RuleCodeAndBody<'_> {
}
pub(super) struct MessageCodeFrame<'a> {
pub(crate) message: &'a OldDiagnostic,
pub(crate) message: &'a Message,
pub(crate) notebook_index: Option<&'a NotebookIndex>,
}
@@ -409,15 +409,15 @@ mod tests {
use crate::message::TextEmitter;
use crate::message::tests::{
capture_emitter_notebook_output, capture_emitter_output, create_diagnostics,
create_notebook_diagnostics, create_syntax_error_diagnostics,
capture_emitter_notebook_output, capture_emitter_output, create_messages,
create_notebook_messages, create_syntax_error_messages,
};
use crate::settings::types::UnsafeFixes;
#[test]
fn default() {
let mut emitter = TextEmitter::default().with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -427,7 +427,7 @@ mod tests {
let mut emitter = TextEmitter::default()
.with_show_fix_status(true)
.with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -438,7 +438,7 @@ mod tests {
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_messages());
assert_snapshot!(content);
}
@@ -449,7 +449,7 @@ mod tests {
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let (messages, notebook_indexes) = create_notebook_diagnostics();
let (messages, notebook_indexes) = create_notebook_messages();
let content = capture_emitter_notebook_output(&mut emitter, &messages, &notebook_indexes);
assert_snapshot!(content);
@@ -458,7 +458,7 @@ mod tests {
#[test]
fn syntax_errors() {
let mut emitter = TextEmitter::default().with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
let content = capture_emitter_output(&mut emitter, &create_syntax_error_messages());
assert_snapshot!(content);
}

View File

@@ -18,7 +18,7 @@ use crate::Edit;
use crate::Locator;
use crate::codes::NoqaCode;
use crate::fs::relativize_path;
use crate::message::OldDiagnostic;
use crate::message::Message;
use crate::registry::Rule;
use crate::rule_redirects::get_redirect_target;
@@ -29,7 +29,7 @@ use crate::rule_redirects::get_redirect_target;
/// simultaneously.
pub fn generate_noqa_edits(
path: &Path,
diagnostics: &[OldDiagnostic],
messages: &[Message],
locator: &Locator,
comment_ranges: &CommentRanges,
external: &[String],
@@ -39,7 +39,7 @@ pub fn generate_noqa_edits(
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
let exemption = FileExemption::from(&file_directives);
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
let comments = find_noqa_comments(messages, locator, &exemption, &directives, noqa_line_for);
build_noqa_edits_by_diagnostic(comments, locator, line_ending)
}
@@ -707,7 +707,7 @@ impl Error for LexicalError {}
/// Adds noqa comments to suppress all messages of a file.
pub(crate) fn add_noqa(
path: &Path,
diagnostics: &[OldDiagnostic],
messages: &[Message],
locator: &Locator,
comment_ranges: &CommentRanges,
external: &[String],
@@ -716,7 +716,7 @@ pub(crate) fn add_noqa(
) -> Result<usize> {
let (count, output) = add_noqa_inner(
path,
diagnostics,
messages,
locator,
comment_ranges,
external,
@@ -730,7 +730,7 @@ pub(crate) fn add_noqa(
fn add_noqa_inner(
path: &Path,
diagnostics: &[OldDiagnostic],
messages: &[Message],
locator: &Locator,
comment_ranges: &CommentRanges,
external: &[String],
@@ -745,7 +745,7 @@ fn add_noqa_inner(
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
let comments = find_noqa_comments(messages, locator, &exemption, &directives, noqa_line_for);
let edits = build_noqa_edits_by_line(comments, locator, line_ending);
@@ -835,7 +835,7 @@ struct NoqaComment<'a> {
}
fn find_noqa_comments<'a>(
diagnostics: &'a [OldDiagnostic],
messages: &'a [Message],
locator: &'a Locator,
exemption: &'a FileExemption,
directives: &'a NoqaDirectives,
@@ -845,7 +845,7 @@ fn find_noqa_comments<'a>(
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
// Mark any non-ignored diagnostics.
for message in diagnostics {
for message in messages {
let Some(code) = message.noqa_code() else {
comments_by_line.push(None);
continue;
@@ -1219,8 +1219,9 @@ mod tests {
use ruff_python_trivia::CommentRanges;
use ruff_source_file::{LineEnding, SourceFileBuilder};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::message::Message;
use crate::noqa::{
Directive, LexicalError, NoqaLexerOutput, NoqaMapping, add_noqa_inner, lex_codes,
lex_file_exemption, lex_inline_noqa,
@@ -1246,6 +1247,12 @@ mod tests {
}
}
/// Create a [`Message`] with a placeholder filename and rule code from `diagnostic`.
fn message_from_diagnostic(diagnostic: OldDiagnostic) -> Message {
let noqa_offset = diagnostic.start();
Message::from_diagnostic(diagnostic, Some(noqa_offset))
}
#[test]
fn noqa_lex_codes() {
let source = " F401,,F402F403 # and so on";
@@ -2833,7 +2840,8 @@ mod tests {
},
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
)];
)]
.map(message_from_diagnostic);
let contents = "x = 1";
let noqa_line_for = NoqaMapping::default();
@@ -2863,7 +2871,8 @@ mod tests {
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
];
]
.map(message_from_diagnostic);
let contents = "x = 1 # noqa: E741\n";
let noqa_line_for = NoqaMapping::default();
let comment_ranges =
@@ -2894,7 +2903,8 @@ mod tests {
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
];
]
.map(message_from_diagnostic);
let contents = "x = 1 # noqa";
let noqa_line_for = NoqaMapping::default();
let comment_ranges =
@@ -2930,7 +2940,8 @@ print(
PrintfStringFormatting,
TextRange::new(12.into(), 79.into()),
&source_file,
)];
)]
.map(message_from_diagnostic);
let comment_ranges = CommentRanges::default();
let edits = generate_noqa_edits(
path,
@@ -2963,7 +2974,8 @@ bar =
UselessSemicolon,
TextRange::new(4.into(), 5.into()),
&source_file,
)];
)]
.map(message_from_diagnostic);
let noqa_line_for = NoqaMapping::default();
let comment_ranges = CommentRanges::default();
let edits = generate_noqa_edits(

View File

@@ -77,10 +77,3 @@ pub(crate) const fn is_multiple_with_statements_fix_safe_enabled(
) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18400
pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

View File

@@ -7,14 +7,12 @@ use ruff_source_file::SourceFile;
use crate::IOError;
use crate::OldDiagnostic;
use crate::message::Message;
use crate::registry::Rule;
use crate::rules::ruff::rules::InvalidPyprojectToml;
use crate::settings::LinterSettings;
pub fn lint_pyproject_toml(
source_file: &SourceFile,
settings: &LinterSettings,
) -> Vec<OldDiagnostic> {
pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings) -> Vec<Message> {
let Some(err) = toml::from_str::<PyProjectToml>(source_file.source_text()).err() else {
return Vec::default();
};
@@ -33,7 +31,7 @@ pub fn lint_pyproject_toml(
if settings.rules.enabled(Rule::IOError) {
let diagnostic =
OldDiagnostic::new(IOError { message }, TextRange::default(), source_file);
messages.push(diagnostic);
messages.push(Message::from_diagnostic(diagnostic, None));
} else {
warn!(
"{}{}{} {message}",
@@ -59,7 +57,7 @@ pub fn lint_pyproject_toml(
range,
source_file,
);
messages.push(diagnostic);
messages.push(Message::from_diagnostic(diagnostic, None));
}
messages

View File

@@ -215,6 +215,12 @@ pub enum Linter {
}
pub trait RuleNamespace: Sized {
/// Returns the prefix that every single code that ruff uses to identify
/// rules from this linter starts with. In the case that multiple
/// `#[prefix]`es are configured for the variant in the `Linter` enum
/// definition this is the empty string.
fn common_prefix(&self) -> &'static str;
/// Attempts to parse the given rule code. If the prefix is recognized
/// returns the respective variant along with the code with the common
/// prefix stripped.

View File

@@ -265,6 +265,7 @@ mod schema {
use strum::IntoEnumIterator;
use crate::RuleSelector;
use crate::registry::RuleNamespace;
use crate::rule_selector::{Linter, RuleCodePrefix};
impl JsonSchema for RuleSelector {

View File

@@ -11,7 +11,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
use crate::{assert_messages, settings};
#[test_case(Rule::AirflowVariableNameTaskIdMismatch, Path::new("AIR001.py"))]
#[test_case(Rule::AirflowDagNoScheduleArgument, Path::new("AIR002.py"))]
@@ -58,7 +58,7 @@ mod tests {
Path::new("airflow").join(path).as_path(),
&settings::LinterSettings::for_rule(rule_code),
)?;
assert_diagnostics!(snapshot, diagnostics);
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -11,7 +11,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};
use crate::{assert_messages, settings};
#[test_case(Rule::CommentedOutCode, Path::new("ERA001.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
@@ -20,7 +20,7 @@ mod tests {
Path::new("eradicate").join(path).as_path(),
&settings::LinterSettings::for_rule(rule_code),
)?;
assert_diagnostics!(snapshot, diagnostics);
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

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