Compare commits
3 Commits
jack/nonlo
...
alex/known
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea95e9f24 | ||
|
|
040bb0f932 | ||
|
|
0150ac964d |
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
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@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
|
||||
64
.github/workflows/ci.yaml
vendored
64
.github/workflows/ci.yaml
vendored
@@ -214,7 +214,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
@@ -234,17 +234,17 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -292,17 +292,17 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -321,11 +321,11 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -348,7 +348,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
@@ -377,7 +377,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
with:
|
||||
file: "Cargo.toml"
|
||||
field: "workspace.package.rust-version"
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
@@ -408,11 +408,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -432,7 +432,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "fuzz -> target"
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -460,7 +460,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -494,7 +494,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -661,7 +661,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -708,11 +708,11 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
with:
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
@@ -731,8 +731,8 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
@@ -765,7 +765,7 @@ jobs:
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
@@ -774,7 +774,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -804,7 +804,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run checks"
|
||||
@@ -874,7 +874,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
@@ -905,14 +905,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -938,14 +938,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
4
.github/workflows/daily_fuzz.yaml
vendored
4
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,12 +34,12 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||
|
||||
85
.github/workflows/mypy_primer.yaml
vendored
85
.github/workflows/mypy_primer.yaml
vendored
@@ -12,7 +12,6 @@ on:
|
||||
- ".github/workflows/mypy_primer.yaml"
|
||||
- ".github/workflows/mypy_primer_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
@@ -38,9 +37,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
@@ -49,13 +48,45 @@ jobs:
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
echo ${{ github.event.number }} > ../pr-number
|
||||
|
||||
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
|
||||
mkdir -p ~/.config/ty
|
||||
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
|
||||
|
||||
echo "new commit"
|
||||
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b base_commit "$MERGE_BASE"
|
||||
echo "base commit"
|
||||
git rev-list --format=%s --max-count=1 base_commit
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@e5f55447969d33ae3c7ccdb183e2a37101867270" \
|
||||
mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker ty \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||
--output concise \
|
||||
--debug > mypy_primer.diff || [ $? -eq 1 ]
|
||||
|
||||
# Output diff with ANSI color codes
|
||||
cat mypy_primer.diff
|
||||
|
||||
# Remove ANSI color codes before uploading
|
||||
sed -ie 's/\x1b\[[0-9;]*m//g' mypy_primer.diff
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
@@ -68,41 +99,3 @@ jobs:
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
memory_usage:
|
||||
name: Run memory statistics
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
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@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
TY_MAX_PARALLELISM: 1 # for deterministic memory numbers
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/memory.txt
|
||||
DIFF_FILE: mypy_primer_memory.diff
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
path: mypy_primer_memory.diff
|
||||
|
||||
31
.github/workflows/mypy_primer_comment.yaml
vendored
31
.github/workflows/mypy_primer_comment.yaml
vendored
@@ -45,28 +45,15 @@ jobs:
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download mypy_primer memory results"
|
||||
id: download-mypy_primer_memory_diff
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
workflow: mypy_primer.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/mypy_primer_memory_diff
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Generate comment content
|
||||
id: generate-comment
|
||||
if: ${{ steps.download-mypy_primer_diff.outputs.found_artifact == 'true' && steps.download-mypy_primer_memory_diff.outputs.found_artifact == 'true' }}
|
||||
if: steps.download-mypy_primer_diff.outputs.found_artifact == 'true'
|
||||
run: |
|
||||
# Guard against malicious mypy_primer results that symlink to a secret
|
||||
# file on this runner
|
||||
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]] || [[ -L pr/mypy_primer_memory_diff/mypy_primer_memory.diff ]]
|
||||
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]]
|
||||
then
|
||||
echo "Error: mypy_primer.diff and mypy_primer_memory.diff cannot be a symlink"
|
||||
echo "Error: mypy_primer.diff cannot be a symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -87,18 +74,6 @@ jobs:
|
||||
echo 'No ecosystem changes detected ✅' >> comment.txt
|
||||
fi
|
||||
|
||||
if [ -s "pr/mypy_primer_memory_diff/mypy_primer_memory.diff" ]; then
|
||||
echo '<details>' >> comment.txt
|
||||
echo '<summary>Memory usage changes were detected when running on open source projects</summary>' >> comment.txt
|
||||
echo '' >> comment.txt
|
||||
echo '```diff' >> comment.txt
|
||||
cat pr/mypy_primer_memory_diff/mypy_primer_memory.diff >> comment.txt
|
||||
echo '```' >> comment.txt
|
||||
echo '</details>' >> comment.txt
|
||||
else
|
||||
echo 'No memory usage changes detected ✅' >> comment.txt
|
||||
fi
|
||||
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.txt >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
|
||||
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@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
80
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
80
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,7 +17,6 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
@@ -33,9 +32,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
@@ -64,75 +63,32 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
diff \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
--new new_commit \
|
||||
--output-old diagnostics-old.json \
|
||||
--output-new diagnostics-new.json
|
||||
analyze \
|
||||
--projects ruff/projects_old.txt \
|
||||
--commit old_commit \
|
||||
--output diagnostics_old.json
|
||||
|
||||
mkdir dist
|
||||
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 \
|
||||
diagnostics_old.json \
|
||||
diagnostics_new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output-html dist/diff.html
|
||||
--output-html diff.html
|
||||
|
||||
ecosystem-analyzer \
|
||||
generate-diff-statistics \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output diff-statistics.md
|
||||
|
||||
echo '## `ecosystem-analyzer` results' > comment.md
|
||||
echo >> comment.md
|
||||
cat diff-statistics.md >> comment.md
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)**" >> comment.md
|
||||
|
||||
- name: Upload comment
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: comment.md
|
||||
path: comment.md
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- name: Upload diagnostics diff
|
||||
- name: Upload HTML diff report
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: diff.html
|
||||
path: dist/diff.html
|
||||
path: diff.html
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
name: PR comment (ty ecosystem-analyzer)
|
||||
|
||||
on: # zizmor: ignore[dangerous-triggers]
|
||||
workflow_run:
|
||||
workflows: [ty ecosystem-analyzer]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_run_id:
|
||||
description: The ty ecosystem-analyzer workflow that triggers the workflow run
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download PR number
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Parse pull request number
|
||||
id: pr-number
|
||||
run: |
|
||||
if [[ -f pr-number ]]
|
||||
then
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download comment.md"
|
||||
id: download-comment
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
with:
|
||||
name: comment.md
|
||||
workflow: ty-ecosystem-analyzer.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/comment
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Generate comment content
|
||||
id: generate-comment
|
||||
if: ${{ steps.download-comment.outputs.found_artifact == 'true' }}
|
||||
run: |
|
||||
# Guard against malicious ty ecosystem-analyzer results that symlink to a secret
|
||||
# file on this runner
|
||||
if [[ -L pr/comment/comment.md ]]
|
||||
then
|
||||
echo "Error: comment.md cannot be a symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Note: this identifier is used to find the comment to update on subsequent runs
|
||||
echo '<!-- generated-comment ty ecosystem-analyzer -->' > comment.md
|
||||
echo >> comment.md
|
||||
cat pr/comment/comment.md >> comment.md
|
||||
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.md >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "<!-- generated-comment ty ecosystem-analyzer -->"
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
body-path: comment.md
|
||||
edit-mode: replace
|
||||
1
.github/zizmor.yml
vendored
1
.github/zizmor.yml
vendored
@@ -10,7 +10,6 @@ rules:
|
||||
ignore:
|
||||
- build-docker.yml
|
||||
- publish-playground.yml
|
||||
- ty-ecosystem-analyzer.yaml
|
||||
excessive-permissions:
|
||||
# it's hard to test what the impact of removing these ignores would be
|
||||
# without actually running the release workflow...
|
||||
|
||||
@@ -6,7 +6,7 @@ exclude: |
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty_python_semantic/resources/corpus/.*|
|
||||
crates/ty/docs/(configuration|rules|cli|environment).md|
|
||||
crates/ty/docs/(configuration|rules|cli).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
||||
@@ -67,7 +67,7 @@ repos:
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.34.0
|
||||
rev: v1.33.1
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -81,7 +81,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.2
|
||||
rev: v0.11.13
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -91,7 +91,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.6.2
|
||||
rev: v3.5.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
@@ -99,12 +99,12 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.11.0
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.33.2
|
||||
rev: 0.33.0
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
|
||||
136
CHANGELOG.md
136
CHANGELOG.md
@@ -1,137 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
|
||||
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
|
||||
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
|
||||
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
|
||||
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
|
||||
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
|
||||
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
|
||||
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
|
||||
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
|
||||
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
|
||||
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
|
||||
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
|
||||
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
|
||||
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
|
||||
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
|
||||
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
|
||||
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
|
||||
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
|
||||
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
|
||||
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
|
||||
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
|
||||
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
|
||||
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
|
||||
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
|
||||
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
|
||||
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
|
||||
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
|
||||
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
|
||||
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
|
||||
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
|
||||
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
|
||||
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
|
||||
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
|
||||
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
|
||||
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
|
||||
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
|
||||
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
|
||||
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
|
||||
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
|
||||
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
|
||||
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
|
||||
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
|
||||
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
|
||||
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
|
||||
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
|
||||
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
|
||||
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
|
||||
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
|
||||
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
|
||||
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
|
||||
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
|
||||
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
|
||||
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
|
||||
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
|
||||
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
|
||||
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
|
||||
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
|
||||
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
|
||||
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
|
||||
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
|
||||
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
|
||||
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
|
||||
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
|
||||
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
|
||||
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
|
||||
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
|
||||
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
|
||||
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
|
||||
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
|
||||
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
|
||||
|
||||
### Server
|
||||
|
||||
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
|
||||
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
|
||||
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
|
||||
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
|
||||
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
|
||||
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
|
||||
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
|
||||
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
|
||||
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
|
||||
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
|
||||
|
||||
## 0.12.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
|
||||
@@ -148,7 +16,7 @@ guide and overview of the changes!
|
||||
|
||||
- **New default Python version handling for syntax errors**
|
||||
|
||||
Ruff will default to the *latest* supported Python version (3.13) when
|
||||
Ruff will default to the _latest_ supported Python version (3.13) when
|
||||
checking for the version-related syntax errors mentioned above to prevent
|
||||
false positives in projects without a Python version configured. The default
|
||||
in all other cases, like applying lint rules, is unchanged and remains at the
|
||||
@@ -203,7 +71,7 @@ The following rules have been stabilized and are no longer in preview:
|
||||
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
|
||||
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
|
||||
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
|
||||
- [`non-pep604-annotation-optional`] (`UP045`)
|
||||
- [`non-pep604-annotation-optional`](https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional) (`UP045`)
|
||||
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
|
||||
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
|
||||
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
|
||||
|
||||
513
Cargo.lock
generated
513
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
32
Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# Please update rustfmt.toml when bumping the Rust edition
|
||||
edition = "2024"
|
||||
rust-version = "1.86"
|
||||
rust-version = "1.85"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -44,7 +44,6 @@ ty_ide = { path = "crates/ty_ide" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_static = { path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
ty_vendored = { path = "crates/ty_vendored" }
|
||||
|
||||
@@ -63,8 +62,8 @@ camino = { version = "1.1.7" }
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.6.0" }
|
||||
clearscreen = { version = "4.0.0" }
|
||||
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
|
||||
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
|
||||
divan = { package = "codspeed-divan-compat", version = "2.10.1" }
|
||||
codspeed-criterion-compat = { version = "2.6.0", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
@@ -80,12 +79,6 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.5.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
"compact-str",
|
||||
] }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
globwalk = { version = "0.9.1" }
|
||||
@@ -99,7 +92,7 @@ ignore = { version = "0.4.22" }
|
||||
imara-diff = { version = "0.1.5" }
|
||||
imperative = { version = "1.0.4" }
|
||||
indexmap = { version = "2.6.0" }
|
||||
indicatif = { version = "0.18.0" }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1" }
|
||||
insta-cmd = { version = "0.6.0" }
|
||||
@@ -138,7 +131,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "fc00eba89e5dcaa5edba51c41aa5f309b5cb126b" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "09627e450566f894956710a3fd923dc80462ae6d" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
@@ -168,13 +161,13 @@ tikv-jemallocator = { version = "0.6.0" }
|
||||
toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-flame = { version = "0.2.0" }
|
||||
tracing-indicatif = { version = "0.3.11" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-log = { version = "0.2.0" }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
||||
"env-filter",
|
||||
"fmt",
|
||||
"ansi",
|
||||
"smallvec",
|
||||
"smallvec"
|
||||
] }
|
||||
tryfn = { version = "0.2.1" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
@@ -184,7 +177,11 @@ unicode-width = { version = "0.2.0" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
||||
uuid = { version = "1.6.1", features = [
|
||||
"v4",
|
||||
"fast-rng",
|
||||
"macro-diagnostics",
|
||||
] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
wasm-bindgen = { version = "0.2.92" }
|
||||
wasm-bindgen-test = { version = "0.3.42" }
|
||||
@@ -219,12 +216,11 @@ must_use_candidate = "allow"
|
||||
similar_names = "allow"
|
||||
single_match_else = "allow"
|
||||
too_many_lines = "allow"
|
||||
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
||||
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
|
||||
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
||||
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
|
||||
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
|
||||
needless_raw_string_hashes = "allow"
|
||||
# Disallowed restriction lints
|
||||
ignore_without_reason = "allow" # Too many exsisting instances, and there's no auto fix.
|
||||
print_stdout = "warn"
|
||||
print_stderr = "warn"
|
||||
dbg_macro = "warn"
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.12.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.2/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.0/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.2
|
||||
rev: v0.12.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
@@ -423,14 +423,12 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
|
||||
- [Albumentations](https://github.com/albumentations-team/albumentations)
|
||||
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
|
||||
- [Anki](https://apps.ankiweb.net/)
|
||||
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
- AstraZeneca ([Magnus](https://github.com/AstraZeneca/magnus-core))
|
||||
- [Babel](https://github.com/python-babel/babel)
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- Capital One ([datacompy](https://github.com/capitalone/datacompy))
|
||||
- CrowdCent ([NumerBlox](https://github.com/crowdcent/numerblox)) <!-- typos: ignore -->
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- CERN ([Indico](https://getindico.io/))
|
||||
@@ -507,7 +505,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Streamlit](https://github.com/streamlit/streamlit)
|
||||
- [The Algorithms](https://github.com/TheAlgorithms/Python)
|
||||
- [Vega-Altair](https://github.com/altair-viz/altair)
|
||||
- [Weblate](https://weblate.org/)
|
||||
- WordPress ([Openverse](https://github.com/WordPress/openverse))
|
||||
- [ZenML](https://github.com/zenml-io/zenml)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.2"
|
||||
version = "0.12.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -68,7 +68,6 @@ ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
|
||||
assert_fs = { workspace = true }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
dunce = { workspace = true }
|
||||
indoc = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { workspace = true }
|
||||
|
||||
@@ -18,15 +18,14 @@ use rustc_hash::FxHashMap;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_linter::message::create_lint_diagnostic;
|
||||
use ruff_linter::message::OldDiagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::{VERSION, warn_user};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_workspace::Settings;
|
||||
use ruff_workspace::resolver::Resolver;
|
||||
|
||||
@@ -349,7 +348,7 @@ impl FileCache {
|
||||
lint.messages
|
||||
.iter()
|
||||
.map(|msg| {
|
||||
create_lint_diagnostic(
|
||||
OldDiagnostic::lint(
|
||||
&msg.body,
|
||||
msg.suggestion.as_ref(),
|
||||
msg.range,
|
||||
@@ -429,11 +428,11 @@ pub(crate) struct LintCacheData {
|
||||
|
||||
impl LintCacheData {
|
||||
pub(crate) fn from_diagnostics(
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
) -> Self {
|
||||
let source = if let Some(msg) = diagnostics.first() {
|
||||
msg.expect_ruff_source_file().source_text().to_owned()
|
||||
msg.source_file().source_text().to_owned()
|
||||
} else {
|
||||
String::new() // No messages, no need to keep the source!
|
||||
};
|
||||
@@ -447,16 +446,16 @@ impl LintCacheData {
|
||||
.map(|(rule, msg)| {
|
||||
// Make sure that all message use the same source file.
|
||||
assert_eq!(
|
||||
msg.expect_ruff_source_file(),
|
||||
diagnostics.first().unwrap().expect_ruff_source_file(),
|
||||
msg.source_file(),
|
||||
diagnostics.first().unwrap().source_file(),
|
||||
"message uses a different source file"
|
||||
);
|
||||
CacheMessage {
|
||||
rule,
|
||||
body: msg.body().to_string(),
|
||||
suggestion: msg.suggestion().map(ToString::to_string),
|
||||
range: msg.expect_range(),
|
||||
parent: msg.parent(),
|
||||
range: msg.range(),
|
||||
parent: msg.parent,
|
||||
fix: msg.fix().cloned(),
|
||||
noqa_offset: msg.noqa_offset(),
|
||||
}
|
||||
@@ -609,12 +608,12 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use filetime::{FileTime, set_file_mtime};
|
||||
use itertools::Itertools;
|
||||
use ruff_linter::settings::LinterSettings;
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_cache::CACHE_DIR_NAME;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::message::OldDiagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::settings::LinterSettings;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_python_ast::{PySourceType, PythonVersion};
|
||||
@@ -681,7 +680,7 @@ mod tests {
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
if diagnostics.inner.iter().any(Diagnostic::is_invalid_syntax) {
|
||||
if diagnostics.inner.iter().any(OldDiagnostic::is_syntax_error) {
|
||||
parse_errors.push(path.clone());
|
||||
}
|
||||
paths.push(path);
|
||||
|
||||
@@ -11,13 +11,13 @@ use log::{debug, error, warn};
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::panic::catch_unwind;
|
||||
use ruff_linter::OldDiagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{LinterSettings, flags};
|
||||
use ruff_linter::{IOError, Violation, fs, warn_user_once};
|
||||
use ruff_linter::{IOError, fs, warn_user_once};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::resolver::{
|
||||
@@ -129,7 +129,11 @@ pub(crate) fn check(
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![IOError { message }.into_diagnostic(TextRange::default(), &dummy)],
|
||||
vec![OldDiagnostic::new(
|
||||
IOError { message },
|
||||
TextRange::default(),
|
||||
&dummy,
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
@@ -162,9 +166,7 @@ pub(crate) fn check(
|
||||
|a, b| (a.0 + b.0, a.1 + b.1),
|
||||
);
|
||||
|
||||
all_diagnostics
|
||||
.inner
|
||||
.sort_by(Diagnostic::ruff_start_ordering);
|
||||
all_diagnostics.inner.sort();
|
||||
|
||||
// Store the caches.
|
||||
caches.persist()?;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
@@ -53,8 +52,6 @@ pub(crate) fn check_stdin(
|
||||
noqa,
|
||||
fix_mode,
|
||||
)?;
|
||||
diagnostics
|
||||
.inner
|
||||
.sort_unstable_by(Diagnostic::ruff_start_ordering);
|
||||
diagnostics.inner.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
@@ -10,35 +10,35 @@ use std::path::Path;
|
||||
use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use log::{debug, warn};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
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::create_syntax_error_diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{LinterSettings, flags};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{IOError, Violation, fs};
|
||||
use ruff_linter::{IOError, fs};
|
||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::Settings;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct Diagnostics {
|
||||
pub(crate) inner: Vec<Diagnostic>,
|
||||
pub(crate) inner: Vec<OldDiagnostic>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub(crate) fn new(
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
diagnostics: Vec<OldDiagnostic>,
|
||||
notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -62,12 +62,13 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let source_file = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![
|
||||
vec![OldDiagnostic::new(
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
}
|
||||
.into_diagnostic(TextRange::default(), &source_file),
|
||||
],
|
||||
},
|
||||
TextRange::default(),
|
||||
&source_file,
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
@@ -97,10 +98,10 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![create_syntax_error_diagnostic(
|
||||
dummy,
|
||||
vec![OldDiagnostic::syntax_error(
|
||||
err,
|
||||
TextRange::default(),
|
||||
dummy,
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
|
||||
@@ -6,16 +6,16 @@ use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use itertools::{Itertools, iterate};
|
||||
use ruff_linter::codes::NoqaCode;
|
||||
use ruff_linter::linter::FixTable;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, SecondaryCode};
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, PylintEmitter, RdjsonEmitter, SarifEmitter,
|
||||
TextEmitter,
|
||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, OldDiagnostic, PylintEmitter, RdjsonEmitter,
|
||||
SarifEmitter, TextEmitter,
|
||||
};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
@@ -36,8 +36,8 @@ bitflags! {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExpandedStatistics<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
struct ExpandedStatistics {
|
||||
code: Option<NoqaCode>,
|
||||
name: &'static str,
|
||||
count: usize,
|
||||
fixable: bool,
|
||||
@@ -303,11 +303,11 @@ impl Printer {
|
||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||
.inner
|
||||
.iter()
|
||||
.map(|message| (message.secondary_code(), message))
|
||||
.map(|message| (message.noqa_code(), message))
|
||||
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
||||
.fold(
|
||||
vec![],
|
||||
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
|
||||
|mut acc: Vec<((Option<NoqaCode>, &OldDiagnostic), usize)>, (code, message)| {
|
||||
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
@@ -349,7 +349,12 @@ impl Printer {
|
||||
);
|
||||
let code_width = statistics
|
||||
.iter()
|
||||
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
||||
.map(|statistic| {
|
||||
statistic
|
||||
.code
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.len()
|
||||
})
|
||||
.max()
|
||||
.unwrap();
|
||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
||||
@@ -365,8 +370,7 @@ impl Printer {
|
||||
statistic.count.to_string().bold(),
|
||||
statistic
|
||||
.code
|
||||
.map(SecondaryCode::as_str)
|
||||
.unwrap_or_default()
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.red()
|
||||
.bold(),
|
||||
if any_fixable {
|
||||
|
||||
@@ -1067,7 +1067,7 @@ fn show_statistics_syntax_errors() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
1 invalid-syntax
|
||||
1 syntax-error
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1080,7 +1080,7 @@ fn show_statistics_syntax_errors() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
1 invalid-syntax
|
||||
1 syntax-error
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
@@ -1093,7 +1093,7 @@ fn show_statistics_syntax_errors() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
1 invalid-syntax
|
||||
1 syntax-error
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -612,7 +612,7 @@ fn extend_passed_via_config_argument() {
|
||||
#[test]
|
||||
fn nonexistent_extend_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
fs::write(
|
||||
project_dir.join("ruff.toml"),
|
||||
r#"
|
||||
@@ -653,7 +653,7 @@ extend = "ruff3.toml"
|
||||
#[test]
|
||||
fn circular_extend() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_path = dunce::canonicalize(tempdir.path())?;
|
||||
let project_path = tempdir.path().canonicalize()?;
|
||||
|
||||
fs::write(
|
||||
project_path.join("ruff.toml"),
|
||||
@@ -698,7 +698,7 @@ extend = "ruff.toml"
|
||||
#[test]
|
||||
fn parse_error_extends() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_path = dunce::canonicalize(tempdir.path())?;
|
||||
let project_path = tempdir.path().canonicalize()?;
|
||||
|
||||
fs::write(
|
||||
project_path.join("ruff.toml"),
|
||||
@@ -2130,7 +2130,7 @@ select = ["UP006"]
|
||||
#[test]
|
||||
fn requires_python_no_tool() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -2441,7 +2441,7 @@ requires-python = ">= 3.11"
|
||||
#[test]
|
||||
fn requires_python_no_tool_target_version_override() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -2752,7 +2752,7 @@ requires-python = ">= 3.11"
|
||||
#[test]
|
||||
fn requires_python_no_tool_with_check() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -2797,7 +2797,7 @@ requires-python = ">= 3.11"
|
||||
#[test]
|
||||
fn requires_python_ruff_toml_no_target_fallback() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -3118,7 +3118,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
#[test]
|
||||
fn requires_python_ruff_toml_no_target_fallback_check() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -3173,7 +3173,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
#[test]
|
||||
fn requires_python_pyproject_toml_above() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let outer_pyproject = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&outer_pyproject,
|
||||
@@ -3200,7 +3200,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
let testpy_canon = testpy.canonicalize()?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/"),(r"(?m)^foo\\test","foo/test")]
|
||||
@@ -3499,7 +3499,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
#[test]
|
||||
fn requires_python_pyproject_toml_above_with_tool() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let outer_pyproject = tempdir.path().join("pyproject.toml");
|
||||
fs::write(
|
||||
&outer_pyproject,
|
||||
@@ -3528,7 +3528,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
let testpy_canon = testpy.canonicalize()?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/"),(r"foo\\","foo/")]
|
||||
@@ -3827,7 +3827,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
#[test]
|
||||
fn requires_python_ruff_toml_above() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -3856,7 +3856,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
let testpy_canon = testpy.canonicalize()?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/")]
|
||||
@@ -4441,7 +4441,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
#[test]
|
||||
fn requires_python_extend_from_shared_config() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
@@ -4479,7 +4479,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
let testpy_canon = testpy.canonicalize()?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/")]
|
||||
|
||||
@@ -12,8 +12,10 @@ fn display_default_settings() -> anyhow::Result<()> {
|
||||
|
||||
// Tempdir path's on macos are symlinks, which doesn't play nicely with
|
||||
// our snapshot filtering.
|
||||
let project_dir =
|
||||
dunce::canonicalize(tempdir.path()).context("Failed to canonical tempdir path.")?;
|
||||
let project_dir = tempdir
|
||||
.path()
|
||||
.canonicalize()
|
||||
.context("Failed to canonical tempdir path.")?;
|
||||
|
||||
std::fs::write(
|
||||
project_dir.join("pyproject.toml"),
|
||||
|
||||
@@ -821,7 +821,11 @@ impl DisplaySourceAnnotation<'_> {
|
||||
// Length of this annotation as displayed in the stderr output
|
||||
fn len(&self) -> usize {
|
||||
// Account for usize underflows
|
||||
self.range.1.abs_diff(self.range.0)
|
||||
if self.range.1 > self.range.0 {
|
||||
self.range.1 - self.range.0
|
||||
} else {
|
||||
self.range.0 - self.range.1
|
||||
}
|
||||
}
|
||||
|
||||
fn takes_space(&self) -> bool {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.bold { font-weight: bold; }
|
||||
tspan {
|
||||
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
||||
white-space: pre;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
|
||||
|
||||
<text xml:space="preserve" class="container fg">
|
||||
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">call-non-callable</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/main.py:5:9</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> def f():</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">5 |</tspan><tspan> return (1 == '2')() # Tab indented</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,45 +0,0 @@
|
||||
|
||||
# [crates/ruff_db/src/diagnostic/render.rs:123:47] diag.to_annotate() = Message {
|
||||
# level: Error,
|
||||
# id: Some(
|
||||
# "call-non-callable",
|
||||
# ),
|
||||
# title: "Object of type `bool` is not callable",
|
||||
# snippets: [
|
||||
# Snippet {
|
||||
# origin: Some(
|
||||
# "main.py",
|
||||
# ),
|
||||
# line_start: 1,
|
||||
# source: "def f():\n\treturn (1 == '2')() # Tab indented\n",
|
||||
# annotations: [
|
||||
# Annotation {
|
||||
# range: 17..29,
|
||||
# label: None,
|
||||
# level: Error,
|
||||
# },
|
||||
# ],
|
||||
# fold: false,
|
||||
# },
|
||||
# ],
|
||||
# footer: [],
|
||||
# }
|
||||
|
||||
[message]
|
||||
level = "Error"
|
||||
id = "E0308"
|
||||
title = "call-non-callable"
|
||||
|
||||
[[message.snippets]]
|
||||
source = "def f():\n\treturn (1 == '2')() # Tab indented\n"
|
||||
line_start = 4
|
||||
origin = "$DIR/main.py"
|
||||
|
||||
[[message.snippets.annotations]]
|
||||
label = ""
|
||||
level = "Error"
|
||||
range = [17, 29]
|
||||
|
||||
[renderer]
|
||||
# anonymized_line_numbers = true
|
||||
color = true
|
||||
@@ -348,10 +348,10 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_complex_constrained_attributes_1(criterion: &mut Criterion) {
|
||||
fn benchmark_many_attribute_assignments(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[complex_constrained_attributes_1]", |b| {
|
||||
criterion.bench_function("ty_micro[many_attribute_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a regression benchmark for https://github.com/astral-sh/ty/issues/627.
|
||||
@@ -400,47 +400,6 @@ fn benchmark_complex_constrained_attributes_1(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[complex_constrained_attributes_2]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is is similar to the case above, but now the attributes are actually defined.
|
||||
// https://github.com/astral-sh/ty/issues/711
|
||||
setup_micro_case(
|
||||
r#"
|
||||
class C:
|
||||
def f(self: "C"):
|
||||
self.a = ""
|
||||
self.b = ""
|
||||
|
||||
if isinstance(self.a, str):
|
||||
return
|
||||
|
||||
if isinstance(self.b, str):
|
||||
return
|
||||
if isinstance(self.b, str):
|
||||
return
|
||||
if isinstance(self.b, str):
|
||||
return
|
||||
if isinstance(self.b, str):
|
||||
return
|
||||
if isinstance(self.b, str):
|
||||
return
|
||||
"#,
|
||||
)
|
||||
},
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
struct ProjectBenchmark<'a> {
|
||||
project: InstalledProject<'a>,
|
||||
fs: MemoryFileSystem,
|
||||
@@ -498,8 +457,11 @@ fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
|
||||
let diagnostics = result.len();
|
||||
|
||||
assert!(
|
||||
diagnostics <= max_diagnostics,
|
||||
"Expected <={max_diagnostics} diagnostics but got {diagnostics}"
|
||||
diagnostics > 1 && diagnostics <= max_diagnostics,
|
||||
"Expected between {} and {} diagnostics but got {}",
|
||||
1,
|
||||
max_diagnostics,
|
||||
diagnostics
|
||||
);
|
||||
}
|
||||
|
||||
@@ -567,30 +529,12 @@ fn anyio(criterion: &mut Criterion) {
|
||||
bench_project(&benchmark, criterion);
|
||||
}
|
||||
|
||||
fn datetype(criterion: &mut Criterion) {
|
||||
let benchmark = ProjectBenchmark::new(
|
||||
RealWorldProject {
|
||||
name: "DateType",
|
||||
repository: "https://github.com/glyph/DateType",
|
||||
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
max_dep_date: "2025-07-04",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
bench_project(&benchmark, criterion);
|
||||
}
|
||||
|
||||
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
|
||||
criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments,
|
||||
benchmark_complex_constrained_attributes_1,
|
||||
benchmark_complex_constrained_attributes_2,
|
||||
benchmark_many_attribute_assignments,
|
||||
);
|
||||
criterion_group!(project, anyio, attrs, hydra, datetype);
|
||||
criterion_group!(project, anyio, attrs, hydra);
|
||||
criterion_main!(check_file, micro, project);
|
||||
|
||||
@@ -242,7 +242,7 @@ fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
|
||||
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=3)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
|
||||
@@ -13,14 +13,12 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true, optional = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["get-size"] }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true, features = ["get-size"] }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
anstyle = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
@@ -29,7 +27,6 @@ countme = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
get-size2 = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
ignore = { workspace = true, optional = true }
|
||||
matchit = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::{fmt::Formatter, sync::Arc};
|
||||
|
||||
use render::{FileResolver, Input};
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
use ruff_source_file::{SourceCode, SourceFile};
|
||||
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
pub use self::render::DisplayDiagnostic;
|
||||
use crate::{Db, files::File};
|
||||
@@ -20,7 +19,7 @@ mod stylesheet;
|
||||
/// characteristics in the inputs given to the tool. Typically, but not always,
|
||||
/// a characteristic is a deficiency. An example of a characteristic that is
|
||||
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
@@ -63,37 +62,10 @@ impl Diagnostic {
|
||||
message: message.into_diagnostic_message(),
|
||||
annotations: vec![],
|
||||
subs: vec![],
|
||||
fix: None,
|
||||
parent: None,
|
||||
noqa_offset: None,
|
||||
secondary_code: None,
|
||||
});
|
||||
Diagnostic { inner }
|
||||
}
|
||||
|
||||
/// Creates a `Diagnostic` for a syntax error.
|
||||
///
|
||||
/// Unlike the more general [`Diagnostic::new`], this requires a [`Span`] and a [`TextRange`]
|
||||
/// attached to it.
|
||||
///
|
||||
/// This should _probably_ be a method on the syntax errors, but
|
||||
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
|
||||
/// the other way around. And since we want to do this conversion in a couple
|
||||
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
|
||||
///
|
||||
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
|
||||
/// message.
|
||||
pub fn invalid_syntax(
|
||||
span: impl Into<Span>,
|
||||
message: impl IntoDiagnosticMessage,
|
||||
range: impl Ranged,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = span.into().with_range(range.range());
|
||||
diag.annotate(Annotation::primary(span).message(message));
|
||||
diag
|
||||
}
|
||||
|
||||
/// Add an annotation to this diagnostic.
|
||||
///
|
||||
/// Annotations for a diagnostic are optional, but if any are added,
|
||||
@@ -254,11 +226,6 @@ impl Diagnostic {
|
||||
self.primary_annotation().map(|ann| ann.span.clone())
|
||||
}
|
||||
|
||||
/// Returns a reference to the primary span of this diagnostic.
|
||||
pub fn primary_span_ref(&self) -> Option<&Span> {
|
||||
self.primary_annotation().map(|ann| &ann.span)
|
||||
}
|
||||
|
||||
/// Returns the tags from the primary annotation of this diagnostic if it exists.
|
||||
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
|
||||
self.primary_annotation().map(|ann| ann.tags.as_slice())
|
||||
@@ -301,176 +268,15 @@ impl Diagnostic {
|
||||
pub fn sub_diagnostics(&self) -> &[SubDiagnostic] {
|
||||
&self.inner.subs
|
||||
}
|
||||
|
||||
/// Returns the fix for this diagnostic if it exists.
|
||||
pub fn fix(&self) -> Option<&Fix> {
|
||||
self.inner.fix.as_ref()
|
||||
}
|
||||
|
||||
/// Set the fix for this diagnostic.
|
||||
pub fn set_fix(&mut self, fix: Fix) {
|
||||
Arc::make_mut(&mut self.inner).fix = Some(fix);
|
||||
}
|
||||
|
||||
/// Remove the fix for this diagnostic.
|
||||
pub fn remove_fix(&mut self) {
|
||||
Arc::make_mut(&mut self.inner).fix = None;
|
||||
}
|
||||
|
||||
/// Returns `true` if the diagnostic contains a [`Fix`].
|
||||
pub fn fixable(&self) -> bool {
|
||||
self.fix().is_some()
|
||||
}
|
||||
|
||||
/// Returns the offset of the parent statement for this diagnostic if it exists.
|
||||
///
|
||||
/// This is primarily used for checking noqa/secondary code suppressions.
|
||||
pub fn parent(&self) -> Option<TextSize> {
|
||||
self.inner.parent
|
||||
}
|
||||
|
||||
/// Set the offset of the diagnostic's parent statement.
|
||||
pub fn set_parent(&mut self, parent: TextSize) {
|
||||
Arc::make_mut(&mut self.inner).parent = Some(parent);
|
||||
}
|
||||
|
||||
/// Returns the remapped offset for a suppression comment if it exists.
|
||||
///
|
||||
/// Like [`Diagnostic::parent`], this is used for noqa code suppression comments in Ruff.
|
||||
pub fn noqa_offset(&self) -> Option<TextSize> {
|
||||
self.inner.noqa_offset
|
||||
}
|
||||
|
||||
/// Set the remapped offset for a suppression comment.
|
||||
pub fn set_noqa_offset(&mut self, noqa_offset: TextSize) {
|
||||
Arc::make_mut(&mut self.inner).noqa_offset = Some(noqa_offset);
|
||||
}
|
||||
|
||||
/// Returns the secondary code for the diagnostic if it exists.
|
||||
///
|
||||
/// The "primary" code for the diagnostic is its lint name. Diagnostics in ty don't have
|
||||
/// secondary codes (yet), but in Ruff the noqa code is used.
|
||||
pub fn secondary_code(&self) -> Option<&SecondaryCode> {
|
||||
self.inner.secondary_code.as_ref()
|
||||
}
|
||||
|
||||
/// Set the secondary code for this diagnostic.
|
||||
pub fn set_secondary_code(&mut self, code: SecondaryCode) {
|
||||
Arc::make_mut(&mut self.inner).secondary_code = Some(code);
|
||||
}
|
||||
|
||||
/// Returns the name used to represent the diagnostic.
|
||||
pub fn name(&self) -> &'static str {
|
||||
self.id().as_str()
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a syntax error message.
|
||||
pub fn is_invalid_syntax(&self) -> bool {
|
||||
self.id().is_invalid_syntax()
|
||||
}
|
||||
|
||||
/// Returns the message body to display to the user.
|
||||
pub fn body(&self) -> &str {
|
||||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the fix suggestion for the violation.
|
||||
pub fn suggestion(&self) -> Option<&str> {
|
||||
self.primary_annotation()?.get_message()
|
||||
}
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
pub fn to_url(&self) -> Option<String> {
|
||||
if self.is_invalid_syntax() {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"{}/rules/{}",
|
||||
env!("CARGO_PKG_HOMEPAGE"),
|
||||
self.name()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
|
||||
pub fn expect_ruff_filename(&self) -> String {
|
||||
self.expect_primary_span()
|
||||
.expect_ruff_file()
|
||||
.name()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Computes the start source location for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
|
||||
/// span has no range.
|
||||
pub fn expect_ruff_start_location(&self) -> LineColumn {
|
||||
self.expect_primary_span()
|
||||
.expect_ruff_file()
|
||||
.to_source_code()
|
||||
.line_column(self.expect_range().start())
|
||||
}
|
||||
|
||||
/// Computes the end source location for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
|
||||
/// span has no range.
|
||||
pub fn expect_ruff_end_location(&self) -> LineColumn {
|
||||
self.expect_primary_span()
|
||||
.expect_ruff_file()
|
||||
.to_source_code()
|
||||
.line_column(self.expect_range().end())
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] which the message belongs to.
|
||||
pub fn ruff_source_file(&self) -> Option<&SourceFile> {
|
||||
self.primary_span_ref()?.as_ruff_file()
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] which the message belongs to.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
|
||||
pub fn expect_ruff_source_file(&self) -> SourceFile {
|
||||
self.expect_primary_span().expect_ruff_file().clone()
|
||||
}
|
||||
|
||||
/// Returns the [`TextRange`] for the diagnostic.
|
||||
pub fn range(&self) -> Option<TextRange> {
|
||||
self.primary_span()?.range()
|
||||
}
|
||||
|
||||
/// Returns the [`TextRange`] for the diagnostic.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span or if the span has no range.
|
||||
pub fn expect_range(&self) -> TextRange {
|
||||
self.range().expect("Expected a range for the primary span")
|
||||
}
|
||||
|
||||
/// Returns the ordering of diagnostics based on the start of their ranges, if they have any.
|
||||
///
|
||||
/// Panics if either diagnostic has no primary span, if the span has no range, or if its file is
|
||||
/// not a `SourceFile`.
|
||||
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.expect_ruff_source_file(), self.expect_range().start()).cmp(&(
|
||||
other.expect_ruff_source_file(),
|
||||
other.expect_range().start(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
message: DiagnosticMessage,
|
||||
annotations: Vec<Annotation>,
|
||||
subs: Vec<SubDiagnostic>,
|
||||
fix: Option<Fix>,
|
||||
parent: Option<TextSize>,
|
||||
noqa_offset: Option<TextSize>,
|
||||
secondary_code: Option<SecondaryCode>,
|
||||
}
|
||||
|
||||
struct RenderingSortKey<'a> {
|
||||
@@ -536,7 +342,7 @@ impl Eq for RenderingSortKey<'_> {}
|
||||
/// Currently, the order in which sub-diagnostics are rendered relative to one
|
||||
/// another (for a single parent diagnostic) is the order in which they were
|
||||
/// attached to the diagnostic.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
@@ -637,7 +443,7 @@ impl SubDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: Severity,
|
||||
message: DiagnosticMessage,
|
||||
@@ -665,7 +471,7 @@ struct SubDiagnosticInner {
|
||||
///
|
||||
/// Messages attached to annotations should also be as brief and specific as
|
||||
/// possible. Long messages could negative impact the quality of rendering.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
@@ -785,7 +591,7 @@ impl Annotation {
|
||||
///
|
||||
/// These tags are used to provide additional information about the annotation.
|
||||
/// and are passed through to the language server protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
@@ -799,7 +605,7 @@ pub enum DiagnosticTag {
|
||||
/// be in kebab case, e.g. `no-foo` (all lower case).
|
||||
///
|
||||
/// Rules use kebab case, e.g. `no-foo`.
|
||||
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
pub struct LintName(&'static str);
|
||||
|
||||
impl LintName {
|
||||
@@ -839,7 +645,7 @@ impl PartialEq<&str> for LintName {
|
||||
}
|
||||
|
||||
/// Uniquely identifies the kind of a diagnostic.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub enum DiagnosticId {
|
||||
Panic,
|
||||
|
||||
@@ -994,7 +800,7 @@ impl std::fmt::Display for DiagnosticId {
|
||||
///
|
||||
/// This enum presents a unified interface to these two types for the sake of creating [`Span`]s and
|
||||
/// emitting diagnostics from both ty and ruff.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum UnifiedFile {
|
||||
Ty(File),
|
||||
Ruff(SourceFile),
|
||||
@@ -1046,7 +852,7 @@ impl DiagnosticSource {
|
||||
/// It consists of a `File` and an optional range into that file. When the
|
||||
/// range isn't present, it semantically implies that the diagnostic refers to
|
||||
/// the entire file. For example, when the file should be executable but isn't.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span {
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
@@ -1091,15 +897,9 @@ impl Span {
|
||||
///
|
||||
/// Panics if the file is a [`UnifiedFile::Ty`] instead of a [`UnifiedFile::Ruff`].
|
||||
pub fn expect_ruff_file(&self) -> &SourceFile {
|
||||
self.as_ruff_file()
|
||||
.expect("Expected a ruff `SourceFile`, found a ty `File`")
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] attached to this [`Span`].
|
||||
pub fn as_ruff_file(&self) -> Option<&SourceFile> {
|
||||
match &self.file {
|
||||
UnifiedFile::Ty(_) => None,
|
||||
UnifiedFile::Ruff(file) => Some(file),
|
||||
UnifiedFile::Ty(_) => panic!("Expected a ruff `SourceFile`, found a ty `File`"),
|
||||
UnifiedFile::Ruff(file) => file,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1124,7 +924,7 @@ impl From<crate::files::FileRange> for Span {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub enum Severity {
|
||||
Info,
|
||||
Warning,
|
||||
@@ -1287,7 +1087,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
/// In most cases, callers shouldn't need to use this. Instead, there is
|
||||
/// a blanket trait implementation for `IntoDiagnosticMessage` for
|
||||
/// anything that implements `std::fmt::Display`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DiagnosticMessage(Box<str>);
|
||||
|
||||
impl DiagnosticMessage {
|
||||
@@ -1347,52 +1147,41 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// A secondary identifier for a lint diagnostic.
|
||||
/// Creates a `Diagnostic` from a parse error.
|
||||
///
|
||||
/// For Ruff rules this means the noqa code.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct SecondaryCode(String);
|
||||
|
||||
impl SecondaryCode {
|
||||
pub fn new(code: String) -> Self {
|
||||
Self(code)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
/// This should _probably_ be a method on `ruff_python_parser::ParseError`, but
|
||||
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
|
||||
/// the other way around. And since we want to do this conversion in a couple
|
||||
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
|
||||
pub fn create_parse_diagnostic(file: File, err: &ruff_python_parser::ParseError) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = Span::from(file).with_range(err.location);
|
||||
diag.annotate(Annotation::primary(span).message(&err.error));
|
||||
diag
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SecondaryCode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
/// Creates a `Diagnostic` from an unsupported syntax error.
|
||||
///
|
||||
/// See [`create_parse_diagnostic`] for more details.
|
||||
pub fn create_unsupported_syntax_diagnostic(
|
||||
file: File,
|
||||
err: &ruff_python_parser::UnsupportedSyntaxError,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = Span::from(file).with_range(err.range);
|
||||
diag.annotate(Annotation::primary(span).message(err.to_string()));
|
||||
diag
|
||||
}
|
||||
|
||||
impl std::ops::Deref for SecondaryCode {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for SecondaryCode {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SecondaryCode> for &str {
|
||||
fn eq(&self, other: &SecondaryCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
// for `hashbrown::EntryRef`
|
||||
impl From<&SecondaryCode> for SecondaryCode {
|
||||
fn from(value: &SecondaryCode) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
/// Creates a `Diagnostic` from a semantic syntax error.
|
||||
///
|
||||
/// See [`create_parse_diagnostic`] for more details.
|
||||
pub fn create_semantic_syntax_diagnostic(
|
||||
file: File,
|
||||
err: &ruff_python_parser::semantic_errors::SemanticSyntaxError,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = Span::from(file).with_range(err.range);
|
||||
diag.annotate(Annotation::primary(span).message(err.to_string()));
|
||||
diag
|
||||
}
|
||||
|
||||
@@ -637,22 +637,6 @@ pub trait FileResolver {
|
||||
fn input(&self, file: File) -> Input;
|
||||
}
|
||||
|
||||
impl<T> FileResolver for T
|
||||
where
|
||||
T: Db,
|
||||
{
|
||||
fn path(&self, file: File) -> &str {
|
||||
relativize_path(self.system().current_directory(), file.path(self).as_str())
|
||||
}
|
||||
|
||||
fn input(&self, file: File) -> Input {
|
||||
Input {
|
||||
text: source_text(self, file),
|
||||
line_index: line_index(self, file),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileResolver for &dyn Db {
|
||||
fn path(&self, file: File) -> &str {
|
||||
relativize_path(self.system().current_directory(), file.path(*self).as_str())
|
||||
@@ -724,6 +708,7 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::Upcast;
|
||||
use crate::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
||||
use crate::files::system_path_to_file;
|
||||
use crate::system::{DbWithWritableSystem, SystemPath};
|
||||
@@ -2236,7 +2221,7 @@ watermelon
|
||||
///
|
||||
/// (This will set the "printed" flag on `Diagnostic`.)
|
||||
fn render(&self, diag: &Diagnostic) -> String {
|
||||
diag.display(&self.db, &self.config).to_string()
|
||||
diag.display(&self.db.upcast(), &self.config).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,23 +263,12 @@ impl Files {
|
||||
|
||||
impl fmt::Debug for Files {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if f.alternate() {
|
||||
let mut map = f.debug_map();
|
||||
let mut map = f.debug_map();
|
||||
|
||||
for entry in self.inner.system_by_path.iter() {
|
||||
map.entry(entry.key(), entry.value());
|
||||
}
|
||||
map.finish()
|
||||
} else {
|
||||
f.debug_struct("Files")
|
||||
.field("system_by_path", &self.inner.system_by_path.len())
|
||||
.field(
|
||||
"system_virtual_by_path",
|
||||
&self.inner.system_virtual_by_path.len(),
|
||||
)
|
||||
.field("vendored_by_path", &self.inner.vendored_by_path.len())
|
||||
.finish()
|
||||
for entry in self.inner.system_by_path.iter() {
|
||||
map.entry(entry.key(), entry.value());
|
||||
}
|
||||
map.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,9 +308,6 @@ pub struct File {
|
||||
count: Count<File>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for File {}
|
||||
|
||||
impl File {
|
||||
/// Reads the content of the file into a [`String`].
|
||||
///
|
||||
|
||||
@@ -5,7 +5,6 @@ use ruff_python_ast::PythonVersion;
|
||||
use rustc_hash::FxHasher;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
@@ -37,6 +36,12 @@ pub trait Db: salsa::Database {
|
||||
fn python_version(&self) -> PythonVersion;
|
||||
}
|
||||
|
||||
/// Trait for upcasting a reference to a base trait object.
|
||||
pub trait Upcast<T: ?Sized> {
|
||||
fn upcast(&self) -> &T;
|
||||
fn upcast_mut(&mut self) -> &mut T;
|
||||
}
|
||||
|
||||
/// Returns the maximum number of tasks that ty is allowed
|
||||
/// to process in parallel.
|
||||
///
|
||||
@@ -51,8 +56,8 @@ pub trait Db: salsa::Database {
|
||||
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
|
||||
/// watching the files for changes.
|
||||
pub fn max_parallelism() -> NonZeroUsize {
|
||||
std::env::var(EnvVars::TY_MAX_PARALLELISM)
|
||||
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))
|
||||
std::env::var("TY_MAX_PARALLELISM")
|
||||
.or_else(|_| std::env::var("RAYON_NUM_THREADS"))
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(|| {
|
||||
@@ -71,11 +76,11 @@ pub trait RustDoc {
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::Db;
|
||||
use crate::files::Files;
|
||||
use crate::system::TestSystem;
|
||||
use crate::system::{DbWithTestSystem, System};
|
||||
use crate::vendored::VendoredFileSystem;
|
||||
use crate::{Db, Upcast};
|
||||
|
||||
type Events = Arc<Mutex<Vec<salsa::Event>>>;
|
||||
|
||||
@@ -148,6 +153,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn Db> for TestDb {
|
||||
fn upcast(&self) -> &(dyn Db + 'static) {
|
||||
self
|
||||
}
|
||||
fn upcast_mut(&mut self) -> &mut (dyn Db + 'static) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DbWithTestSystem for TestDb {
|
||||
fn test_system(&self) -> &TestSystem {
|
||||
&self.system
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arc_swap::ArcSwapOption;
|
||||
use get_size2::GetSize;
|
||||
use ruff_python_ast::{AnyRootNodeRef, ModModule, NodeIndex};
|
||||
use ruff_python_parser::{ParseOptions, Parsed, parse_unchecked};
|
||||
|
||||
@@ -21,7 +20,7 @@ use crate::source::source_text;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||
/// for determining if a query result is unchanged.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::GetSize::get_heap_size)]
|
||||
#[salsa::tracked(returns(ref), no_eq)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
@@ -45,10 +44,9 @@ pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed<ModModule> {
|
||||
///
|
||||
/// This type manages instances of the module AST. A particular instance of the AST
|
||||
/// is represented with the [`ParsedModuleRef`] type.
|
||||
#[derive(Clone, get_size2::GetSize)]
|
||||
#[derive(Clone)]
|
||||
pub struct ParsedModule {
|
||||
file: File,
|
||||
#[get_size(size_fn = arc_swap_size)]
|
||||
inner: Arc<ArcSwapOption<indexed::IndexedModule>>,
|
||||
}
|
||||
|
||||
@@ -144,18 +142,6 @@ impl std::ops::Deref for ParsedModuleRef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the heap-size of the currently stored `T` in the `ArcSwap`.
|
||||
fn arc_swap_size<T>(arc_swap: &Arc<ArcSwapOption<T>>) -> usize
|
||||
where
|
||||
T: GetSize,
|
||||
{
|
||||
if let Some(value) = &*arc_swap.load() {
|
||||
T::get_heap_size(value)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
mod indexed {
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -164,7 +150,7 @@ mod indexed {
|
||||
use ruff_python_parser::Parsed;
|
||||
|
||||
/// A wrapper around the AST that allows access to AST nodes by index.
|
||||
#[derive(Debug, get_size2::GetSize)]
|
||||
#[derive(Debug)]
|
||||
pub struct IndexedModule {
|
||||
index: Box<[AnyRootNodeRef<'static>]>,
|
||||
pub parsed: Parsed<ModModule>,
|
||||
|
||||
@@ -11,7 +11,7 @@ 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(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
#[salsa::tracked]
|
||||
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
let path = file.path(db);
|
||||
let _span = tracing::trace_span!("source_text", file = %path).entered();
|
||||
@@ -65,7 +65,7 @@ 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, get_size2::GetSize)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct SourceText {
|
||||
inner: Arc<SourceTextInner>,
|
||||
}
|
||||
@@ -123,9 +123,8 @@ impl std::fmt::Debug for SourceText {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct SourceTextInner {
|
||||
#[get_size(ignore)]
|
||||
count: Count<SourceText>,
|
||||
kind: SourceTextKind,
|
||||
read_error: Option<SourceTextError>,
|
||||
@@ -137,19 +136,6 @@ enum SourceTextKind {
|
||||
Notebook(Box<Notebook>),
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for SourceTextKind {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
match self {
|
||||
SourceTextKind::Text(text) => text.get_heap_size(),
|
||||
// TODO: The `get-size` derive does not support ignoring enum variants.
|
||||
//
|
||||
// Jupyter notebooks are not very relevant for memory profiling, and contain
|
||||
// arbitrary JSON values that do not implement the `GetSize` trait.
|
||||
SourceTextKind::Notebook(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SourceTextKind {
|
||||
fn from(value: String) -> Self {
|
||||
SourceTextKind::Text(value)
|
||||
@@ -162,7 +148,7 @@ impl From<Notebook> for SourceTextKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
|
||||
pub enum SourceTextError {
|
||||
#[error("Failed to read notebook: {0}`")]
|
||||
FailedToReadNotebook(String),
|
||||
@@ -171,7 +157,7 @@ pub enum SourceTextError {
|
||||
}
|
||||
|
||||
/// Computes the [`LineIndex`] for `file`.
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
#[salsa::tracked]
|
||||
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
|
||||
let _span = tracing::trace_span!("line_index", ?file).entered();
|
||||
|
||||
|
||||
@@ -124,11 +124,6 @@ pub trait System: Debug {
|
||||
/// Returns `None` if no such convention exists for the system.
|
||||
fn user_config_directory(&self) -> Option<SystemPathBuf>;
|
||||
|
||||
/// Returns the directory path where cached files are stored.
|
||||
///
|
||||
/// Returns `None` if no such convention exists for the system.
|
||||
fn cache_dir(&self) -> Option<SystemPathBuf>;
|
||||
|
||||
/// Iterate over the contents of the directory at `path`.
|
||||
///
|
||||
/// The returned iterator must have the following properties:
|
||||
@@ -191,9 +186,6 @@ pub trait System: Debug {
|
||||
Err(std::env::VarError::NotPresent)
|
||||
}
|
||||
|
||||
/// Returns a handle to a [`WritableSystem`] if this system is writeable.
|
||||
fn as_writable(&self) -> Option<&dyn WritableSystem>;
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||
@@ -234,52 +226,11 @@ impl fmt::Display for CaseSensitivity {
|
||||
|
||||
/// System trait for non-readonly systems.
|
||||
pub trait WritableSystem: System {
|
||||
/// Creates a file at the given path.
|
||||
///
|
||||
/// Returns an error if the file already exists.
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
|
||||
|
||||
/// Writes the given content to the file at the given path.
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
|
||||
|
||||
/// Creates a directory at `path` as well as any intermediate directories.
|
||||
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
|
||||
|
||||
/// Reads the provided file from the system cache, or creates the file if necessary.
|
||||
///
|
||||
/// Returns `Ok(None)` if the system does not expose a suitable cache directory.
|
||||
fn get_or_cache(
|
||||
&self,
|
||||
path: &SystemPath,
|
||||
read_contents: &dyn Fn() -> Result<String>,
|
||||
) -> Result<Option<SystemPathBuf>> {
|
||||
let Some(cache_dir) = self.cache_dir() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let cache_path = cache_dir.join(path);
|
||||
|
||||
// The file has already been cached.
|
||||
if self.is_file(&cache_path) {
|
||||
return Ok(Some(cache_path));
|
||||
}
|
||||
|
||||
// Read the file contents.
|
||||
let contents = read_contents()?;
|
||||
|
||||
// Create the parent directory.
|
||||
self.create_directory_all(cache_path.parent().unwrap())?;
|
||||
|
||||
// Create and write to the file on the system.
|
||||
//
|
||||
// Note that `create_new_file` will fail if the file has already been created. This
|
||||
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
|
||||
// the cache.
|
||||
self.create_new_file(&cache_path)?;
|
||||
self.write_file(&cache_path, &contents)?;
|
||||
|
||||
Ok(Some(cache_path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::{BTreeMap, btree_map};
|
||||
use std::io;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::FusedIterator;
|
||||
use std::sync::{Arc, RwLock, RwLockWriteGuard};
|
||||
|
||||
@@ -154,26 +153,6 @@ impl MemoryFileSystem {
|
||||
virtual_files.contains_key(&path.to_path_buf())
|
||||
}
|
||||
|
||||
pub(crate) fn create_new_file(&self, path: &SystemPath) -> Result<()> {
|
||||
let normalized = self.normalize_path(path);
|
||||
|
||||
let mut by_path = self.inner.by_path.write().unwrap();
|
||||
match by_path.entry(normalized) {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Entry::File(File {
|
||||
content: String::new(),
|
||||
last_modified: file_time_now(),
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
btree_map::Entry::Occupied(_) => Err(io::Error::new(
|
||||
io::ErrorKind::AlreadyExists,
|
||||
"File already exists",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a new file in the file system.
|
||||
///
|
||||
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||
@@ -299,14 +278,14 @@ impl MemoryFileSystem {
|
||||
let normalized = fs.normalize_path(path);
|
||||
|
||||
match by_path.entry(normalized) {
|
||||
btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
Entry::File(_) => {
|
||||
entry.remove();
|
||||
Ok(())
|
||||
}
|
||||
Entry::Directory(_) => Err(is_a_directory()),
|
||||
},
|
||||
btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,14 +345,14 @@ impl MemoryFileSystem {
|
||||
}
|
||||
|
||||
match by_path.entry(normalized.clone()) {
|
||||
btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
Entry::Directory(_) => {
|
||||
entry.remove();
|
||||
Ok(())
|
||||
}
|
||||
Entry::File(_) => Err(not_a_directory()),
|
||||
},
|
||||
btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,39 +160,6 @@ impl System for OsSystem {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns an absolute cache directory on the system.
|
||||
///
|
||||
/// On Linux and macOS, uses `$XDG_CACHE_HOME/ty` or `.cache/ty`.
|
||||
/// On Windows, uses `C:\Users\User\AppData\Local\ty\cache`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cache_dir(&self) -> Option<SystemPathBuf> {
|
||||
use etcetera::BaseStrategy as _;
|
||||
|
||||
let cache_dir = etcetera::base_strategy::choose_base_strategy()
|
||||
.ok()
|
||||
.map(|dirs| dirs.cache_dir().join("ty"))
|
||||
.map(|cache_dir| {
|
||||
if cfg!(windows) {
|
||||
// On Windows, we append `cache` to the LocalAppData directory, i.e., prefer
|
||||
// `C:\Users\User\AppData\Local\ty\cache` over `C:\Users\User\AppData\Local\ty`.
|
||||
cache_dir.join("cache")
|
||||
} else {
|
||||
cache_dir
|
||||
}
|
||||
})
|
||||
.and_then(|path| SystemPathBuf::from_path_buf(path).ok())
|
||||
.unwrap_or_else(|| SystemPathBuf::from(".ty_cache"));
|
||||
|
||||
Some(cache_dir)
|
||||
}
|
||||
|
||||
// TODO: Remove this feature gating once `ruff_wasm` no longer indirectly depends on `ruff_db` with the
|
||||
// `os` feature enabled (via `ruff_workspace` -> `ruff_graph` -> `ruff_db`).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn cache_dir(&self) -> Option<SystemPathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Creates a builder to recursively walk `path`.
|
||||
///
|
||||
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
|
||||
@@ -225,10 +192,6 @@ impl System for OsSystem {
|
||||
})
|
||||
}
|
||||
|
||||
fn as_writable(&self) -> Option<&dyn WritableSystem> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -347,10 +310,6 @@ impl OsSystem {
|
||||
}
|
||||
|
||||
impl WritableSystem for OsSystem {
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
|
||||
std::fs::File::create_new(path).map(drop)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
std::fs::write(path.as_std_path(), content)
|
||||
}
|
||||
|
||||
@@ -503,12 +503,6 @@ impl ToOwned for SystemPath {
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for SystemPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemPathBuf {
|
||||
pub fn new() -> Self {
|
||||
Self(Utf8PathBuf::new())
|
||||
|
||||
@@ -102,10 +102,6 @@ impl System for TestSystem {
|
||||
self.system().user_config_directory()
|
||||
}
|
||||
|
||||
fn cache_dir(&self) -> Option<SystemPathBuf> {
|
||||
self.system().cache_dir()
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &SystemPath,
|
||||
@@ -127,10 +123,6 @@ impl System for TestSystem {
|
||||
self.system().glob(pattern)
|
||||
}
|
||||
|
||||
fn as_writable(&self) -> Option<&dyn WritableSystem> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
@@ -157,10 +149,6 @@ impl Default for TestSystem {
|
||||
}
|
||||
|
||||
impl WritableSystem for TestSystem {
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
|
||||
self.system().create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
self.system().write_file(path, content)
|
||||
}
|
||||
@@ -347,10 +335,6 @@ impl System for InMemorySystem {
|
||||
self.user_config_directory.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
fn cache_dir(&self) -> Option<SystemPathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &SystemPath,
|
||||
@@ -373,10 +357,6 @@ impl System for InMemorySystem {
|
||||
Ok(Box::new(iterator))
|
||||
}
|
||||
|
||||
fn as_writable(&self) -> Option<&dyn WritableSystem> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
@@ -397,10 +377,6 @@ impl System for InMemorySystem {
|
||||
}
|
||||
|
||||
impl WritableSystem for InMemorySystem {
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
|
||||
self.memory_fs.create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
self.memory_fs.write_file(path, content)
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ impl Display for Error {
|
||||
path: Some(path),
|
||||
err,
|
||||
} => {
|
||||
write!(f, "IO error for operation on {path}: {err}")
|
||||
write!(f, "IO error for operation on {}: {}", path, err)
|
||||
}
|
||||
ErrorKind::Io { path: None, err } => err.fmt(f),
|
||||
ErrorKind::NonUtf8Path { path } => {
|
||||
|
||||
@@ -4,12 +4,12 @@ use std::fmt::{self, Debug};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use crate::file_revision::FileRevision;
|
||||
use zip::result::ZipResult;
|
||||
use zip::write::FileOptions;
|
||||
use zip::{CompressionMethod, ZipArchive, ZipWriter, read::ZipFile};
|
||||
|
||||
pub use self::path::{VendoredPath, VendoredPathBuf};
|
||||
use crate::file_revision::FileRevision;
|
||||
|
||||
mod path;
|
||||
|
||||
|
||||
@@ -87,12 +87,6 @@ impl ToOwned for VendoredPath {
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for VendoredPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VendoredPathBuf {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
||||
@@ -13,7 +13,6 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["schemars"] }
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
|
||||
generate_ty_env_vars_reference, generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
};
|
||||
|
||||
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
|
||||
@@ -44,8 +44,5 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
generate_ty_options::main(&generate_ty_options::Args { mode: args.mode })?;
|
||||
generate_ty_rules::main(&generate_ty_rules::Args { mode: args.mode })?;
|
||||
generate_ty_cli_reference::main(&generate_ty_cli_reference::Args { mode: args.mode })?;
|
||||
generate_ty_env_vars_reference::main(&generate_ty_env_vars_reference::Args {
|
||||
mode: args.mode,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
//! Generate the environment variables reference from `ty_static::EnvVars`.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::bail;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
|
||||
let reference_string = generate();
|
||||
let filename = "environment.md";
|
||||
let reference_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("crates")
|
||||
.join("ty")
|
||||
.join("docs")
|
||||
.join(filename);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{reference_string}");
|
||||
}
|
||||
Mode::Check => match fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &reference_string);
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{comparison}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!(
|
||||
"{filename} not found, please run `cargo dev generate-ty-env-vars-reference`"
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{err}"
|
||||
);
|
||||
}
|
||||
},
|
||||
Mode::Write => {
|
||||
// Ensure the docs directory exists
|
||||
if let Some(parent) = reference_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
match fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
println!("Updating: {filename}");
|
||||
fs::write(&reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
println!("Updating: {filename}");
|
||||
fs::write(&reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str("# Environment variables\n\n");
|
||||
|
||||
// Partition and sort environment variables into TY_ and external variables.
|
||||
let (ty_vars, external_vars): (BTreeSet<_>, BTreeSet<_>) = EnvVars::metadata()
|
||||
.iter()
|
||||
.partition(|(var, _)| var.starts_with("TY_"));
|
||||
|
||||
output.push_str("ty defines and respects the following environment variables:\n\n");
|
||||
|
||||
for (var, doc) in ty_vars {
|
||||
output.push_str(&render(var, doc));
|
||||
}
|
||||
|
||||
output.push_str("## Externally-defined variables\n\n");
|
||||
output.push_str("ty also reads the following externally defined environment variables:\n\n");
|
||||
|
||||
for (var, doc) in external_vars {
|
||||
output.push_str(&render(var, doc));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Render an environment variable and its documentation.
|
||||
fn render(var: &str, doc: &str) -> String {
|
||||
format!("### `{var}`\n\n{doc}\n\n")
|
||||
}
|
||||
@@ -114,7 +114,6 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||
parents.pop();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Set {
|
||||
Toplevel(OptionSet),
|
||||
Named { name: String, set: OptionSet },
|
||||
@@ -137,7 +136,7 @@ impl Set {
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
|
||||
let header_level = "#".repeat(parents.len() + 1);
|
||||
let header_level = if parents.is_empty() { "###" } else { "####" };
|
||||
|
||||
let _ = writeln!(output, "{header_level} `{name}`");
|
||||
|
||||
|
||||
@@ -73,20 +73,12 @@ fn generate_markdown() -> String {
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
|
||||
// Reformat headers as bold text
|
||||
let mut in_code_fence = false;
|
||||
// Increase the header-level by one
|
||||
let documentation = lint
|
||||
.documentation_lines()
|
||||
.map(|line| {
|
||||
// Toggle the code fence state if we encounter a boundary
|
||||
if line.starts_with("```") {
|
||||
in_code_fence = !in_code_fence;
|
||||
}
|
||||
if !in_code_fence && line.starts_with('#') {
|
||||
Cow::Owned(format!(
|
||||
"**{line}**\n",
|
||||
line = line.trim_start_matches('#').trim_start()
|
||||
))
|
||||
if line.starts_with('#') {
|
||||
Cow::Owned(format!("#{line}"))
|
||||
} else {
|
||||
Cow::Borrowed(line)
|
||||
}
|
||||
@@ -95,15 +87,21 @@ fn generate_markdown() -> String {
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: [`{level}`](../rules.md#rule-levels "This lint has a default level of '{level}'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
|
||||
</small>
|
||||
r#"**Default level**: {level}
|
||||
|
||||
<details>
|
||||
<summary>{summary}</summary>
|
||||
|
||||
{documentation}
|
||||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name})
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
|
||||
</details>
|
||||
"#,
|
||||
level = lint.default_level(),
|
||||
// GitHub doesn't support markdown in `summary` headers
|
||||
summary = replace_inline_code(lint.summary()),
|
||||
encoded_name = url::form_urlencoded::byte_serialize(lint.name().as_str().as_bytes())
|
||||
.collect::<String>(),
|
||||
file = url::form_urlencoded::byte_serialize(lint.file().replace('\\', "/").as_bytes())
|
||||
@@ -115,6 +113,25 @@ Default level: [`{level}`](../rules.md#rule-levels "This lint has a default leve
|
||||
output
|
||||
}
|
||||
|
||||
/// Replaces inline code blocks (`code`) with `<code>code</code>`
|
||||
fn replace_inline_code(input: &str) -> String {
|
||||
let mut output = String::new();
|
||||
let mut parts = input.split('`');
|
||||
|
||||
while let Some(before) = parts.next() {
|
||||
if let Some(between) = parts.next() {
|
||||
output.push_str(before);
|
||||
output.push_str("<code>");
|
||||
output.push_str(between);
|
||||
output.push_str("</code>");
|
||||
} else {
|
||||
output.push_str(before);
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -18,7 +18,6 @@ mod generate_json_schema;
|
||||
mod generate_options;
|
||||
mod generate_rules_table;
|
||||
mod generate_ty_cli_reference;
|
||||
mod generate_ty_env_vars_reference;
|
||||
mod generate_ty_options;
|
||||
mod generate_ty_rules;
|
||||
mod generate_ty_schema;
|
||||
@@ -54,8 +53,6 @@ enum Command {
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
GenerateOptions,
|
||||
GenerateTyOptions(generate_ty_options::Args),
|
||||
/// Generate environment variables reference for ty.
|
||||
GenerateTyEnvVarsReference(generate_ty_env_vars_reference::Args),
|
||||
/// Generate CLI help.
|
||||
GenerateCliHelp(generate_cli_help::Args),
|
||||
/// Generate Markdown docs.
|
||||
@@ -101,7 +98,6 @@ fn main() -> Result<ExitCode> {
|
||||
Command::GenerateTyRules(args) => generate_ty_rules::main(&args)?,
|
||||
Command::GenerateOptions => println!("{}", generate_options::generate()),
|
||||
Command::GenerateTyOptions(args) => generate_ty_options::main(&args)?,
|
||||
Command::GenerateTyEnvVarsReference(args) => generate_ty_env_vars_reference::main(&args)?,
|
||||
Command::GenerateCliHelp(args) => generate_cli_help::main(&args)?,
|
||||
Command::GenerateDocs(args) => generate_docs::main(&args)?,
|
||||
Command::PrintAST(args) => print_ast::main(&args)?,
|
||||
|
||||
@@ -16,6 +16,5 @@ doctest = false
|
||||
[dependencies]
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
get-size2 = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
serde = { workspace = true, optional = true, features = [] }
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
/// A text edit to be applied to a source file. Inserts, deletes, or replaces
|
||||
/// content at a given location.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Edit {
|
||||
/// The start location of the edit.
|
||||
|
||||
@@ -6,9 +6,7 @@ use ruff_text_size::{Ranged, TextSize};
|
||||
use crate::edit::Edit;
|
||||
|
||||
/// Indicates if a fix can be applied.
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is, get_size2::GetSize,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||
pub enum Applicability {
|
||||
@@ -32,7 +30,7 @@ pub enum Applicability {
|
||||
}
|
||||
|
||||
/// Indicates the level of isolation required to apply a fix.
|
||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, get_size2::GetSize)]
|
||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum IsolationLevel {
|
||||
/// The fix should be applied as long as no other fixes in the same group have been applied.
|
||||
@@ -43,7 +41,7 @@ pub enum IsolationLevel {
|
||||
}
|
||||
|
||||
/// A collection of [`Edit`] elements to be applied to a source file.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Fix {
|
||||
/// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order.
|
||||
|
||||
@@ -2,10 +2,10 @@ use anyhow::{Context, Result};
|
||||
use std::sync::Arc;
|
||||
use zip::CompressionMethod;
|
||||
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
@@ -66,6 +66,15 @@ impl ModuleDb {
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn SourceDb> for ModuleDb {
|
||||
fn upcast(&self) -> &(dyn SourceDb + 'static) {
|
||||
self
|
||||
}
|
||||
fn upcast_mut(&mut self) -> &mut (dyn SourceDb + 'static) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl SourceDb for ModuleDb {
|
||||
fn vendored(&self) -> &VendoredFileSystem {
|
||||
|
||||
@@ -14,7 +14,6 @@ license = { workspace = true }
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
get-size2 = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
salsa = { workspace = true, optional = true }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut, RangeBounds};
|
||||
|
||||
/// An owned sequence of `T` indexed by `I`
|
||||
#[derive(Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct IndexVec<I, T> {
|
||||
pub raw: Vec<T>,
|
||||
@@ -191,6 +191,6 @@ where
|
||||
#[expect(unsafe_code)]
|
||||
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
|
||||
let old_vec: &mut IndexVec<I, T> = unsafe { &mut *old_pointer };
|
||||
unsafe { salsa::Update::maybe_update(&raw mut old_vec.raw, new_value.raw) }
|
||||
unsafe { salsa::Update::maybe_update(&mut old_vec.raw, new_value.raw) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.2"
|
||||
version = "0.12.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -15,7 +15,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["serde"] }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true, features = ["serde"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
@@ -38,7 +38,6 @@ colored = { workspace = true }
|
||||
fern = { workspace = true }
|
||||
glob = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
imperative = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
is-wsl = { workspace = true }
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
for _ in []:
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception,):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (Exception,):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception, ValueError):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (Exception, ValueError):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
continue
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
Should emit:
|
||||
B017 - on lines 24, 28, 46, 49, 52, and 58
|
||||
B017 - on lines 23 and 41
|
||||
"""
|
||||
import asyncio
|
||||
import unittest
|
||||
import pytest, contextlib
|
||||
import pytest
|
||||
|
||||
CONSTANT = True
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
"""
|
||||
Should emit:
|
||||
B017 - on lines 20, 21, 25, and 26
|
||||
"""
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
def something_else() -> None:
|
||||
for i in (1, 2, 3):
|
||||
print(i)
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
|
||||
class Foobar(unittest.TestCase):
|
||||
def call_form_raises(self) -> None:
|
||||
self.assertRaises(Exception, something_else)
|
||||
self.assertRaises(BaseException, something_else)
|
||||
|
||||
|
||||
def test_pytest_call_form() -> None:
|
||||
pytest.raises(Exception, something_else)
|
||||
pytest.raises(BaseException, something_else)
|
||||
|
||||
pytest.raises(Exception, something_else, match="hello")
|
||||
@@ -185,45 +185,38 @@ for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Shouldn't trigger the warning when there is a return statement.
|
||||
def foo():
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
return section_items
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
return section_items
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return statement after.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return in another branch.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger, since only one branch has a return statement.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
# Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
# NOTE: This should always be at the end of the file.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
foo or{x: None for x in bar}
|
||||
|
||||
|
||||
# C420 fix must make sure to insert a leading space if needed,
|
||||
# See https://github.com/astral-sh/ruff/issues/18599
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env -S uv tool run ruff check --isolated --select EXE003
|
||||
print("hello world")
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env -S uvx ruff check --isolated --select EXE003
|
||||
print("hello world")
|
||||
@@ -26,9 +26,8 @@ abc(**{'a': b}, **{'a': c}) # PIE804
|
||||
abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||
|
||||
# Some values need to be parenthesized.
|
||||
def foo():
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18036
|
||||
# The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
|
||||
@@ -119,26 +119,4 @@ field35: "int | str | int" # Error
|
||||
# Technically, this falls into the domain of the rule but it is an unlikely edge case,
|
||||
# only works if you have from `__future__ import annotations` at the top of the file,
|
||||
# and stringified annotations are discouraged in stub files.
|
||||
field36: "int | str" | int # Ok
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18546
|
||||
# Expand Optional[T] to Union[T, None]
|
||||
# OK
|
||||
field37: typing.Optional[int]
|
||||
field38: typing.Union[int, None]
|
||||
# equivalent to None
|
||||
field39: typing.Optional[None]
|
||||
# equivalent to int | None
|
||||
field40: typing.Union[typing.Optional[int], None]
|
||||
field41: typing.Optional[typing.Union[int, None]]
|
||||
field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
field43: typing.Optional[int] | None
|
||||
field44: typing.Optional[int | None]
|
||||
field45: typing.Optional[int] | typing.Optional[int]
|
||||
# equivalent to int | dict | None
|
||||
field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
field47: typing.Optional[int] | typing.Optional[dict]
|
||||
|
||||
# avoid reporting twice
|
||||
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
field49: typing.Optional[complex | complex] | complex
|
||||
field36: "int | str" | int # Ok
|
||||
@@ -111,25 +111,3 @@ field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
|
||||
|
||||
# Test case for mixed union type
|
||||
field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18546
|
||||
# Expand Optional[T] to Union[T, None]
|
||||
# OK
|
||||
field37: typing.Optional[int]
|
||||
field38: typing.Union[int, None]
|
||||
# equivalent to None
|
||||
field39: typing.Optional[None]
|
||||
# equivalent to int | None
|
||||
field40: typing.Union[typing.Optional[int], None]
|
||||
field41: typing.Optional[typing.Union[int, None]]
|
||||
field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
field43: typing.Optional[int] | None
|
||||
field44: typing.Optional[int | None]
|
||||
field45: typing.Optional[int] | typing.Optional[int]
|
||||
# equivalent to int | dict | None
|
||||
field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
field47: typing.Optional[int] | typing.Optional[dict]
|
||||
|
||||
# avoid reporting twice
|
||||
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
field49: typing.Optional[complex | complex] | complex
|
||||
|
||||
@@ -27,9 +27,8 @@ with contextlib.ExitStack() as stack:
|
||||
close_files = stack.pop_all().close
|
||||
|
||||
# OK
|
||||
async def foo():
|
||||
with contextlib.AsyncExitStack() as exit_stack:
|
||||
f = await exit_stack.enter_async_context(open("filename"))
|
||||
with contextlib.AsyncExitStack() as exit_stack:
|
||||
f = await exit_stack.enter_async_context(open("filename"))
|
||||
|
||||
# OK (false negative)
|
||||
with contextlib.ExitStack():
|
||||
@@ -276,10 +275,9 @@ class ExampleClassTests(TestCase):
|
||||
cls.enterClassContext(open("filename"))
|
||||
|
||||
# OK
|
||||
async def foo():
|
||||
class ExampleAsyncTests(IsolatedAsyncioTestCase):
|
||||
async def test_something(self):
|
||||
await self.enterAsyncContext(open("filename"))
|
||||
class ExampleAsyncTests(IsolatedAsyncioTestCase):
|
||||
async def test_something(self):
|
||||
await self.enterAsyncContext(open("filename"))
|
||||
|
||||
# OK
|
||||
class ExampleTests(TestCase):
|
||||
|
||||
@@ -1,99 +1,98 @@
|
||||
def foo():
|
||||
# Errors
|
||||
a = "hello"
|
||||
# Errors
|
||||
a = "hello"
|
||||
|
||||
# SIM116
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return "baz"
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
# SIM116
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return "baz"
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return (1, 2, 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif a == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return ("hello'", 'hi"', 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# OK
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return baz()
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# OK
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif b == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if func_name == "create":
|
||||
return "A"
|
||||
elif func_name == "modify":
|
||||
return "M"
|
||||
elif func_name == "remove":
|
||||
return "D"
|
||||
elif func_name == "move":
|
||||
return "MV"
|
||||
|
||||
# OK
|
||||
def no_return_in_else(platform):
|
||||
if platform == "linux":
|
||||
return "auditwheel repair -w {dest_dir} {wheel}"
|
||||
elif platform == "macos":
|
||||
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
||||
elif platform == "windows":
|
||||
return ""
|
||||
else:
|
||||
return 42
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return (1, 2, 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif a == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return ("hello'", 'hi"', 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# OK
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return baz()
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# OK
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif b == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if func_name == "create":
|
||||
return "A"
|
||||
elif func_name == "modify":
|
||||
return "M"
|
||||
elif func_name == "remove":
|
||||
return "D"
|
||||
elif func_name == "move":
|
||||
return "MV"
|
||||
|
||||
# OK
|
||||
def no_return_in_else(platform):
|
||||
if platform == "linux":
|
||||
return "auditwheel repair -w {dest_dir} {wheel}"
|
||||
elif platform == "macos":
|
||||
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
||||
elif platform == "windows":
|
||||
return ""
|
||||
else:
|
||||
msg = f"Unknown platform: {platform!r}"
|
||||
raise ValueError(msg)
|
||||
msg = f"Unknown platform: {platform!r}"
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -50,23 +50,3 @@ class Baz:
|
||||
class Nested:
|
||||
a: TypeAlias = 'Baz' # OK
|
||||
type A = 'Baz' # TC008
|
||||
|
||||
# O should have parenthesis added
|
||||
o: TypeAlias = """int
|
||||
| None"""
|
||||
type O = """int
|
||||
| None"""
|
||||
|
||||
# P, Q, and R should not have parenthesis added
|
||||
p: TypeAlias = ("""int
|
||||
| None""")
|
||||
type P = ("""int
|
||||
| None""")
|
||||
|
||||
q: TypeAlias = """(int
|
||||
| None)"""
|
||||
type Q = """(int
|
||||
| None)"""
|
||||
|
||||
r: TypeAlias = """int | None"""
|
||||
type R = """int | None"""
|
||||
@@ -1,6 +0,0 @@
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/19175
|
||||
# there is a (potentially invisible) unicode formfeed character (000C) between `TYPE_CHECKING` and the backslash
|
||||
from typing import TYPE_CHECKING\
|
||||
|
||||
if TYPE_CHECKING: import builtins
|
||||
builtins.print("!")
|
||||
@@ -1,4 +1,4 @@
|
||||
import os.path, pathlib
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
from os.path import getatime
|
||||
|
||||
@@ -10,26 +10,3 @@ os.path.getatime(Path("filename"))
|
||||
getatime("filename")
|
||||
getatime(b"filename")
|
||||
getatime(Path("filename"))
|
||||
|
||||
|
||||
file = __file__
|
||||
|
||||
os.path.getatime(file)
|
||||
os.path.getatime(filename="filename")
|
||||
os.path.getatime(filename=Path("filename"))
|
||||
|
||||
os.path.getatime( # comment 1
|
||||
# comment 2
|
||||
"filename" # comment 3
|
||||
# comment 4
|
||||
, # comment 5
|
||||
# comment 6
|
||||
) # comment 7
|
||||
|
||||
os.path.getatime("file" + "name")
|
||||
|
||||
getatime(Path("filename").resolve())
|
||||
|
||||
os.path.getatime(pathlib.Path("filename"))
|
||||
|
||||
getatime(Path("dir") / "file.txt")
|
||||
|
||||
@@ -170,25 +170,3 @@ def foo():
|
||||
v = {}
|
||||
for o,(x,)in():
|
||||
v[x,]=o
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19005
|
||||
def issue_19005_1():
|
||||
c = {}
|
||||
a = object()
|
||||
for a.b in ():
|
||||
c[a.b] = a.b
|
||||
|
||||
|
||||
def issue_19005_2():
|
||||
a = object()
|
||||
c = {}
|
||||
for a.k, a.v in ():
|
||||
c[a.k] = a.v
|
||||
|
||||
|
||||
def issue_19005_3():
|
||||
a = [None, None]
|
||||
c = {}
|
||||
for a[0], a[1] in ():
|
||||
c[a[0]] = a[1]
|
||||
|
||||
@@ -81,5 +81,4 @@ match(foo):
|
||||
# https://github.com/astral-sh/ruff/issues/12094
|
||||
pass;
|
||||
|
||||
def foo():
|
||||
yield, x
|
||||
yield, x
|
||||
|
||||
@@ -69,11 +69,3 @@ def func():
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
|
||||
|
||||
def func():
|
||||
("""Docstring.
|
||||
|
||||
Raises:
|
||||
ValueError: An error.
|
||||
""")
|
||||
|
||||
@@ -57,7 +57,3 @@ _ = lambda x: z(lambda y: x + y)(x)
|
||||
# lambda uses an additional keyword
|
||||
_ = lambda *args: f(*args, y=1)
|
||||
_ = lambda *args: f(*args, y=x)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18675
|
||||
_ = lambda x: (string := str)(x)
|
||||
_ = lambda x: ((x := 1) and str)(x)
|
||||
|
||||
@@ -90,52 +90,3 @@ class AClass:
|
||||
|
||||
def myfunc(param: "tuple[Union[int, 'AClass', None], str]"):
|
||||
print(param)
|
||||
|
||||
|
||||
from typing import NamedTuple, Union
|
||||
|
||||
import typing_extensions
|
||||
from typing_extensions import (
|
||||
NamedTuple as NamedTupleTE,
|
||||
Union as UnionTE,
|
||||
)
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/18619
|
||||
# Don't emit lint for `NamedTuple`
|
||||
a_plain_1: Union[NamedTuple, int] = None
|
||||
a_plain_2: Union[int, NamedTuple] = None
|
||||
a_plain_3: Union[NamedTuple, None] = None
|
||||
a_plain_4: Union[None, NamedTuple] = None
|
||||
a_plain_te_1: UnionTE[NamedTupleTE, int] = None
|
||||
a_plain_te_2: UnionTE[int, NamedTupleTE] = None
|
||||
a_plain_te_3: UnionTE[NamedTupleTE, None] = None
|
||||
a_plain_te_4: UnionTE[None, NamedTupleTE] = None
|
||||
a_plain_typing_1: UnionTE[typing.NamedTuple, int] = None
|
||||
a_plain_typing_2: UnionTE[int, typing.NamedTuple] = None
|
||||
a_plain_typing_3: UnionTE[typing.NamedTuple, None] = None
|
||||
a_plain_typing_4: UnionTE[None, typing.NamedTuple] = None
|
||||
a_string_1: "Union[NamedTuple, int]" = None
|
||||
a_string_2: "Union[int, NamedTuple]" = None
|
||||
a_string_3: "Union[NamedTuple, None]" = None
|
||||
a_string_4: "Union[None, NamedTuple]" = None
|
||||
a_string_te_1: "UnionTE[NamedTupleTE, int]" = None
|
||||
a_string_te_2: "UnionTE[int, NamedTupleTE]" = None
|
||||
a_string_te_3: "UnionTE[NamedTupleTE, None]" = None
|
||||
a_string_te_4: "UnionTE[None, NamedTupleTE]" = None
|
||||
a_string_typing_1: "typing.Union[typing.NamedTuple, int]" = None
|
||||
a_string_typing_2: "typing.Union[int, typing.NamedTuple]" = None
|
||||
a_string_typing_3: "typing.Union[typing.NamedTuple, None]" = None
|
||||
a_string_typing_4: "typing.Union[None, typing.NamedTuple]" = None
|
||||
|
||||
b_plain_1: Union[NamedTuple] = None
|
||||
b_plain_2: Union[NamedTuple, None] = None
|
||||
b_plain_te_1: UnionTE[NamedTupleTE] = None
|
||||
b_plain_te_2: UnionTE[NamedTupleTE, None] = None
|
||||
b_plain_typing_1: UnionTE[typing.NamedTuple] = None
|
||||
b_plain_typing_2: UnionTE[typing.NamedTuple, None] = None
|
||||
b_string_1: "Union[NamedTuple]" = None
|
||||
b_string_2: "Union[NamedTuple, None]" = None
|
||||
b_string_te_1: "UnionTE[NamedTupleTE]" = None
|
||||
b_string_te_2: "UnionTE[NamedTupleTE, None]" = None
|
||||
b_string_typing_1: "typing.Union[typing.NamedTuple]" = None
|
||||
b_string_typing_2: "typing.Union[typing.NamedTuple, None]" = None
|
||||
|
||||
@@ -105,39 +105,3 @@ import builtins
|
||||
class C:
|
||||
def f(self):
|
||||
builtins.super(C, self)
|
||||
|
||||
|
||||
# see: https://github.com/astral-sh/ruff/issues/18533
|
||||
class ClassForCommentEnthusiasts(BaseClass):
|
||||
def with_comments(self):
|
||||
super(
|
||||
# super helpful comment
|
||||
ClassForCommentEnthusiasts,
|
||||
self
|
||||
).f()
|
||||
super(
|
||||
ClassForCommentEnthusiasts,
|
||||
# even more helpful comment
|
||||
self
|
||||
).f()
|
||||
super(
|
||||
ClassForCommentEnthusiasts,
|
||||
self
|
||||
# also a comment
|
||||
).f()
|
||||
|
||||
|
||||
# Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
class Ord(int):
|
||||
def __len__(self):
|
||||
return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
|
||||
class ExampleWithKeywords:
|
||||
def method1(self):
|
||||
super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method2(self):
|
||||
super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method3(self):
|
||||
super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -26,9 +26,3 @@ def hello():
|
||||
|
||||
f"foo"u"bar" # OK
|
||||
f"foo" u"bar" # OK
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18895
|
||||
""u""
|
||||
""u"hi"
|
||||
""""""""""""""""""""u"hi"
|
||||
""U"helloooo"
|
||||
@@ -47,25 +47,3 @@ class ServiceRefOrValue:
|
||||
# Test for: https://github.com/astral-sh/ruff/issues/18508
|
||||
# Optional[None] should not be offered a fix
|
||||
foo: Optional[None] = None
|
||||
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
import typing_extensions
|
||||
from typing_extensions import (
|
||||
NamedTuple as NamedTupleTE,
|
||||
Optional as OptionalTE,
|
||||
)
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/18619
|
||||
# Don't emit lint for `NamedTuple`
|
||||
a1: Optional[NamedTuple] = None
|
||||
a2: typing.Optional[NamedTuple] = None
|
||||
a3: OptionalTE[NamedTuple] = None
|
||||
a4: typing_extensions.Optional[NamedTuple] = None
|
||||
a5: Optional[typing.NamedTuple] = None
|
||||
a6: typing.Optional[typing.NamedTuple] = None
|
||||
a7: OptionalTE[typing.NamedTuple] = None
|
||||
a8: typing_extensions.Optional[typing.NamedTuple] = None
|
||||
a9: "Optional[NamedTuple]" = None
|
||||
a10: Optional[NamedTupleTE] = None
|
||||
|
||||
@@ -85,10 +85,3 @@ def _():
|
||||
|
||||
if isinstance(foo, type(None)):
|
||||
...
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19047
|
||||
if isinstance(foo, ()):
|
||||
pass
|
||||
|
||||
if isinstance(foo, Union[()]):
|
||||
pass
|
||||
|
||||
@@ -125,20 +125,3 @@ class J:
|
||||
class K:
|
||||
f: F = F()
|
||||
g: G = G()
|
||||
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/19014
|
||||
# These are all valid field calls and should not cause diagnostics.
|
||||
@attr.define
|
||||
class TestAttrField:
|
||||
attr_field_factory: list[int] = attr.field(factory=list)
|
||||
attr_field_default: list[int] = attr.field(default=attr.Factory(list))
|
||||
attr_factory: list[int] = attr.Factory(list)
|
||||
attr_ib: list[int] = attr.ib(factory=list)
|
||||
attr_attr: list[int] = attr.attr(factory=list)
|
||||
attr_attrib: list[int] = attr.attrib(factory=list)
|
||||
|
||||
|
||||
@attr.attributes
|
||||
class TestAttrAttributes:
|
||||
x: list[int] = list() # RUF009
|
||||
|
||||
@@ -7,9 +7,6 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::{
|
||||
is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled,
|
||||
};
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear,
|
||||
@@ -93,13 +90,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::UnnecessaryLiteralUnion) {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::DuplicateUnionMember)
|
||||
// Avoid duplicate checks inside `Optional`
|
||||
&& !(
|
||||
is_optional_as_none_in_union_enabled(checker.settings())
|
||||
&& checker.semantic.inside_optional()
|
||||
)
|
||||
{
|
||||
if checker.is_rule_enabled(Rule::DuplicateUnionMember) {
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::RedundantLiteralUnion) {
|
||||
@@ -1039,84 +1030,46 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
Rule::OsPathGetatime,
|
||||
Rule::OsPathGetmtime,
|
||||
Rule::OsPathGetctime,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
Rule::OsSymlink,
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) {
|
||||
let segments = qualified_name.segments();
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathAbspath) {
|
||||
flake8_use_pathlib::rules::os_path_abspath(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRmdir) {
|
||||
flake8_use_pathlib::rules::os_rmdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRemove) {
|
||||
flake8_use_pathlib::rules::os_remove(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsUnlink) {
|
||||
flake8_use_pathlib::rules::os_unlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExists) {
|
||||
flake8_use_pathlib::rules::os_path_exists(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExpanduser) {
|
||||
flake8_use_pathlib::rules::os_path_expanduser(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathBasename) {
|
||||
flake8_use_pathlib::rules::os_path_basename(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathDirname) {
|
||||
flake8_use_pathlib::rules::os_path_dirname(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsabs) {
|
||||
flake8_use_pathlib::rules::os_path_isabs(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsdir) {
|
||||
flake8_use_pathlib::rules::os_path_isdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsfile) {
|
||||
flake8_use_pathlib::rules::os_path_isfile(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIslink) {
|
||||
flake8_use_pathlib::rules::os_path_islink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsReadlink) {
|
||||
flake8_use_pathlib::rules::os_readlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
}
|
||||
@@ -1270,11 +1223,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::NonOctalPermissions) {
|
||||
ruff::rules::non_octal_permissions(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::AssertRaisesException)
|
||||
&& is_assert_raises_exception_call_enabled(checker.settings())
|
||||
{
|
||||
flake8_bugbear::rules::assert_raises_exception_call(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_rule_enabled(&[
|
||||
@@ -1482,11 +1430,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if !checker.semantic.in_nested_union() {
|
||||
if checker.is_rule_enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
// Avoid duplicate checks inside `Optional`
|
||||
&& !(
|
||||
is_optional_as_none_in_union_enabled(checker.settings())
|
||||
&& checker.semantic.inside_optional()
|
||||
)
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ use itertools::Itertools;
|
||||
use log::debug;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::{Applicability, Fix, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
|
||||
@@ -75,7 +74,7 @@ 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, Violation};
|
||||
use crate::{Edit, OldDiagnostic, Violation};
|
||||
use crate::{Locator, docstrings, noqa};
|
||||
|
||||
mod analyze;
|
||||
@@ -389,7 +388,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
|
||||
///
|
||||
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
|
||||
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
|
||||
/// before it is added to the collection in the checker on `Drop`.
|
||||
pub(crate) fn report_diagnostic<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
@@ -402,7 +401,7 @@ impl<'a> Checker<'a> {
|
||||
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
|
||||
/// enabled.
|
||||
///
|
||||
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
|
||||
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
|
||||
/// before it is added to the collection in the checker on `Drop`.
|
||||
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
@@ -670,12 +669,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_)
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel
|
||||
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
||||
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_)
|
||||
| SemanticSyntaxErrorKind::InvalidNonlocal(_) => {
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
}
|
||||
@@ -2771,7 +2765,9 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_typing_only_annotation() {
|
||||
if self.semantic.in_annotation()
|
||||
&& self.semantic.in_typing_only_annotation()
|
||||
{
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, annotation, range);
|
||||
}
|
||||
@@ -3122,9 +3118,9 @@ pub(crate) fn check_ast(
|
||||
/// A type for collecting diagnostics in a given file.
|
||||
///
|
||||
/// [`LintContext::report_diagnostic`] can be used to obtain a [`DiagnosticGuard`], which will push
|
||||
/// a [`Violation`] to the contained [`Diagnostic`] collection on `Drop`.
|
||||
/// a [`Violation`] to the contained [`OldDiagnostic`] collection on `Drop`.
|
||||
pub(crate) struct LintContext<'a> {
|
||||
diagnostics: RefCell<Vec<Diagnostic>>,
|
||||
diagnostics: RefCell<Vec<OldDiagnostic>>,
|
||||
source_file: SourceFile,
|
||||
rules: RuleTable,
|
||||
settings: &'a LinterSettings,
|
||||
@@ -3132,7 +3128,7 @@ pub(crate) struct LintContext<'a> {
|
||||
|
||||
impl<'a> LintContext<'a> {
|
||||
/// Create a new collector with the given `source_file` and an empty collection of
|
||||
/// `Diagnostic`s.
|
||||
/// `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();
|
||||
@@ -3153,7 +3149,7 @@ impl<'a> LintContext<'a> {
|
||||
|
||||
/// Return a [`DiagnosticGuard`] for reporting a diagnostic.
|
||||
///
|
||||
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
|
||||
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
|
||||
/// before it is added to the collection in the context on `Drop`.
|
||||
pub(crate) fn report_diagnostic<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
@@ -3162,7 +3158,7 @@ impl<'a> LintContext<'a> {
|
||||
) -> DiagnosticGuard<'chk, 'a> {
|
||||
DiagnosticGuard {
|
||||
context: self,
|
||||
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
|
||||
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
|
||||
rule: T::rule(),
|
||||
}
|
||||
}
|
||||
@@ -3170,7 +3166,7 @@ impl<'a> LintContext<'a> {
|
||||
/// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is
|
||||
/// enabled.
|
||||
///
|
||||
/// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic
|
||||
/// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic
|
||||
/// before it is added to the collection in the context on `Drop`.
|
||||
pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>(
|
||||
&'chk self,
|
||||
@@ -3181,7 +3177,7 @@ impl<'a> LintContext<'a> {
|
||||
if self.is_rule_enabled(rule) {
|
||||
Some(DiagnosticGuard {
|
||||
context: self,
|
||||
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
|
||||
diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)),
|
||||
rule,
|
||||
})
|
||||
} else {
|
||||
@@ -3205,17 +3201,17 @@ impl<'a> LintContext<'a> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn into_parts(self) -> (Vec<Diagnostic>, SourceFile) {
|
||||
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<Diagnostic> {
|
||||
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 = &Diagnostic> {
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &OldDiagnostic> {
|
||||
self.diagnostics.get_mut().iter()
|
||||
}
|
||||
}
|
||||
@@ -3233,7 +3229,7 @@ pub(crate) struct DiagnosticGuard<'a, 'b> {
|
||||
/// The diagnostic that we want to report.
|
||||
///
|
||||
/// This is always `Some` until the `Drop` (or `defuse`) call.
|
||||
diagnostic: Option<Diagnostic>,
|
||||
diagnostic: Option<OldDiagnostic>,
|
||||
rule: Rule,
|
||||
}
|
||||
|
||||
@@ -3259,14 +3255,11 @@ impl DiagnosticGuard<'_, '_> {
|
||||
#[inline]
|
||||
pub(crate) fn set_fix(&mut self, fix: Fix) {
|
||||
if !self.context.rules.should_fix(self.rule) {
|
||||
self.diagnostic.as_mut().unwrap().remove_fix();
|
||||
self.fix = None;
|
||||
return;
|
||||
}
|
||||
let applicability = self.resolve_applicability(&fix);
|
||||
self.diagnostic
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_fix(fix.with_applicability(applicability));
|
||||
self.fix = Some(fix.with_applicability(applicability));
|
||||
}
|
||||
|
||||
/// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`.
|
||||
@@ -3295,9 +3288,9 @@ impl DiagnosticGuard<'_, '_> {
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DiagnosticGuard<'_, '_> {
|
||||
type Target = Diagnostic;
|
||||
type Target = OldDiagnostic;
|
||||
|
||||
fn deref(&self) -> &Diagnostic {
|
||||
fn deref(&self) -> &OldDiagnostic {
|
||||
// OK because `self.diagnostic` is only `None` within `Drop`.
|
||||
self.diagnostic.as_ref().unwrap()
|
||||
}
|
||||
@@ -3305,7 +3298,7 @@ impl std::ops::Deref for DiagnosticGuard<'_, '_> {
|
||||
|
||||
/// Return a mutable borrow of the diagnostic in this guard.
|
||||
impl std::ops::DerefMut for DiagnosticGuard<'_, '_> {
|
||||
fn deref_mut(&mut self) -> &mut Diagnostic {
|
||||
fn deref_mut(&mut self) -> &mut OldDiagnostic {
|
||||
// OK because `self.diagnostic` is only `None` within `Drop`.
|
||||
self.diagnostic.as_mut().unwrap()
|
||||
}
|
||||
|
||||
@@ -35,40 +35,45 @@ pub(crate) fn check_noqa(
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let file_noqa_directives =
|
||||
FileNoqaDirectives::extract(locator, comment_ranges, &settings.external, path);
|
||||
let exemption = FileExemption::from(&file_noqa_directives);
|
||||
|
||||
// Extract all `noqa` directives.
|
||||
let mut noqa_directives =
|
||||
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
||||
|
||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let exemption = FileExemption::from(&file_noqa_directives);
|
||||
|
||||
// Indices of diagnostics that were ignored by a `noqa` directive.
|
||||
let mut ignored_diagnostics = vec![];
|
||||
|
||||
// Remove any ignored diagnostics.
|
||||
'outer: for (index, diagnostic) in context.iter().enumerate() {
|
||||
// Can't ignore syntax errors.
|
||||
let Some(code) = diagnostic.secondary_code() else {
|
||||
let Some(code) = diagnostic.noqa_code() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if *code == Rule::BlanketNOQA.noqa_code() {
|
||||
if code == Rule::BlanketNOQA.noqa_code() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if exemption.contains_secondary_code(code) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
match &exemption {
|
||||
FileExemption::All(_) => {
|
||||
// If the file is exempted, ignore all diagnostics.
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, ignore it.
|
||||
if codes.contains(&&code) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let noqa_offsets = diagnostic
|
||||
.parent()
|
||||
.parent
|
||||
.into_iter()
|
||||
.chain(std::iter::once(diagnostic.expect_range().start()))
|
||||
.chain(std::iter::once(diagnostic.start()))
|
||||
.map(|position| noqa_line_for.resolve(position))
|
||||
.unique();
|
||||
|
||||
@@ -77,21 +82,13 @@ pub(crate) fn check_noqa(
|
||||
{
|
||||
let suppressed = match &directive_line.directive {
|
||||
Directive::All(_) => {
|
||||
let Ok(rule) = Rule::from_code(code) else {
|
||||
debug_assert!(false, "Invalid secondary code `{code}`");
|
||||
continue;
|
||||
};
|
||||
directive_line.matches.push(rule);
|
||||
directive_line.matches.push(code);
|
||||
ignored_diagnostics.push(index);
|
||||
true
|
||||
}
|
||||
Directive::Codes(directive) => {
|
||||
if directive.includes(code) {
|
||||
let Ok(rule) = Rule::from_code(code) else {
|
||||
debug_assert!(false, "Invalid secondary code `{code}`");
|
||||
continue;
|
||||
};
|
||||
directive_line.matches.push(rule);
|
||||
directive_line.matches.push(code);
|
||||
ignored_diagnostics.push(index);
|
||||
true
|
||||
} else {
|
||||
@@ -150,11 +147,11 @@ pub(crate) fn check_noqa(
|
||||
|
||||
if seen_codes.insert(original_code) {
|
||||
let is_code_used = if is_file_level {
|
||||
context.iter().any(|diag| {
|
||||
diag.secondary_code().is_some_and(|noqa| *noqa == code)
|
||||
})
|
||||
context
|
||||
.iter()
|
||||
.any(|diag| diag.noqa_code().is_some_and(|noqa| noqa == code))
|
||||
} else {
|
||||
matches.iter().any(|match_| match_.noqa_code() == code)
|
||||
matches.iter().any(|match_| *match_ == code)
|
||||
} || settings
|
||||
.external
|
||||
.iter()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
/// `--select`. For pylint this is e.g. C0414 and E0118 but also C and E01.
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use ruff_db::diagnostic::SecondaryCode;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::registry::Linter;
|
||||
@@ -47,24 +46,6 @@ impl PartialEq<&str> for NoqaCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NoqaCode> for &str {
|
||||
fn eq(&self, other: &NoqaCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NoqaCode> for SecondaryCode {
|
||||
fn eq(&self, other: &NoqaCode) -> bool {
|
||||
&self.as_str() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SecondaryCode> for NoqaCode {
|
||||
fn eq(&self, other: &SecondaryCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for NoqaCode {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
@@ -919,27 +900,27 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Tryceratops, "401") => (RuleGroup::Stable, rules::tryceratops::rules::VerboseLogMessage),
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathAbspath),
|
||||
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsChmod),
|
||||
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
|
||||
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
|
||||
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRename),
|
||||
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReplace),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsUnlink),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsUnlink),
|
||||
(Flake8UsePathlib, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsGetcwd),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReadlink),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReadlink),
|
||||
(Flake8UsePathlib, "116") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsStat),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsabs),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsabs),
|
||||
(Flake8UsePathlib, "118") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathJoin),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathDirname),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSamefile),
|
||||
(Flake8UsePathlib, "122") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
|
||||
@@ -618,7 +618,7 @@ mod tests {
|
||||
use crate::fix::edits::{
|
||||
add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon,
|
||||
};
|
||||
use crate::{Edit, Fix, Locator, Violation};
|
||||
use crate::{Edit, Fix, Locator, OldDiagnostic};
|
||||
|
||||
/// Parse the given source using [`Mode::Module`] and return the first statement.
|
||||
fn parse_first_stmt(source: &str) -> Result<Stmt> {
|
||||
@@ -749,12 +749,12 @@ x = 1 \
|
||||
let diag = {
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
let mut iter = edits.into_iter();
|
||||
// The choice of rule here is arbitrary.
|
||||
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
|
||||
let mut diagnostic = OldDiagnostic::new(
|
||||
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
|
||||
TextRange::default(),
|
||||
&SourceFileBuilder::new("<filename>", "<code>").finish(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edits(
|
||||
diagnostic.fix = Some(Fix::safe_edits(
|
||||
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
|
||||
iter,
|
||||
));
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::collections::BTreeSet;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::{IsolationLevel, SourceMap};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::linter::FixTable;
|
||||
use crate::message::OldDiagnostic;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::{Edit, Fix};
|
||||
@@ -28,7 +28,7 @@ pub(crate) struct FixResult {
|
||||
|
||||
/// Fix errors in a file, and write the fixed source code to disk.
|
||||
pub(crate) fn fix_file(
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
locator: &Locator,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> Option<FixResult> {
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn fix_file(
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(
|
||||
diagnostics: impl Iterator<Item = &'a Diagnostic>,
|
||||
diagnostics: impl Iterator<Item = &'a OldDiagnostic>,
|
||||
locator: &'a Locator<'a>,
|
||||
) -> FixResult {
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
@@ -63,7 +63,7 @@ fn apply_fixes<'a>(
|
||||
let mut source_map = SourceMap::default();
|
||||
|
||||
for (code, name, fix) in diagnostics
|
||||
.filter_map(|msg| msg.secondary_code().map(|code| (code, msg.name(), msg)))
|
||||
.filter_map(|msg| msg.noqa_code().map(|code| (code, msg.name(), msg)))
|
||||
.filter_map(|(code, name, diagnostic)| diagnostic.fix().map(|fix| (code, name, fix)))
|
||||
.sorted_by(|(_, name1, fix1), (_, name2, fix2)| cmp_fix(name1, name2, fix1, fix2))
|
||||
{
|
||||
@@ -172,25 +172,26 @@ mod tests {
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::OldDiagnostic;
|
||||
use crate::fix::{FixResult, apply_fixes};
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
use crate::{Edit, Fix};
|
||||
use crate::{Locator, Violation};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
|
||||
fn create_diagnostics(
|
||||
filename: &str,
|
||||
source: &str,
|
||||
edit: impl IntoIterator<Item = Edit>,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> Vec<OldDiagnostic> {
|
||||
edit.into_iter()
|
||||
.map(|edit| {
|
||||
// The choice of rule here is arbitrary.
|
||||
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
|
||||
let mut diagnostic = OldDiagnostic::new(
|
||||
MissingNewlineAtEndOfFile,
|
||||
edit.range(),
|
||||
&SourceFileBuilder::new(filename, source).finish(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
diagnostic.fix = Some(Fix::safe_edit(edit));
|
||||
diagnostic
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -5,7 +5,6 @@ use ruff_python_ast::Stmt;
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{TokenKind, Tokens};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_python_trivia::{PythonWhitespace, textwrap::indent};
|
||||
use ruff_source_file::{LineRanges, UniversalNewlineIterator};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
@@ -307,7 +306,7 @@ fn match_semicolon(s: &str) -> Option<TextSize> {
|
||||
fn match_continuation(s: &str) -> Option<TextSize> {
|
||||
for (offset, c) in s.char_indices() {
|
||||
match c {
|
||||
_ if is_python_whitespace(c) => continue,
|
||||
' ' | '\t' => continue,
|
||||
'\\' => return Some(TextSize::try_from(offset).unwrap()),
|
||||
_ => break,
|
||||
}
|
||||
|
||||
@@ -14,6 +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(crate) use ruff_diagnostics::{Applicability, Edit, Fix};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata};
|
||||
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use ruff_python_parser::semantic_errors::SemanticSyntaxError;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, SecondaryCode};
|
||||
use ruff_notebook::Notebook;
|
||||
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_text_size::Ranged;
|
||||
|
||||
use crate::OldDiagnostic;
|
||||
use crate::checkers::ast::{LintContext, check_ast};
|
||||
use crate::checkers::filesystem::check_file_path;
|
||||
use crate::checkers::imports::check_imports;
|
||||
use crate::checkers::noqa::check_noqa;
|
||||
use crate::checkers::physical_lines::check_physical_lines;
|
||||
use crate::checkers::tokens::check_tokens;
|
||||
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::create_syntax_error_diagnostic;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::is_py314_support_enabled;
|
||||
@@ -40,7 +42,7 @@ pub(crate) mod float;
|
||||
|
||||
pub struct LinterResult {
|
||||
/// A collection of diagnostic messages generated by the linter.
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
pub diagnostics: Vec<OldDiagnostic>,
|
||||
/// Flag indicating that the parsed source code does not contain any
|
||||
/// [`ParseError`]s
|
||||
has_valid_syntax: bool,
|
||||
@@ -93,25 +95,25 @@ struct FixCount {
|
||||
|
||||
/// A mapping from a noqa code to the corresponding lint name and a count of applied fixes.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct FixTable(hashbrown::HashMap<SecondaryCode, FixCount, rustc_hash::FxBuildHasher>);
|
||||
pub struct FixTable(FxHashMap<NoqaCode, FixCount>);
|
||||
|
||||
impl FixTable {
|
||||
pub fn counts(&self) -> impl Iterator<Item = usize> {
|
||||
self.0.values().map(|fc| fc.count)
|
||||
}
|
||||
|
||||
pub fn entry<'a>(&'a mut self, code: &'a SecondaryCode) -> FixTableEntry<'a> {
|
||||
FixTableEntry(self.0.entry_ref(code))
|
||||
pub fn entry(&mut self, code: NoqaCode) -> FixTableEntry {
|
||||
FixTableEntry(self.0.entry(code))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&SecondaryCode, &'static str, usize)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (NoqaCode, &'static str, usize)> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|(code, FixCount { rule_name, count })| (code, *rule_name, *count))
|
||||
.map(|(code, FixCount { rule_name, count })| (*code, *rule_name, *count))
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &SecondaryCode> {
|
||||
self.0.keys()
|
||||
pub fn keys(&self) -> impl Iterator<Item = NoqaCode> {
|
||||
self.0.keys().copied()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@@ -119,9 +121,7 @@ impl FixTable {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixTableEntry<'a>(
|
||||
hashbrown::hash_map::EntryRef<'a, 'a, SecondaryCode, SecondaryCode, FixCount, FxBuildHasher>,
|
||||
);
|
||||
pub struct FixTableEntry<'a>(Entry<'a, NoqaCode, FixCount>);
|
||||
|
||||
impl<'a> FixTableEntry<'a> {
|
||||
pub fn or_default(self, rule_name: &'static str) -> &'a mut usize {
|
||||
@@ -144,7 +144,7 @@ pub struct FixerResult<'a> {
|
||||
pub fixed: FixTable,
|
||||
}
|
||||
|
||||
/// Generate [`Diagnostic`]s from the source code contents at the given `Path`.
|
||||
/// Generate [`OldDiagnostic`]s from the source code contents at the given `Path`.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn check_path(
|
||||
path: &Path,
|
||||
@@ -159,7 +159,7 @@ pub fn check_path(
|
||||
source_type: PySourceType,
|
||||
parsed: &Parsed<ModModule>,
|
||||
target_version: TargetVersion,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> Vec<OldDiagnostic> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut context = LintContext::new(path, locator.contents(), settings);
|
||||
|
||||
@@ -381,7 +381,7 @@ pub fn check_path(
|
||||
if !parsed.has_valid_syntax() {
|
||||
// Avoid fixing in case the source code contains syntax errors.
|
||||
for diagnostic in &mut diagnostics {
|
||||
diagnostic.remove_fix();
|
||||
diagnostic.fix = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,6 +392,7 @@ pub fn check_path(
|
||||
parsed.errors(),
|
||||
syntax_errors,
|
||||
&semantic_syntax_errors,
|
||||
locator,
|
||||
directives,
|
||||
&source_file,
|
||||
)
|
||||
@@ -457,7 +458,7 @@ pub fn add_noqa_to_path(
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a [`Diagnostic`] for each diagnostic triggered by the given source code.
|
||||
/// Generate an [`OldDiagnostic`] for each diagnostic triggered by the given source code.
|
||||
pub fn lint_only(
|
||||
path: &Path,
|
||||
package: Option<PackageRoot<'_>>,
|
||||
@@ -514,7 +515,7 @@ pub fn lint_only(
|
||||
|
||||
LinterResult {
|
||||
has_valid_syntax: parsed.has_valid_syntax(),
|
||||
has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_invalid_syntax),
|
||||
has_no_syntax_errors: !diagnostics.iter().any(OldDiagnostic::is_syntax_error),
|
||||
diagnostics,
|
||||
}
|
||||
}
|
||||
@@ -523,32 +524,30 @@ pub fn lint_only(
|
||||
///
|
||||
/// Also use `directives` to attach noqa offsets to lint diagnostics.
|
||||
fn diagnostics_to_messages(
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
diagnostics: Vec<OldDiagnostic>,
|
||||
parse_errors: &[ParseError],
|
||||
unsupported_syntax_errors: &[UnsupportedSyntaxError],
|
||||
semantic_syntax_errors: &[SemanticSyntaxError],
|
||||
locator: &Locator,
|
||||
directives: &Directives,
|
||||
source_file: &SourceFile,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> Vec<OldDiagnostic> {
|
||||
parse_errors
|
||||
.iter()
|
||||
.map(|parse_error| {
|
||||
create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error)
|
||||
OldDiagnostic::from_parse_error(parse_error, locator, source_file.clone())
|
||||
})
|
||||
.chain(unsupported_syntax_errors.iter().map(|syntax_error| {
|
||||
create_syntax_error_diagnostic(source_file.clone(), syntax_error, syntax_error)
|
||||
OldDiagnostic::from_unsupported_syntax_error(syntax_error, source_file.clone())
|
||||
}))
|
||||
.chain(
|
||||
semantic_syntax_errors
|
||||
.iter()
|
||||
.map(|error| create_syntax_error_diagnostic(source_file.clone(), error, error)),
|
||||
.map(|error| OldDiagnostic::from_semantic_syntax_error(error, source_file.clone())),
|
||||
)
|
||||
.chain(diagnostics.into_iter().map(|mut diagnostic| {
|
||||
let noqa_offset = directives
|
||||
.noqa_line_for
|
||||
.resolve(diagnostic.expect_range().start());
|
||||
diagnostic.set_noqa_offset(noqa_offset);
|
||||
diagnostic
|
||||
.chain(diagnostics.into_iter().map(|diagnostic| {
|
||||
let noqa_offset = directives.noqa_line_for.resolve(diagnostic.start());
|
||||
diagnostic.with_noqa_offset(noqa_offset)
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
@@ -629,7 +628,7 @@ pub fn lint_fix<'a>(
|
||||
|
||||
if iterations == 0 {
|
||||
has_valid_syntax = parsed.has_valid_syntax();
|
||||
has_no_syntax_errors = !diagnostics.iter().any(Diagnostic::is_invalid_syntax);
|
||||
has_no_syntax_errors = !diagnostics.iter().any(OldDiagnostic::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
|
||||
@@ -679,16 +678,18 @@ pub fn lint_fix<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_rule_codes<T>(rules: impl IntoIterator<Item = T>) -> String
|
||||
where
|
||||
T: Ord + PartialEq + std::fmt::Display,
|
||||
{
|
||||
rules.into_iter().sorted_unstable().dedup().join(", ")
|
||||
fn collect_rule_codes(rules: impl IntoIterator<Item = NoqaCode>) -> String {
|
||||
rules
|
||||
.into_iter()
|
||||
.map(|rule| rule.to_string())
|
||||
.sorted_unstable()
|
||||
.dedup()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
#[expect(clippy::print_stderr)]
|
||||
fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[Diagnostic]) {
|
||||
let codes = collect_rule_codes(diagnostics.iter().filter_map(Diagnostic::secondary_code));
|
||||
fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[OldDiagnostic]) {
|
||||
let codes = collect_rule_codes(diagnostics.iter().filter_map(OldDiagnostic::noqa_code));
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
@@ -720,11 +721,11 @@ This indicates a bug in Ruff. If you could open an issue at:
|
||||
}
|
||||
|
||||
#[expect(clippy::print_stderr)]
|
||||
fn report_fix_syntax_error<'a>(
|
||||
fn report_fix_syntax_error(
|
||||
path: &Path,
|
||||
transformed: &str,
|
||||
error: &ParseError,
|
||||
rules: impl IntoIterator<Item = &'a SecondaryCode>,
|
||||
rules: impl IntoIterator<Item = NoqaCode>,
|
||||
) {
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
@@ -806,12 +807,13 @@ mod tests {
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::ParseOptions;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::Ranged;
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
|
||||
use crate::linter::check_path;
|
||||
use crate::message::OldDiagnostic;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::source_kind::SourceKind;
|
||||
@@ -969,7 +971,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<Diagnostic> {
|
||||
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<OldDiagnostic> {
|
||||
let contents = dedent(contents);
|
||||
test_contents_syntax_errors(
|
||||
&SourceKind::Python(contents.to_string()),
|
||||
@@ -984,7 +986,7 @@ mod tests {
|
||||
source_kind: &SourceKind,
|
||||
path: &Path,
|
||||
settings: &LinterSettings,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> Vec<OldDiagnostic> {
|
||||
let source_type = PySourceType::from(path);
|
||||
let target_version = settings.resolve_target_version(path);
|
||||
let options =
|
||||
@@ -1015,7 +1017,7 @@ mod tests {
|
||||
&parsed,
|
||||
target_version,
|
||||
);
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
diagnostics.sort_by_key(Ranged::start);
|
||||
diagnostics
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use fern;
|
||||
use log::Level;
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_source_file::{LineColumn, LineIndex, OneIndexed, SourceCode};
|
||||
@@ -248,7 +248,7 @@ impl Display for DisplayParseError {
|
||||
row = location.line,
|
||||
column = location.column,
|
||||
colon = ":".cyan(),
|
||||
inner = self.error.error
|
||||
inner = &DisplayParseErrorType(&self.error.error)
|
||||
)
|
||||
}
|
||||
ErrorLocation::Cell(cell, location) => {
|
||||
@@ -259,13 +259,27 @@ impl Display for DisplayParseError {
|
||||
row = location.line,
|
||||
column = location.column,
|
||||
colon = ":".cyan(),
|
||||
inner = self.error.error
|
||||
inner = &DisplayParseErrorType(&self.error.error)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayParseErrorType<'a>(&'a ParseErrorType);
|
||||
|
||||
impl<'a> DisplayParseErrorType<'a> {
|
||||
pub(crate) fn new(error: &'a ParseErrorType) -> Self {
|
||||
Self(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DisplayParseErrorType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", TruncateAtNewline(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ErrorLocation {
|
||||
/// The error occurred in a Python file.
|
||||
@@ -274,6 +288,44 @@ enum ErrorLocation {
|
||||
Cell(OneIndexed, LineColumn),
|
||||
}
|
||||
|
||||
/// Truncates the display text before the first newline character to avoid line breaks.
|
||||
struct TruncateAtNewline<'a>(&'a dyn Display);
|
||||
|
||||
impl Display for TruncateAtNewline<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
struct TruncateAdapter<'a> {
|
||||
inner: &'a mut dyn Write,
|
||||
after_new_line: bool,
|
||||
}
|
||||
|
||||
impl Write for TruncateAdapter<'_> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
if self.after_new_line {
|
||||
Ok(())
|
||||
} else {
|
||||
if let Some(end) = s.find(['\n', '\r']) {
|
||||
self.inner.write_str(&s[..end])?;
|
||||
self.inner.write_str("\u{23ce}...")?;
|
||||
self.after_new_line = true;
|
||||
Ok(())
|
||||
} else {
|
||||
self.inner.write_str(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write!(
|
||||
TruncateAdapter {
|
||||
inner: f,
|
||||
after_new_line: false,
|
||||
},
|
||||
"{}",
|
||||
self.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::logging::LogLevel;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
|
||||
/// 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)
|
||||
@@ -14,27 +13,27 @@ impl Emitter for AzureEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let location = if context.is_notebook(&filename) {
|
||||
let location = if context.is_notebook(&diagnostic.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.expect_ruff_start_location()
|
||||
diagnostic.compute_start_location()
|
||||
};
|
||||
|
||||
writeln!(
|
||||
writer,
|
||||
"##vso[task.logissue type=error\
|
||||
;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}",
|
||||
filename = diagnostic.filename(),
|
||||
line = location.line,
|
||||
col = location.column,
|
||||
code = diagnostic
|
||||
.secondary_code()
|
||||
.noqa_code()
|
||||
.map_or_else(String::new, |code| format!("code={code};")),
|
||||
body = diagnostic.body(),
|
||||
)?;
|
||||
|
||||
@@ -2,12 +2,13 @@ use std::fmt::{Display, Formatter};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::{Color, ColoredString, Colorize, Styles};
|
||||
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::{OneIndexed, SourceFile};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::message::OldDiagnostic;
|
||||
use crate::text_helpers::ShowNonprinting;
|
||||
use crate::{Applicability, Fix};
|
||||
|
||||
@@ -25,9 +26,9 @@ pub(super) struct Diff<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Diff<'a> {
|
||||
pub(crate) fn from_message(message: &'a Diagnostic) -> Option<Diff<'a>> {
|
||||
pub(crate) fn from_message(message: &'a OldDiagnostic) -> Option<Diff<'a>> {
|
||||
message.fix().map(|fix| Diff {
|
||||
source_code: message.expect_ruff_source_file(),
|
||||
source_code: message.source_file(),
|
||||
fix,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
|
||||
/// 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)
|
||||
@@ -15,13 +14,12 @@ impl Emitter for GithubEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
let source_location = diagnostic.expect_ruff_start_location();
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let location = if context.is_notebook(&filename) {
|
||||
let source_location = diagnostic.compute_start_location();
|
||||
let location = if context.is_notebook(&diagnostic.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
LineColumn::default()
|
||||
@@ -29,15 +27,15 @@ impl Emitter for GithubEmitter {
|
||||
source_location
|
||||
};
|
||||
|
||||
let end_location = diagnostic.expect_ruff_end_location();
|
||||
let end_location = diagnostic.compute_end_location();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = diagnostic
|
||||
.secondary_code()
|
||||
.noqa_code()
|
||||
.map_or_else(String::new, |code| format!(" ({code})")),
|
||||
file = filename,
|
||||
file = diagnostic.filename(),
|
||||
row = source_location.line,
|
||||
column = source_location.column,
|
||||
end_row = end_location.line,
|
||||
@@ -47,12 +45,12 @@ impl Emitter for GithubEmitter {
|
||||
write!(
|
||||
writer,
|
||||
"{path}:{row}:{column}:",
|
||||
path = relativize_path(&filename),
|
||||
path = relativize_path(&*diagnostic.filename()),
|
||||
row = location.line,
|
||||
column = location.column,
|
||||
)?;
|
||||
|
||||
if let Some(code) = diagnostic.secondary_code() {
|
||||
if let Some(code) = diagnostic.noqa_code() {
|
||||
write!(writer, " {code}")?;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,8 @@ use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::json;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
|
||||
use crate::fs::{relativize_path, relativize_path_to};
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
|
||||
/// Generate JSON with violations in GitLab CI format
|
||||
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
|
||||
@@ -30,7 +28,7 @@ impl Emitter for GitlabEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
serde_json::to_writer_pretty(
|
||||
@@ -47,7 +45,7 @@ impl Emitter for GitlabEmitter {
|
||||
}
|
||||
|
||||
struct SerializedMessages<'a> {
|
||||
diagnostics: &'a [Diagnostic],
|
||||
diagnostics: &'a [OldDiagnostic],
|
||||
context: &'a EmitterContext<'a>,
|
||||
project_dir: Option<&'a str>,
|
||||
}
|
||||
@@ -61,11 +59,10 @@ impl Serialize for SerializedMessages<'_> {
|
||||
let mut fingerprints = HashSet::<u64>::with_capacity(self.diagnostics.len());
|
||||
|
||||
for diagnostic in self.diagnostics {
|
||||
let start_location = diagnostic.expect_ruff_start_location();
|
||||
let end_location = diagnostic.expect_ruff_end_location();
|
||||
let start_location = diagnostic.compute_start_location();
|
||||
let end_location = diagnostic.compute_end_location();
|
||||
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let lines = if self.context.is_notebook(&filename) {
|
||||
let lines = if self.context.is_notebook(&diagnostic.filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
json!({
|
||||
@@ -80,8 +77,8 @@ impl Serialize for SerializedMessages<'_> {
|
||||
};
|
||||
|
||||
let path = self.project_dir.as_ref().map_or_else(
|
||||
|| relativize_path(&filename),
|
||||
|project_dir| relativize_path_to(&filename, project_dir),
|
||||
|| relativize_path(&*diagnostic.filename()),
|
||||
|project_dir| relativize_path_to(&*diagnostic.filename(), project_dir),
|
||||
);
|
||||
|
||||
let mut message_fingerprint = fingerprint(diagnostic, &path, 0);
|
||||
@@ -93,15 +90,18 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let (description, check_name) = if let Some(code) = diagnostic.secondary_code() {
|
||||
(diagnostic.body().to_string(), code.as_str())
|
||||
let (description, check_name) = if let Some(code) = diagnostic.noqa_code() {
|
||||
(diagnostic.body().to_string(), code.to_string())
|
||||
} else {
|
||||
let description = diagnostic.body();
|
||||
let description_without_prefix = description
|
||||
.strip_prefix("SyntaxError: ")
|
||||
.unwrap_or(description);
|
||||
|
||||
(description_without_prefix.to_string(), "syntax-error")
|
||||
(
|
||||
description_without_prefix.to_string(),
|
||||
"syntax-error".to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
let value = json!({
|
||||
@@ -123,7 +123,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
|
||||
/// Generate a unique fingerprint to identify a violation.
|
||||
fn fingerprint(message: &Diagnostic, project_path: &str, salt: u64) -> u64 {
|
||||
fn fingerprint(message: &OldDiagnostic, project_path: &str, salt: u64) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
salt.hash(&mut hasher);
|
||||
|
||||
@@ -4,14 +4,15 @@ use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::diff::calculate_print_width;
|
||||
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
|
||||
use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename};
|
||||
use crate::message::{
|
||||
Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename,
|
||||
};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,7 +46,7 @@ impl Emitter for GroupedEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for (filename, messages) in group_diagnostics_by_filename(diagnostics) {
|
||||
@@ -72,7 +73,7 @@ impl Emitter for GroupedEmitter {
|
||||
writer,
|
||||
"{}",
|
||||
DisplayGroupedMessage {
|
||||
notebook_index: context.notebook_index(&message.expect_ruff_filename()),
|
||||
notebook_index: context.notebook_index(&message.filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
unsafe_fixes: self.unsafe_fixes,
|
||||
|
||||
@@ -4,13 +4,12 @@ use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed, SourceCode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Edit;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JsonEmitter;
|
||||
@@ -19,7 +18,7 @@ impl Emitter for JsonEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
serde_json::to_writer_pretty(
|
||||
@@ -35,7 +34,7 @@ impl Emitter for JsonEmitter {
|
||||
}
|
||||
|
||||
struct ExpandedMessages<'a> {
|
||||
diagnostics: &'a [Diagnostic],
|
||||
diagnostics: &'a [OldDiagnostic],
|
||||
context: &'a EmitterContext<'a>,
|
||||
}
|
||||
|
||||
@@ -55,11 +54,10 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn message_to_json_value(message: &Diagnostic, context: &EmitterContext) -> Value {
|
||||
let source_file = message.expect_ruff_source_file();
|
||||
pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterContext) -> Value {
|
||||
let source_file = message.source_file();
|
||||
let source_code = source_file.to_source_code();
|
||||
let filename = message.expect_ruff_filename();
|
||||
let notebook_index = context.notebook_index(&filename);
|
||||
let notebook_index = context.notebook_index(&message.filename());
|
||||
|
||||
let fix = message.fix().map(|fix| {
|
||||
json!({
|
||||
@@ -69,8 +67,8 @@ pub(crate) fn message_to_json_value(message: &Diagnostic, context: &EmitterConte
|
||||
})
|
||||
});
|
||||
|
||||
let mut start_location = source_code.line_column(message.expect_range().start());
|
||||
let mut end_location = source_code.line_column(message.expect_range().end());
|
||||
let mut start_location = source_code.line_column(message.start());
|
||||
let mut end_location = source_code.line_column(message.end());
|
||||
let mut noqa_location = message
|
||||
.noqa_offset()
|
||||
.map(|offset| source_code.line_column(offset));
|
||||
@@ -89,14 +87,14 @@ pub(crate) fn message_to_json_value(message: &Diagnostic, context: &EmitterConte
|
||||
}
|
||||
|
||||
json!({
|
||||
"code": message.secondary_code(),
|
||||
"code": message.noqa_code().map(|code| code.to_string()),
|
||||
"url": message.to_url(),
|
||||
"message": message.body(),
|
||||
"fix": fix,
|
||||
"cell": notebook_cell_index,
|
||||
"location": location_to_json(start_location),
|
||||
"end_location": location_to_json(end_location),
|
||||
"filename": filename,
|
||||
"filename": message.filename(),
|
||||
"noqa_row": noqa_location.map(|location| location.line)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
|
||||
use crate::message::json::message_to_json_value;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JsonLinesEmitter;
|
||||
@@ -12,7 +10,7 @@ impl Emitter for JsonLinesEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
|
||||
@@ -3,10 +3,11 @@ use std::path::Path;
|
||||
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename};
|
||||
use crate::message::{
|
||||
Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JunitEmitter;
|
||||
@@ -15,7 +16,7 @@ impl Emitter for JunitEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[OldDiagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut report = Report::new("ruff");
|
||||
@@ -43,7 +44,7 @@ impl Emitter for JunitEmitter {
|
||||
} = message;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(message.body());
|
||||
let location = if context.is_notebook(&message.expect_ruff_filename()) {
|
||||
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()
|
||||
@@ -58,7 +59,7 @@ impl Emitter for JunitEmitter {
|
||||
body = message.body()
|
||||
));
|
||||
let mut case = TestCase::new(
|
||||
if let Some(code) = message.secondary_code() {
|
||||
if let Some(code) = message.noqa_code() {
|
||||
format!("org.ruff.{code}")
|
||||
} else {
|
||||
"org.ruff".to_string()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user