Compare commits
2 Commits
micha/add-
...
ibraheem/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
541b2096b6 | ||
|
|
d1f16703ab |
29
.github/renovate.json5
vendored
29
.github/renovate.json5
vendored
@@ -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: {
|
||||
|
||||
16
.github/workflows/build-binaries.yml
vendored
16
.github/workflows/build-binaries.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/build-docker.yml
vendored
8
.github/workflows/build-docker.yml
vendored
@@ -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
|
||||
|
||||
44
.github/workflows/ci.yaml
vendored
44
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
|
||||
4
.github/workflows/daily_fuzz.yaml
vendored
4
.github/workflows/daily_fuzz.yaml
vendored
@@ -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,
|
||||
|
||||
2
.github/workflows/mypy_primer.yaml
vendored
2
.github/workflows/mypy_primer.yaml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -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-*
|
||||
|
||||
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
94
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
94
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -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
16
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -52,6 +52,6 @@ pub(crate) fn check_stdin(
|
||||
noqa,
|
||||
fix_mode,
|
||||
)?;
|
||||
diagnostics.inner.sort_unstable();
|
||||
diagnostics.messages.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)],)
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))}"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(): ...
|
||||
|
||||
@@ -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): ...
|
||||
|
||||
@@ -105,8 +105,3 @@ if future.exception():
|
||||
future = executor.submit(float, "a")
|
||||
if future.exception():
|
||||
raise future.Exception()
|
||||
|
||||
|
||||
raise TypeError(
|
||||
# comment
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()):
|
||||
...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
):
|
||||
...
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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
|
||||
):
|
||||
...
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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__", {})
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¶meter.name,
|
||||
parameter.name.range(),
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BuiltinArgumentShadowing) {
|
||||
if checker.enabled(Rule::BuiltinArgumentShadowing) {
|
||||
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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![]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
104
crates/ruff_linter/src/diagnostic.rs
Normal file
104
crates/ruff_linter/src/diagnostic.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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, ¬ebook_indexes);
|
||||
let (messages, notebook_indexes) = create_notebook_messages();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
@@ -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, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(¬ebook_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")
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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, ¬ebook_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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user