Compare commits
103 Commits
0.12.1
...
zb/dev-dri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40b4aa28f9 | ||
|
|
ea4bf00c23 | ||
|
|
7f4aa4b3fb | ||
|
|
34c98361ae | ||
|
|
38bb96a6c2 | ||
|
|
a014d55455 | ||
|
|
306f6f17a9 | ||
|
|
b233888f00 | ||
|
|
540cbd9085 | ||
|
|
0112f7f0e4 | ||
|
|
d0f0577ac7 | ||
|
|
dc56c33618 | ||
|
|
a95c18a8e1 | ||
|
|
e212dc2e8e | ||
|
|
c4f2eec865 | ||
|
|
9fc04d6bf0 | ||
|
|
352b896c89 | ||
|
|
321575e48f | ||
|
|
066018859f | ||
|
|
f76d3f87cf | ||
|
|
5f426b9f8b | ||
|
|
37ba185c04 | ||
|
|
93413d3631 | ||
|
|
efd9b75352 | ||
|
|
4cf56d7ad4 | ||
|
|
4e4e428a95 | ||
|
|
522fd4462e | ||
|
|
e599c9d0d3 | ||
|
|
e9b5ea71b3 | ||
|
|
ebc70a4002 | ||
|
|
f7fc8fb084 | ||
|
|
cdf91b8b74 | ||
|
|
d1e705738e | ||
|
|
c3d9b21db5 | ||
|
|
316c1b21e2 | ||
|
|
47733c0647 | ||
|
|
48366a7bbb | ||
|
|
cc736c3a51 | ||
|
|
8cc14ad02d | ||
|
|
667dc62038 | ||
|
|
dac4e356eb | ||
|
|
ebf59e2bef | ||
|
|
c6fd11fe36 | ||
|
|
7d468ee58a | ||
|
|
4016521bf6 | ||
|
|
b8653a9d3a | ||
|
|
966adca6f6 | ||
|
|
77941af1c6 | ||
|
|
4bc170a5c1 | ||
|
|
28ab61d885 | ||
|
|
4963835d0d | ||
|
|
09fa80f94c | ||
|
|
fde82fc563 | ||
|
|
96decb17a9 | ||
|
|
2ae0bd9464 | ||
|
|
34052a1185 | ||
|
|
9f0d3cca89 | ||
|
|
eb9d9c3646 | ||
|
|
4fbf7e9de8 | ||
|
|
b23b4071eb | ||
|
|
462dbadee4 | ||
|
|
a3638b3adc | ||
|
|
f857546aeb | ||
|
|
d78f18cda9 | ||
|
|
db3dcd8ad6 | ||
|
|
54769ac9f9 | ||
|
|
c80762debd | ||
|
|
4103d73224 | ||
|
|
bedb53daec | ||
|
|
0ec2ad2fa5 | ||
|
|
9469a982cc | ||
|
|
e33980fd5c | ||
|
|
053739f698 | ||
|
|
192569c848 | ||
|
|
ae7eaa9913 | ||
|
|
d88bebca32 | ||
|
|
e7aadfc28b | ||
|
|
de1f8177be | ||
|
|
9218bf72ad | ||
|
|
29927f2b59 | ||
|
|
c5995c40d3 | ||
|
|
68f98cfcd8 | ||
|
|
315adba906 | ||
|
|
523174e8be | ||
|
|
ed2e90371b | ||
|
|
90cb0d3a7b | ||
|
|
1297d6a9eb | ||
|
|
caf3c916e8 | ||
|
|
c60e590b4c | ||
|
|
a50a993b9c | ||
|
|
6802c4702f | ||
|
|
96f3c8d1ab | ||
|
|
efcb63fe3a | ||
|
|
5f6b0ded21 | ||
|
|
a3c79d8170 | ||
|
|
57bd7d055d | ||
|
|
3c18d85c7d | ||
|
|
e5e3d998c5 | ||
|
|
85b2a08b5c | ||
|
|
1874d52eda | ||
|
|
18efe2ab46 | ||
|
|
6f7b1c9bb3 | ||
|
|
a1579d82d0 |
16
.github/workflows/build-binaries.yml
vendored
16
.github/workflows/build-binaries.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build sdist"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels - x86_64"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --locked --out dist
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels - aarch64"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: aarch64
|
||||
args: --release --locked --out dist
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: --release --locked --out dist
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: auto
|
||||
@@ -304,7 +304,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: auto
|
||||
@@ -370,7 +370,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
manylinux: musllinux_1_2
|
||||
@@ -435,7 +435,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
manylinux: musllinux_1_2
|
||||
|
||||
30
.github/workflows/ci.yaml
vendored
30
.github/workflows/ci.yaml
vendored
@@ -321,14 +321,30 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Dev Drive
|
||||
run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
|
||||
|
||||
# actions/checkout does not let us clone into anywhere outside `github.workspace`, so we have to copy the clone
|
||||
- name: Copy Git Repo to Dev Drive
|
||||
env:
|
||||
RUFF_WORKSPACE: ${{ env.RUFF_WORKSPACE }}
|
||||
run: |
|
||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${env:RUFF_WORKSPACE}" -Recurse
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: ${{ env.RUFF_WORKSPACE }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
working-directory: ${{ env.RUFF_WORKSPACE }}
|
||||
run: rustup show
|
||||
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
working-directory: ${{ env.RUFF_WORKSPACE }}
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
@@ -460,7 +476,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -661,7 +677,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -712,7 +728,7 @@ jobs:
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@35be3186fc8e037e329f06b68dcd807d83dcc6dc # v1.49.2
|
||||
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||
with:
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
@@ -731,7 +747,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -774,7 +790,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -906,7 +922,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -939,7 +955,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
@@ -48,6 +48,8 @@ jobs:
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
run: |
|
||||
cd ruff
|
||||
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
93
.github/workflows/setup-dev-drive.ps1
vendored
Normal file
93
.github/workflows/setup-dev-drive.ps1
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# Configures a drive for testing in CI.
|
||||
#
|
||||
# When using standard GitHub Actions runners, a `D:` drive is present and has
|
||||
# similar or better performance characteristics than a ReFS dev drive. Sometimes
|
||||
# using a larger runner is still more performant (e.g., when running the test
|
||||
# suite) and we need to create a dev drive. This script automatically configures
|
||||
# the appropriate drive.
|
||||
#
|
||||
# When using GitHub Actions' "larger runners", the `D:` drive is not present and
|
||||
# we create a DevDrive mount on `C:`. This is purported to be more performant
|
||||
# than an ReFS drive, though we did not see a change when we switched over.
|
||||
#
|
||||
# When using Depot runners, the underling infrastructure is EC2, which does not
|
||||
# support Hyper-V. The `New-VHD` commandlet only works with Hyper-V, but we can
|
||||
# create a ReFS drive using `diskpart` and `format` directory. We cannot use a
|
||||
# DevDrive, as that also requires Hyper-V. The Depot runners use `D:` already,
|
||||
# so we must check if it's a Depot runner first, and we use `V:` as the target
|
||||
# instead.
|
||||
|
||||
|
||||
if ($env:DEPOT_RUNNER -eq "1") {
|
||||
Write-Output "DEPOT_RUNNER detected, setting up custom dev drive..."
|
||||
|
||||
# Create VHD and configure drive using diskpart
|
||||
$vhdPath = "C:\ruff_dev_drive.vhdx"
|
||||
@"
|
||||
create vdisk file="$vhdPath" maximum=20480 type=expandable
|
||||
attach vdisk
|
||||
create partition primary
|
||||
active
|
||||
assign letter=V
|
||||
"@ | diskpart
|
||||
|
||||
# Format the drive as ReFS
|
||||
format V: /fs:ReFS /q /y
|
||||
$Drive = "V:"
|
||||
|
||||
Write-Output "Custom dev drive created at $Drive"
|
||||
} elseif (Test-Path "D:\") {
|
||||
# Note `Get-PSDrive` is not sufficient because the drive letter is assigned.
|
||||
Write-Output "Using existing drive at D:"
|
||||
$Drive = "D:"
|
||||
} else {
|
||||
# The size (20 GB) is chosen empirically to be large enough for our
|
||||
# workflows; larger drives can take longer to set up.
|
||||
$Volume = New-VHD -Path C:/ruff_dev_drive.vhdx -SizeBytes 20GB |
|
||||
Mount-VHD -Passthru |
|
||||
Initialize-Disk -Passthru |
|
||||
New-Partition -AssignDriveLetter -UseMaximumSize |
|
||||
Format-Volume -DevDrive -Confirm:$false -Force
|
||||
|
||||
$Drive = "$($Volume.DriveLetter):"
|
||||
|
||||
# Set the drive as trusted
|
||||
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-designate-a-dev-drive-as-trusted
|
||||
fsutil devdrv trust $Drive
|
||||
|
||||
# Disable antivirus filtering on dev drives
|
||||
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-configure-additional-filters-on-dev-drive
|
||||
fsutil devdrv enable /disallowAv
|
||||
|
||||
# Remount so the changes take effect
|
||||
Dismount-VHD -Path C:/ruff_dev_drive.vhdx
|
||||
Mount-VHD -Path C:/ruff_dev_drive.vhdx
|
||||
|
||||
# Show some debug information
|
||||
Write-Output $Volume
|
||||
fsutil devdrv query $Drive
|
||||
|
||||
Write-Output "Using Dev Drive at $Volume"
|
||||
}
|
||||
|
||||
$Tmp = "$($Drive)\ruff-tmp"
|
||||
|
||||
# Create the directory ahead of time in an attempt to avoid race-conditions
|
||||
New-Item $Tmp -ItemType Directory
|
||||
|
||||
# Move Cargo to the dev drive
|
||||
New-Item -Path "$($Drive)/.cargo/bin" -ItemType Directory -Force
|
||||
if (Test-Path "C:/Users/runneradmin/.cargo") {
|
||||
Copy-Item -Path "C:/Users/runneradmin/.cargo/*" -Destination "$($Drive)/.cargo/" -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Output `
|
||||
"DEV_DRIVE=$($Drive)" `
|
||||
"TMP=$($Tmp)" `
|
||||
"TEMP=$($Tmp)" `
|
||||
"UV_INTERNAL__TEST_DIR=$($Tmp)" `
|
||||
"RUSTUP_HOME=$($Drive)/.rustup" `
|
||||
"CARGO_HOME=$($Drive)/.cargo" `
|
||||
"RUFF_WORKSPACE=$($Drive)/ruff" `
|
||||
"PATH=$($Drive)/.cargo/bin;$env:PATH" `
|
||||
>> $env:GITHUB_ENV
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
|
||||
@@ -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.11.13
|
||||
rev: v0.12.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -91,7 +91,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.5.3
|
||||
rev: v3.6.2
|
||||
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.9.0
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.33.0
|
||||
rev: 0.33.1
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
|
||||
257
Cargo.lock
generated
257
Cargo.lock
generated
@@ -132,6 +132,15 @@ version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
@@ -169,6 +178,36 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attribute-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54"
|
||||
dependencies = [
|
||||
"attribute-derive-macro",
|
||||
"derive-where",
|
||||
"manyhow",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attribute-derive-macro"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b"
|
||||
dependencies = [
|
||||
"collection_literals",
|
||||
"interpolator",
|
||||
"manyhow",
|
||||
"proc-macro-utils",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"quote-use",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@@ -181,6 +220,15 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
@@ -419,9 +467,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
version = "4.0.1"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4"
|
||||
checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae"
|
||||
dependencies = [
|
||||
"nix 0.29.0",
|
||||
"terminfo",
|
||||
@@ -432,22 +480,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f4cce9c27c49c4f101fffeebb1826f41a9df2e7498b7cd4d95c0658b796c6c"
|
||||
checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 1.3.3",
|
||||
"colored 2.2.0",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"statrs",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c23d880a28a2aab52d38ca8481dd7a3187157d0a952196b6db1db3c8499725"
|
||||
checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
@@ -456,9 +509,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0a2f7365e347f4f22a67e9ea689bf7bc89900a354e22e26cf8a531a42c8fbb"
|
||||
checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
@@ -481,9 +534,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8620a09dfaf37b3c45f982c4b65bd8f9b0203944da3ffa705c0fcae6b84655ff"
|
||||
checksum = "3acf1d6fe367c2ff5ff136ca723f678490c3691d59d7f2b83d5e53b7b25ac91e"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-divan-compat-macros",
|
||||
@@ -492,9 +545,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30fe872bc4214626b35d3a1706a905d0243503bb6ba3bb7be2fc59083d5d680c"
|
||||
checksum = "bcfa2013d7bee54a497d0e1410751d5de690fd67a3e9eb728ca049b6a3d16d0b"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools 0.14.0",
|
||||
@@ -506,9 +559,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "2.10.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "104caa97b36d4092d89e24e4b103b40ede1edab03c0372d19e14a33f9393132b"
|
||||
checksum = "e513100fb0e7ba02fb3824546ecd2abfb8f334262f0972225b463aad07f99ff0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -519,6 +572,12 @@ dependencies = [
|
||||
"regex-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collection_literals"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
@@ -808,6 +867,17 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
@@ -1067,6 +1137,29 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aac2af9f9a6a50e31b1e541d05b7925add83d3982c2793193fe9d4ee584323c"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a0312efd19e1c45922dfcc2d6806d3ffc4bca261f89f31fcc4f63f438d885"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.15.4",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
@@ -1377,9 +1470,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.4",
|
||||
@@ -1455,6 +1548,12 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolator"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
@@ -1716,9 +1815,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
@@ -1755,6 +1854,29 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manyhow"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587"
|
||||
dependencies = [
|
||||
"manyhow-macros",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manyhow-macros"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495"
|
||||
dependencies = [
|
||||
"proc-macro-utils",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "1.0.0"
|
||||
@@ -1981,9 +2103,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d31b8b7a99f71bdff4235faf9ce9eada0ad3562c8fbeb7d607d9f41a6ec569d"
|
||||
checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2014,6 +2136,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "papaya"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"seize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@@ -2315,6 +2447,17 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-utils"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -2391,6 +2534,28 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote-use"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"quote-use-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote-use-macros"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35"
|
||||
dependencies = [
|
||||
"proc-macro-utils",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
@@ -2564,13 +2729,14 @@ dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_fs",
|
||||
"bincode",
|
||||
"bincode 2.0.1",
|
||||
"bitflags 2.9.1",
|
||||
"cachedir",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored 3.0.0",
|
||||
"dunce",
|
||||
"filetime",
|
||||
"globwalk",
|
||||
"ignore",
|
||||
@@ -2680,6 +2846,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"etcetera",
|
||||
"filetime",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"ignore",
|
||||
"insta",
|
||||
@@ -2795,6 +2962,7 @@ dependencies = [
|
||||
name = "ruff_index"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"ruff_macros",
|
||||
"salsa",
|
||||
"static_assertions",
|
||||
@@ -2812,6 +2980,7 @@ dependencies = [
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
"hashbrown 0.15.4",
|
||||
"imperative",
|
||||
"insta",
|
||||
"is-macro",
|
||||
@@ -2907,6 +3076,7 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.9.1",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"is-macro",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
@@ -3006,6 +3176,7 @@ dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"memchr",
|
||||
"ruff_annotate_snippets",
|
||||
@@ -3112,6 +3283,7 @@ dependencies = [
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"memchr",
|
||||
"ruff_text_size",
|
||||
"serde",
|
||||
@@ -3121,6 +3293,7 @@ dependencies = [
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_test",
|
||||
@@ -3248,8 +3421,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3259,6 +3432,7 @@ dependencies = [
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"intrusive-collections",
|
||||
"papaya",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
@@ -3272,13 +3446,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=09627e450566f894956710a3fd923dc80462ae6d#09627e450566f894956710a3fd923dc80462ae6d"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3331,6 +3505,16 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "seize"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b8d813387d566f627f3ea1b914c068aac94c40ae27ec43f5f33bde65abefe7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@@ -3531,6 +3715,16 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "statrs"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.1"
|
||||
@@ -3991,6 +4185,7 @@ dependencies = [
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"crossbeam",
|
||||
"get-size2",
|
||||
"globset",
|
||||
"insta",
|
||||
"notify",
|
||||
@@ -4030,6 +4225,7 @@ dependencies = [
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.15.4",
|
||||
"indexmap",
|
||||
@@ -4091,6 +4287,7 @@ dependencies = [
|
||||
"ty_ide",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4130,6 +4327,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"path-slash",
|
||||
"ruff_db",
|
||||
"static_assertions",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
@@ -4564,11 +4762,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "7.0.3"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
|
||||
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"env_home",
|
||||
"rustix",
|
||||
"winsafe",
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# Please update rustfmt.toml when bumping the Rust edition
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.86"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -62,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 = "2.10.1" }
|
||||
codspeed-criterion-compat = { version = "2.6.0", default-features = false }
|
||||
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
|
||||
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
@@ -79,6 +79,12 @@ 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" }
|
||||
@@ -131,7 +137,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.git", rev = "09627e450566f894956710a3fd923dc80462ae6d" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "fc00eba89e5dcaa5edba51c41aa5f309b5cb126b" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
@@ -221,6 +227,7 @@ unnecessary_debug_formatting = "allow" # too many instances, the display also d
|
||||
# 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"
|
||||
|
||||
@@ -423,6 +423,7 @@ 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))
|
||||
|
||||
@@ -68,6 +68,7 @@ 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 }
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
|
||||
@@ -15,7 +14,7 @@ use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||
JsonEmitter, JsonLinesEmitter, JunitEmitter, OldDiagnostic, PylintEmitter, RdjsonEmitter,
|
||||
SarifEmitter, TextEmitter,
|
||||
SarifEmitter, SecondaryCode, TextEmitter,
|
||||
};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
@@ -36,8 +35,8 @@ bitflags! {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExpandedStatistics {
|
||||
code: Option<NoqaCode>,
|
||||
struct ExpandedStatistics<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
name: &'static str,
|
||||
count: usize,
|
||||
fixable: bool,
|
||||
@@ -303,11 +302,12 @@ impl Printer {
|
||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||
.inner
|
||||
.iter()
|
||||
.map(|message| (message.noqa_code(), message))
|
||||
.map(|message| (message.secondary_code(), message))
|
||||
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
||||
.fold(
|
||||
vec![],
|
||||
|mut acc: Vec<((Option<NoqaCode>, &OldDiagnostic), usize)>, (code, message)| {
|
||||
|mut acc: Vec<((Option<&SecondaryCode>, &OldDiagnostic), usize)>,
|
||||
(code, message)| {
|
||||
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
@@ -349,12 +349,7 @@ impl Printer {
|
||||
);
|
||||
let code_width = statistics
|
||||
.iter()
|
||||
.map(|statistic| {
|
||||
statistic
|
||||
.code
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.len()
|
||||
})
|
||||
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
||||
.max()
|
||||
.unwrap();
|
||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
||||
@@ -370,7 +365,8 @@ impl Printer {
|
||||
statistic.count.to_string().bold(),
|
||||
statistic
|
||||
.code
|
||||
.map_or_else(String::new, |rule| rule.to_string())
|
||||
.map(SecondaryCode::as_str)
|
||||
.unwrap_or_default()
|
||||
.red()
|
||||
.bold(),
|
||||
if any_fixable {
|
||||
|
||||
@@ -612,7 +612,7 @@ fn extend_passed_via_config_argument() {
|
||||
#[test]
|
||||
fn nonexistent_extend_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let project_dir = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_path = dunce::canonicalize(tempdir.path())?;
|
||||
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_path = dunce::canonicalize(tempdir.path())?;
|
||||
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = testpy.canonicalize()?;
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = testpy.canonicalize()?;
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = testpy.canonicalize()?;
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
|
||||
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 = tempdir.path().canonicalize()?;
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
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 = testpy.canonicalize()?;
|
||||
let testpy_canon = dunce::canonicalize(testpy)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/")]
|
||||
|
||||
@@ -12,10 +12,8 @@ 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 = tempdir
|
||||
.path()
|
||||
.canonicalize()
|
||||
.context("Failed to canonical tempdir path.")?;
|
||||
let project_dir =
|
||||
dunce::canonicalize(tempdir.path()).context("Failed to canonical tempdir path.")?;
|
||||
|
||||
std::fs::write(
|
||||
project_dir.join("pyproject.toml"),
|
||||
|
||||
@@ -821,11 +821,7 @@ impl DisplaySourceAnnotation<'_> {
|
||||
// Length of this annotation as displayed in the stderr output
|
||||
fn len(&self) -> usize {
|
||||
// Account for usize underflows
|
||||
if self.range.1 > self.range.0 {
|
||||
self.range.1 - self.range.0
|
||||
} else {
|
||||
self.range.0 - self.range.1
|
||||
}
|
||||
self.range.1.abs_diff(self.range.0)
|
||||
}
|
||||
|
||||
fn takes_space(&self) -> bool {
|
||||
|
||||
@@ -348,10 +348,10 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_many_attribute_assignments(criterion: &mut Criterion) {
|
||||
fn benchmark_complex_constrained_attributes_1(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[many_attribute_assignments]", |b| {
|
||||
criterion.bench_function("ty_micro[complex_constrained_attributes_1]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a regression benchmark for https://github.com/astral-sh/ty/issues/627.
|
||||
@@ -400,6 +400,47 @@ fn benchmark_many_attribute_assignments(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,
|
||||
@@ -534,7 +575,8 @@ criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments,
|
||||
benchmark_many_attribute_assignments,
|
||||
benchmark_complex_constrained_attributes_1,
|
||||
benchmark_complex_constrained_attributes_2,
|
||||
);
|
||||
criterion_group!(project, anyio, attrs, hydra);
|
||||
criterion_main!(check_file, micro, project);
|
||||
|
||||
@@ -14,10 +14,10 @@ license = { workspace = true }
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true, optional = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["get-size"] }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_source_file = { workspace = true, features = ["get-size"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
anstyle = { workspace = true }
|
||||
@@ -27,6 +27,7 @@ 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 }
|
||||
|
||||
@@ -19,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)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
@@ -270,7 +270,7 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
@@ -342,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)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
@@ -443,7 +443,7 @@ impl SubDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: Severity,
|
||||
message: DiagnosticMessage,
|
||||
@@ -471,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)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
@@ -591,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)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
@@ -605,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)]
|
||||
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
pub struct LintName(&'static str);
|
||||
|
||||
impl LintName {
|
||||
@@ -645,7 +645,7 @@ impl PartialEq<&str> for LintName {
|
||||
}
|
||||
|
||||
/// Uniquely identifies the kind of a diagnostic.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
pub enum DiagnosticId {
|
||||
Panic,
|
||||
|
||||
@@ -800,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)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub enum UnifiedFile {
|
||||
Ty(File),
|
||||
Ruff(SourceFile),
|
||||
@@ -852,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)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub struct Span {
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
@@ -924,7 +924,7 @@ impl From<crate::files::FileRange> for Span {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
pub enum Severity {
|
||||
Info,
|
||||
Warning,
|
||||
@@ -1087,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)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct DiagnosticMessage(Box<str>);
|
||||
|
||||
impl DiagnosticMessage {
|
||||
|
||||
@@ -637,6 +637,22 @@ 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())
|
||||
@@ -708,7 +724,6 @@ 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};
|
||||
@@ -2221,7 +2236,7 @@ watermelon
|
||||
///
|
||||
/// (This will set the "printed" flag on `Diagnostic`.)
|
||||
fn render(&self, diag: &Diagnostic) -> String {
|
||||
diag.display(&self.db.upcast(), &self.config).to_string()
|
||||
diag.display(&self.db, &self.config).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,12 +263,23 @@ impl Files {
|
||||
|
||||
impl fmt::Debug for Files {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut map = f.debug_map();
|
||||
if f.alternate() {
|
||||
let mut map = f.debug_map();
|
||||
|
||||
for entry in self.inner.system_by_path.iter() {
|
||||
map.entry(entry.key(), entry.value());
|
||||
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()
|
||||
}
|
||||
map.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +319,9 @@ 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`].
|
||||
///
|
||||
|
||||
@@ -36,12 +36,6 @@ 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.
|
||||
///
|
||||
@@ -76,11 +70,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>>>;
|
||||
|
||||
@@ -153,15 +147,6 @@ 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,6 +2,7 @@ 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};
|
||||
|
||||
@@ -20,7 +21,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)]
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
@@ -44,9 +45,10 @@ 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)]
|
||||
#[derive(Clone, get_size2::GetSize)]
|
||||
pub struct ParsedModule {
|
||||
file: File,
|
||||
#[get_size(size_fn = arc_swap_size)]
|
||||
inner: Arc<ArcSwapOption<indexed::IndexedModule>>,
|
||||
}
|
||||
|
||||
@@ -142,6 +144,18 @@ 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;
|
||||
|
||||
@@ -150,7 +164,7 @@ mod indexed {
|
||||
use ruff_python_parser::Parsed;
|
||||
|
||||
/// A wrapper around the AST that allows access to AST nodes by index.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, get_size2::GetSize)]
|
||||
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]
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
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)]
|
||||
#[derive(Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct SourceText {
|
||||
inner: Arc<SourceTextInner>,
|
||||
}
|
||||
@@ -123,8 +123,9 @@ impl std::fmt::Debug for SourceText {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SourceTextInner {
|
||||
#[get_size(ignore)]
|
||||
count: Count<SourceText>,
|
||||
kind: SourceTextKind,
|
||||
read_error: Option<SourceTextError>,
|
||||
@@ -136,6 +137,19 @@ 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)
|
||||
@@ -148,7 +162,7 @@ impl From<Notebook> for SourceTextKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
pub enum SourceTextError {
|
||||
#[error("Failed to read notebook: {0}`")]
|
||||
FailedToReadNotebook(String),
|
||||
@@ -157,7 +171,7 @@ pub enum SourceTextError {
|
||||
}
|
||||
|
||||
/// Computes the [`LineIndex`] for `file`.
|
||||
#[salsa::tracked]
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
|
||||
let _span = tracing::trace_span!("line_index", ?file).entered();
|
||||
|
||||
|
||||
@@ -124,6 +124,11 @@ 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:
|
||||
@@ -186,6 +191,9 @@ 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;
|
||||
@@ -226,11 +234,52 @@ 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,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, btree_map};
|
||||
use std::io;
|
||||
use std::iter::FusedIterator;
|
||||
use std::sync::{Arc, RwLock, RwLockWriteGuard};
|
||||
|
||||
@@ -153,6 +154,26 @@ 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`.
|
||||
@@ -278,14 +299,14 @@ impl MemoryFileSystem {
|
||||
let normalized = fs.normalize_path(path);
|
||||
|
||||
match by_path.entry(normalized) {
|
||||
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
Entry::File(_) => {
|
||||
entry.remove();
|
||||
Ok(())
|
||||
}
|
||||
Entry::Directory(_) => Err(is_a_directory()),
|
||||
},
|
||||
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,14 +366,14 @@ impl MemoryFileSystem {
|
||||
}
|
||||
|
||||
match by_path.entry(normalized.clone()) {
|
||||
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||
Entry::Directory(_) => {
|
||||
entry.remove();
|
||||
Ok(())
|
||||
}
|
||||
Entry::File(_) => Err(not_a_directory()),
|
||||
},
|
||||
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,39 @@ 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`]
|
||||
@@ -192,6 +225,10 @@ impl System for OsSystem {
|
||||
})
|
||||
}
|
||||
|
||||
fn as_writable(&self) -> Option<&dyn WritableSystem> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -310,6 +347,10 @@ 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,6 +503,12 @@ 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,6 +102,10 @@ 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,
|
||||
@@ -123,6 +127,10 @@ 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
|
||||
}
|
||||
@@ -149,6 +157,10 @@ 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)
|
||||
}
|
||||
@@ -335,6 +347,10 @@ 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,
|
||||
@@ -357,6 +373,10 @@ 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
|
||||
}
|
||||
@@ -377,6 +397,10 @@ 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,6 +87,12 @@ 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()
|
||||
|
||||
@@ -114,6 +114,7 @@ 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 },
|
||||
@@ -136,7 +137,7 @@ impl Set {
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
|
||||
let header_level = if parents.is_empty() { "###" } else { "####" };
|
||||
let header_level = "#".repeat(parents.len() + 1);
|
||||
|
||||
let _ = writeln!(output, "{header_level} `{name}`");
|
||||
|
||||
|
||||
@@ -73,12 +73,20 @@ fn generate_markdown() -> String {
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
|
||||
// Increase the header-level by one
|
||||
// Reformat headers as bold text
|
||||
let mut in_code_fence = false;
|
||||
let documentation = lint
|
||||
.documentation_lines()
|
||||
.map(|line| {
|
||||
if line.starts_with('#') {
|
||||
Cow::Owned(format!("#{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()
|
||||
))
|
||||
} else {
|
||||
Cow::Borrowed(line)
|
||||
}
|
||||
@@ -87,21 +95,15 @@ fn generate_markdown() -> String {
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"**Default level**: {level}
|
||||
|
||||
<details>
|
||||
<summary>{summary}</summary>
|
||||
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>
|
||||
|
||||
{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())
|
||||
@@ -113,25 +115,6 @@ fn generate_markdown() -> String {
|
||||
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;
|
||||
|
||||
@@ -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,15 +66,6 @@ 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,6 +14,7 @@ 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)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[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(&mut old_vec.raw, new_value.raw) }
|
||||
unsafe { salsa::Update::maybe_update(&raw mut old_vec.raw, new_value.raw) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ 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 }
|
||||
|
||||
5
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420_2.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420_2.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_executable/EXE003_uv_tool.py
vendored
Executable file
2
crates/ruff_linter/resources/test/fixtures/flake8_executable/EXE003_uv_tool.py
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env -S uv tool run ruff check --isolated --select EXE003
|
||||
print("hello world")
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_executable/EXE003_uvx.py
vendored
Executable file
2
crates/ruff_linter/resources/test/fixtures/flake8_executable/EXE003_uvx.py
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env -S uvx ruff check --isolated --select EXE003
|
||||
print("hello world")
|
||||
@@ -119,4 +119,26 @@ 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
|
||||
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
|
||||
|
||||
@@ -111,3 +111,25 @@ 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
|
||||
|
||||
@@ -170,3 +170,25 @@ 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]
|
||||
|
||||
@@ -69,3 +69,11 @@ def func():
|
||||
Returns:
|
||||
the value
|
||||
"""
|
||||
|
||||
|
||||
def func():
|
||||
("""Docstring.
|
||||
|
||||
Raises:
|
||||
ValueError: An error.
|
||||
""")
|
||||
|
||||
@@ -57,3 +57,7 @@ _ = 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,3 +90,52 @@ 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,3 +105,23 @@ 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()
|
||||
|
||||
@@ -26,3 +26,9 @@ 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,3 +47,25 @@ 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,3 +85,10 @@ def _():
|
||||
|
||||
if isinstance(foo, type(None)):
|
||||
...
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19047
|
||||
if isinstance(foo, ()):
|
||||
pass
|
||||
|
||||
if isinstance(foo, Union[()]):
|
||||
pass
|
||||
|
||||
@@ -7,6 +7,7 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::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,
|
||||
@@ -90,7 +91,13 @@ 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) {
|
||||
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()
|
||||
)
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::RedundantLiteralUnion) {
|
||||
@@ -1430,6 +1437,11 @@ 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);
|
||||
}
|
||||
|
||||
@@ -2765,9 +2765,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_annotation()
|
||||
&& self.semantic.in_typing_only_annotation()
|
||||
{
|
||||
if self.semantic.in_typing_only_annotation() {
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, annotation, range);
|
||||
}
|
||||
|
||||
@@ -35,39 +35,34 @@ 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.noqa_code() else {
|
||||
let Some(code) = diagnostic.secondary_code() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if code == Rule::BlanketNOQA.noqa_code() {
|
||||
if *code == Rule::BlanketNOQA.noqa_code() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if exemption.contains_secondary_code(code) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
let noqa_offsets = diagnostic
|
||||
@@ -82,13 +77,21 @@ pub(crate) fn check_noqa(
|
||||
{
|
||||
let suppressed = match &directive_line.directive {
|
||||
Directive::All(_) => {
|
||||
directive_line.matches.push(code);
|
||||
let Ok(rule) = Rule::from_code(code) else {
|
||||
debug_assert!(false, "Invalid secondary code `{code}`");
|
||||
continue;
|
||||
};
|
||||
directive_line.matches.push(rule);
|
||||
ignored_diagnostics.push(index);
|
||||
true
|
||||
}
|
||||
Directive::Codes(directive) => {
|
||||
if directive.includes(code) {
|
||||
directive_line.matches.push(code);
|
||||
let Ok(rule) = Rule::from_code(code) else {
|
||||
debug_assert!(false, "Invalid secondary code `{code}`");
|
||||
continue;
|
||||
};
|
||||
directive_line.matches.push(rule);
|
||||
ignored_diagnostics.push(index);
|
||||
true
|
||||
} else {
|
||||
@@ -147,11 +150,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.noqa_code().is_some_and(|noqa| noqa == code))
|
||||
context.iter().any(|diag| {
|
||||
diag.secondary_code().is_some_and(|noqa| *noqa == code)
|
||||
})
|
||||
} else {
|
||||
matches.iter().any(|match_| *match_ == code)
|
||||
matches.iter().any(|match_| match_.noqa_code() == code)
|
||||
} || settings
|
||||
.external
|
||||
.iter()
|
||||
|
||||
@@ -46,6 +46,12 @@ impl PartialEq<&str> for NoqaCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NoqaCode> for &str {
|
||||
fn eq(&self, other: &NoqaCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for NoqaCode {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -63,7 +63,7 @@ fn apply_fixes<'a>(
|
||||
let mut source_map = SourceMap::default();
|
||||
|
||||
for (code, name, fix) in diagnostics
|
||||
.filter_map(|msg| msg.noqa_code().map(|code| (code, msg.name(), msg)))
|
||||
.filter_map(|msg| msg.secondary_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))
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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::FxHashMap;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_python_ast::{ModModule, PySourceType, PythonVersion};
|
||||
@@ -23,10 +22,10 @@ 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::SecondaryCode;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::is_py314_support_enabled;
|
||||
@@ -95,25 +94,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(FxHashMap<NoqaCode, FixCount>);
|
||||
pub struct FixTable(hashbrown::HashMap<SecondaryCode, FixCount, rustc_hash::FxBuildHasher>);
|
||||
|
||||
impl FixTable {
|
||||
pub fn counts(&self) -> impl Iterator<Item = usize> {
|
||||
self.0.values().map(|fc| fc.count)
|
||||
}
|
||||
|
||||
pub fn entry(&mut self, code: NoqaCode) -> FixTableEntry {
|
||||
FixTableEntry(self.0.entry(code))
|
||||
pub fn entry<'a>(&'a mut self, code: &'a SecondaryCode) -> FixTableEntry<'a> {
|
||||
FixTableEntry(self.0.entry_ref(code))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (NoqaCode, &'static str, usize)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&SecondaryCode, &'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 = NoqaCode> {
|
||||
self.0.keys().copied()
|
||||
pub fn keys(&self) -> impl Iterator<Item = &SecondaryCode> {
|
||||
self.0.keys()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@@ -121,7 +120,9 @@ impl FixTable {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixTableEntry<'a>(Entry<'a, NoqaCode, FixCount>);
|
||||
pub struct FixTableEntry<'a>(
|
||||
hashbrown::hash_map::EntryRef<'a, 'a, SecondaryCode, SecondaryCode, FixCount, FxBuildHasher>,
|
||||
);
|
||||
|
||||
impl<'a> FixTableEntry<'a> {
|
||||
pub fn or_default(self, rule_name: &'static str) -> &'a mut usize {
|
||||
@@ -678,18 +679,16 @@ pub fn lint_fix<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_rule_codes(rules: impl IntoIterator<Item = NoqaCode>) -> String {
|
||||
rules
|
||||
.into_iter()
|
||||
.map(|rule| rule.to_string())
|
||||
.sorted_unstable()
|
||||
.dedup()
|
||||
.join(", ")
|
||||
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(", ")
|
||||
}
|
||||
|
||||
#[expect(clippy::print_stderr)]
|
||||
fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[OldDiagnostic]) {
|
||||
let codes = collect_rule_codes(diagnostics.iter().filter_map(OldDiagnostic::noqa_code));
|
||||
let codes = collect_rule_codes(diagnostics.iter().filter_map(OldDiagnostic::secondary_code));
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
@@ -721,11 +720,11 @@ This indicates a bug in Ruff. If you could open an issue at:
|
||||
}
|
||||
|
||||
#[expect(clippy::print_stderr)]
|
||||
fn report_fix_syntax_error(
|
||||
fn report_fix_syntax_error<'a>(
|
||||
path: &Path,
|
||||
transformed: &str,
|
||||
error: &ParseError,
|
||||
rules: impl IntoIterator<Item = NoqaCode>,
|
||||
rules: impl IntoIterator<Item = &'a SecondaryCode>,
|
||||
) {
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Emitter for AzureEmitter {
|
||||
line = location.line,
|
||||
col = location.column,
|
||||
code = diagnostic
|
||||
.noqa_code()
|
||||
.secondary_code()
|
||||
.map_or_else(String::new, |code| format!("code={code};")),
|
||||
body = diagnostic.body(),
|
||||
)?;
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Emitter for GithubEmitter {
|
||||
writer,
|
||||
"::error title=Ruff{code},file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = diagnostic
|
||||
.noqa_code()
|
||||
.secondary_code()
|
||||
.map_or_else(String::new, |code| format!(" ({code})")),
|
||||
file = diagnostic.filename(),
|
||||
row = source_location.line,
|
||||
@@ -50,7 +50,7 @@ impl Emitter for GithubEmitter {
|
||||
column = location.column,
|
||||
)?;
|
||||
|
||||
if let Some(code) = diagnostic.noqa_code() {
|
||||
if let Some(code) = diagnostic.secondary_code() {
|
||||
write!(writer, " {code}")?;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,18 +90,15 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let (description, check_name) = if let Some(code) = diagnostic.noqa_code() {
|
||||
(diagnostic.body().to_string(), code.to_string())
|
||||
let (description, check_name) = if let Some(code) = diagnostic.secondary_code() {
|
||||
(diagnostic.body().to_string(), code.as_str())
|
||||
} else {
|
||||
let description = diagnostic.body();
|
||||
let description_without_prefix = description
|
||||
.strip_prefix("SyntaxError: ")
|
||||
.unwrap_or(description);
|
||||
|
||||
(
|
||||
description_without_prefix.to_string(),
|
||||
"syntax-error".to_string(),
|
||||
)
|
||||
(description_without_prefix.to_string(), "syntax-error")
|
||||
};
|
||||
|
||||
let value = json!({
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterCo
|
||||
}
|
||||
|
||||
json!({
|
||||
"code": message.noqa_code().map(|code| code.to_string()),
|
||||
"code": message.secondary_code(),
|
||||
"url": message.to_url(),
|
||||
"message": message.body(),
|
||||
"fix": fix,
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Emitter for JunitEmitter {
|
||||
body = message.body()
|
||||
));
|
||||
let mut case = TestCase::new(
|
||||
if let Some(code) = message.noqa_code() {
|
||||
if let Some(code) = message.secondary_code() {
|
||||
format!("org.ruff.{code}")
|
||||
} else {
|
||||
"org.ruff".to_string()
|
||||
|
||||
@@ -62,7 +62,7 @@ pub struct OldDiagnostic {
|
||||
pub fix: Option<Fix>,
|
||||
pub parent: Option<TextSize>,
|
||||
pub(crate) noqa_offset: Option<TextSize>,
|
||||
pub(crate) noqa_code: Option<NoqaCode>,
|
||||
pub(crate) secondary_code: Option<SecondaryCode>,
|
||||
}
|
||||
|
||||
impl OldDiagnostic {
|
||||
@@ -79,7 +79,7 @@ impl OldDiagnostic {
|
||||
fix: None,
|
||||
parent: None,
|
||||
noqa_offset: None,
|
||||
noqa_code: None,
|
||||
secondary_code: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ impl OldDiagnostic {
|
||||
fix,
|
||||
parent,
|
||||
noqa_offset,
|
||||
noqa_code: Some(rule.noqa_code()),
|
||||
secondary_code: Some(SecondaryCode(rule.noqa_code().to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,9 +247,9 @@ impl OldDiagnostic {
|
||||
self.fix().is_some()
|
||||
}
|
||||
|
||||
/// Returns the [`NoqaCode`] corresponding to the diagnostic message.
|
||||
pub fn noqa_code(&self) -> Option<NoqaCode> {
|
||||
self.noqa_code
|
||||
/// Returns the noqa code for the diagnostic message as a string.
|
||||
pub fn secondary_code(&self) -> Option<&SecondaryCode> {
|
||||
self.secondary_code.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
@@ -384,6 +384,68 @@ impl<'a> EmitterContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A secondary identifier for a lint diagnostic.
|
||||
///
|
||||
/// For Ruff rules this means the noqa code.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, 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
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SecondaryCode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// for `hashbrown::EntryRef`
|
||||
impl From<&SecondaryCode> for SecondaryCode {
|
||||
fn from(value: &SecondaryCode) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -26,7 +26,7 @@ impl Emitter for PylintEmitter {
|
||||
diagnostic.compute_start_location().line
|
||||
};
|
||||
|
||||
let body = if let Some(code) = diagnostic.noqa_code() {
|
||||
let body = if let Some(code) = diagnostic.secondary_code() {
|
||||
format!("[{code}] {body}", body = diagnostic.body())
|
||||
} else {
|
||||
diagnostic.body().to_string()
|
||||
|
||||
@@ -71,7 +71,7 @@ fn message_to_rdjson_value(message: &OldDiagnostic) -> Value {
|
||||
"range": rdjson_range(start_location, end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.noqa_code().map(|code| code.to_string()),
|
||||
"value": message.secondary_code(),
|
||||
"url": message.to_url(),
|
||||
},
|
||||
"suggestions": rdjson_suggestions(fix.edits(), &source_code),
|
||||
@@ -84,7 +84,7 @@ fn message_to_rdjson_value(message: &OldDiagnostic) -> Value {
|
||||
"range": rdjson_range(start_location, end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.noqa_code().map(|code| code.to_string()),
|
||||
"value": message.secondary_code(),
|
||||
"url": message.to_url(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -8,9 +8,8 @@ use serde_json::json;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::VERSION;
|
||||
use crate::codes::NoqaCode;
|
||||
use crate::fs::normalize_path;
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic, SecondaryCode};
|
||||
use crate::registry::{Linter, RuleNamespace};
|
||||
|
||||
pub struct SarifEmitter;
|
||||
@@ -29,7 +28,7 @@ impl Emitter for SarifEmitter {
|
||||
|
||||
let unique_rules: HashSet<_> = results.iter().filter_map(|result| result.code).collect();
|
||||
let mut rules: Vec<SarifRule> = unique_rules.into_iter().map(SarifRule::from).collect();
|
||||
rules.sort_by(|a, b| a.code.cmp(&b.code));
|
||||
rules.sort_by(|a, b| a.code.cmp(b.code));
|
||||
|
||||
let output = json!({
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
@@ -54,26 +53,25 @@ impl Emitter for SarifEmitter {
|
||||
#[derive(Debug, Clone)]
|
||||
struct SarifRule<'a> {
|
||||
name: &'a str,
|
||||
code: String,
|
||||
code: &'a SecondaryCode,
|
||||
linter: &'a str,
|
||||
summary: &'a str,
|
||||
explanation: Option<&'a str>,
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
impl From<NoqaCode> for SarifRule<'_> {
|
||||
fn from(code: NoqaCode) -> Self {
|
||||
let code_str = code.to_string();
|
||||
impl<'a> From<&'a SecondaryCode> for SarifRule<'a> {
|
||||
fn from(code: &'a SecondaryCode) -> Self {
|
||||
// This is a manual re-implementation of Rule::from_code, but we also want the Linter. This
|
||||
// avoids calling Linter::parse_code twice.
|
||||
let (linter, suffix) = Linter::parse_code(&code_str).unwrap();
|
||||
let (linter, suffix) = Linter::parse_code(code).unwrap();
|
||||
let rule = linter
|
||||
.all_rules()
|
||||
.find(|rule| rule.noqa_code().suffix() == suffix)
|
||||
.expect("Expected a valid noqa code corresponding to a rule");
|
||||
Self {
|
||||
name: rule.into(),
|
||||
code: code_str,
|
||||
code,
|
||||
linter: linter.name(),
|
||||
summary: rule.message_formats()[0],
|
||||
explanation: rule.explanation(),
|
||||
@@ -111,8 +109,8 @@ impl Serialize for SarifRule<'_> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SarifResult {
|
||||
code: Option<NoqaCode>,
|
||||
struct SarifResult<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
level: String,
|
||||
message: String,
|
||||
uri: String,
|
||||
@@ -122,14 +120,14 @@ struct SarifResult {
|
||||
end_column: OneIndexed,
|
||||
}
|
||||
|
||||
impl SarifResult {
|
||||
impl<'a> SarifResult<'a> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn from_message(message: &OldDiagnostic) -> Result<Self> {
|
||||
fn from_message(message: &'a OldDiagnostic) -> Result<Self> {
|
||||
let start_location = message.compute_start_location();
|
||||
let end_location = message.compute_end_location();
|
||||
let path = normalize_path(&*message.filename());
|
||||
Ok(Self {
|
||||
code: message.noqa_code(),
|
||||
code: message.secondary_code(),
|
||||
level: "error".to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: url::Url::from_file_path(&path)
|
||||
@@ -144,12 +142,12 @@ impl SarifResult {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[expect(clippy::unnecessary_wraps)]
|
||||
fn from_message(message: &OldDiagnostic) -> Result<Self> {
|
||||
fn from_message(message: &'a OldDiagnostic) -> Result<Self> {
|
||||
let start_location = message.compute_start_location();
|
||||
let end_location = message.compute_end_location();
|
||||
let path = normalize_path(&*message.filename());
|
||||
Ok(Self {
|
||||
code: message.noqa_code(),
|
||||
code: message.secondary_code(),
|
||||
level: "error".to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: path.display().to_string(),
|
||||
@@ -161,7 +159,7 @@ impl SarifResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SarifResult {
|
||||
impl Serialize for SarifResult<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@@ -184,7 +182,7 @@ impl Serialize for SarifResult {
|
||||
}
|
||||
}
|
||||
}],
|
||||
"ruleId": self.code.map(|code| code.to_string()),
|
||||
"ruleId": self.code,
|
||||
})
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::Locator;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic};
|
||||
use crate::message::{Emitter, EmitterContext, OldDiagnostic, SecondaryCode};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
bitflags! {
|
||||
@@ -151,8 +151,8 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
if let Some(fix) = self.message.fix() {
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if fix.applies(self.unsafe_fixes.required_applicability()) {
|
||||
if let Some(code) = self.message.noqa_code() {
|
||||
write!(f, "{} ", code.to_string().red().bold())?;
|
||||
if let Some(code) = self.message.secondary_code() {
|
||||
write!(f, "{} ", code.red().bold())?;
|
||||
}
|
||||
return write!(
|
||||
f,
|
||||
@@ -164,11 +164,11 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(code) = self.message.noqa_code() {
|
||||
if let Some(code) = self.message.secondary_code() {
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = code.to_string().red().bold(),
|
||||
code = code.red().bold(),
|
||||
body = self.message.body(),
|
||||
)
|
||||
} else {
|
||||
@@ -254,8 +254,9 @@ impl Display for MessageCodeFrame<'_> {
|
||||
|
||||
let label = self
|
||||
.message
|
||||
.noqa_code()
|
||||
.map_or_else(String::new, |code| code.to_string());
|
||||
.secondary_code()
|
||||
.map(SecondaryCode::as_str)
|
||||
.unwrap_or_default();
|
||||
|
||||
let line_start = self.notebook_index.map_or_else(
|
||||
|| start_index.get(),
|
||||
@@ -269,7 +270,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
|
||||
let span = usize::from(source.annotation_range.start())
|
||||
..usize::from(source.annotation_range.end());
|
||||
let annotation = Level::Error.span(span).label(&label);
|
||||
let annotation = Level::Error.span(span).label(label);
|
||||
let snippet = Snippet::source(&source.text)
|
||||
.line_start(line_start)
|
||||
.annotation(annotation)
|
||||
|
||||
@@ -16,9 +16,8 @@ use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::Edit;
|
||||
use crate::Locator;
|
||||
use crate::codes::NoqaCode;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::OldDiagnostic;
|
||||
use crate::message::{OldDiagnostic, SecondaryCode};
|
||||
use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
|
||||
@@ -106,9 +105,9 @@ impl Codes<'_> {
|
||||
|
||||
/// Returns `true` if the string list of `codes` includes `code` (or an alias
|
||||
/// thereof).
|
||||
pub(crate) fn includes(&self, needle: NoqaCode) -> bool {
|
||||
pub(crate) fn includes<T: for<'a> PartialEq<&'a str>>(&self, needle: &T) -> bool {
|
||||
self.iter()
|
||||
.any(|code| needle == get_redirect_target(code.as_str()).unwrap_or(code.as_str()))
|
||||
.any(|code| *needle == get_redirect_target(code.as_str()).unwrap_or(code.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,48 +139,55 @@ pub(crate) fn rule_is_ignored(
|
||||
Ok(Some(NoqaLexerOutput {
|
||||
directive: Directive::Codes(codes),
|
||||
..
|
||||
})) => codes.includes(code.noqa_code()),
|
||||
})) => codes.includes(&code.noqa_code()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of the file-level exemption as extracted from [`FileNoqaDirectives`].
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum FileExemption<'a> {
|
||||
pub(crate) enum FileExemption {
|
||||
/// The file is exempt from all rules.
|
||||
All(Vec<&'a NoqaCode>),
|
||||
All(Vec<Rule>),
|
||||
/// The file is exempt from the given rules.
|
||||
Codes(Vec<&'a NoqaCode>),
|
||||
Codes(Vec<Rule>),
|
||||
}
|
||||
|
||||
impl FileExemption<'_> {
|
||||
/// Returns `true` if the file is exempt from the given rule.
|
||||
pub(crate) fn includes(&self, needle: Rule) -> bool {
|
||||
let needle = needle.noqa_code();
|
||||
impl FileExemption {
|
||||
/// Returns `true` if the file is exempt from the given rule, as identified by its noqa code.
|
||||
pub(crate) fn contains_secondary_code(&self, needle: &SecondaryCode) -> bool {
|
||||
match self {
|
||||
FileExemption::All(_) => true,
|
||||
FileExemption::Codes(codes) => codes.iter().any(|code| needle == **code),
|
||||
FileExemption::Codes(codes) => codes.iter().any(|code| *needle == code.noqa_code()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the file is exempt from the given rule.
|
||||
pub(crate) fn includes(&self, needle: Rule) -> bool {
|
||||
match self {
|
||||
FileExemption::All(_) => true,
|
||||
FileExemption::Codes(codes) => codes.contains(&needle),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the file exemption lists the rule directly, rather than via a blanket
|
||||
/// exemption.
|
||||
pub(crate) fn enumerates(&self, needle: Rule) -> bool {
|
||||
let needle = needle.noqa_code();
|
||||
let codes = match self {
|
||||
FileExemption::All(codes) => codes,
|
||||
FileExemption::Codes(codes) => codes,
|
||||
};
|
||||
codes.iter().any(|code| needle == **code)
|
||||
codes.contains(&needle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> {
|
||||
impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption {
|
||||
fn from(directives: &'a FileNoqaDirectives) -> Self {
|
||||
let codes = directives
|
||||
.lines()
|
||||
.iter()
|
||||
.flat_map(|line| &line.matches)
|
||||
.copied()
|
||||
.collect();
|
||||
if directives
|
||||
.lines()
|
||||
@@ -203,7 +209,7 @@ pub(crate) struct FileNoqaDirectiveLine<'a> {
|
||||
/// The blanket noqa directive.
|
||||
pub(crate) parsed_file_exemption: Directive<'a>,
|
||||
/// The codes that are ignored by the parsed exemptions.
|
||||
pub(crate) matches: Vec<NoqaCode>,
|
||||
pub(crate) matches: Vec<Rule>,
|
||||
}
|
||||
|
||||
impl Ranged for FileNoqaDirectiveLine<'_> {
|
||||
@@ -270,7 +276,7 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code))
|
||||
{
|
||||
Some(rule.noqa_code())
|
||||
Some(rule)
|
||||
} else {
|
||||
#[expect(deprecated)]
|
||||
let line = locator.compute_line_index(range.start());
|
||||
@@ -303,6 +309,10 @@ impl<'a> FileNoqaDirectives<'a> {
|
||||
pub(crate) fn lines(&self) -> &[FileNoqaDirectiveLine] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Output of lexing a `noqa` directive.
|
||||
@@ -830,7 +840,7 @@ fn build_noqa_edits_by_line<'a>(
|
||||
|
||||
struct NoqaComment<'a> {
|
||||
line: TextSize,
|
||||
code: NoqaCode,
|
||||
code: &'a SecondaryCode,
|
||||
directive: Option<&'a Directive<'a>>,
|
||||
}
|
||||
|
||||
@@ -846,24 +856,14 @@ fn find_noqa_comments<'a>(
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for message in diagnostics {
|
||||
let Some(code) = message.noqa_code() else {
|
||||
let Some(code) = message.secondary_code() else {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
};
|
||||
|
||||
match &exemption {
|
||||
FileExemption::All(_) => {
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
FileExemption::Codes(codes) => {
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if codes.contains(&&code) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if exemption.contains_secondary_code(code) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
@@ -921,7 +921,7 @@ fn find_noqa_comments<'a>(
|
||||
|
||||
struct NoqaEdit<'a> {
|
||||
edit_range: TextRange,
|
||||
noqa_codes: FxHashSet<NoqaCode>,
|
||||
noqa_codes: FxHashSet<&'a SecondaryCode>,
|
||||
codes: Option<&'a Codes<'a>>,
|
||||
line_ending: LineEnding,
|
||||
}
|
||||
@@ -942,13 +942,13 @@ impl NoqaEdit<'_> {
|
||||
writer,
|
||||
self.noqa_codes
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.chain(codes.iter().map(ToString::to_string))
|
||||
.map(|code| code.as_str())
|
||||
.chain(codes.iter().map(Code::as_str))
|
||||
.sorted_unstable(),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
push_codes(writer, self.noqa_codes.iter().map(ToString::to_string));
|
||||
push_codes(writer, self.noqa_codes.iter().sorted_unstable());
|
||||
}
|
||||
}
|
||||
write!(writer, "{}", self.line_ending.as_str()).unwrap();
|
||||
@@ -964,7 +964,7 @@ impl Ranged for NoqaEdit<'_> {
|
||||
fn generate_noqa_edit<'a>(
|
||||
directive: Option<&'a Directive>,
|
||||
offset: TextSize,
|
||||
noqa_codes: FxHashSet<NoqaCode>,
|
||||
noqa_codes: FxHashSet<&'a SecondaryCode>,
|
||||
locator: &Locator,
|
||||
line_ending: LineEnding,
|
||||
) -> Option<NoqaEdit<'a>> {
|
||||
@@ -1017,7 +1017,7 @@ pub(crate) struct NoqaDirectiveLine<'a> {
|
||||
/// The noqa directive.
|
||||
pub(crate) directive: Directive<'a>,
|
||||
/// The codes that are ignored by the directive.
|
||||
pub(crate) matches: Vec<NoqaCode>,
|
||||
pub(crate) matches: Vec<Rule>,
|
||||
/// Whether the directive applies to `range.end`.
|
||||
pub(crate) includes_end: bool,
|
||||
}
|
||||
@@ -1142,6 +1142,10 @@ impl<'a> NoqaDirectives<'a> {
|
||||
pub(crate) fn lines(&self) -> &[NoqaDirectiveLine] {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Remaps offsets falling into one of the ranges to instead check for a noqa comment on the
|
||||
|
||||
@@ -90,6 +90,11 @@ pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18572
|
||||
pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18547
|
||||
pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
@@ -99,3 +104,10 @@ pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &Linter
|
||||
pub(crate) const fn is_raise_exception_byte_string_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18683
|
||||
pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -23,12 +23,16 @@ use crate::{FixAvailability, Violation};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from airflow.auth.managers.fab.fab_auth_manage import FabAuthManager
|
||||
/// from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager
|
||||
///
|
||||
/// fab_auth_manager_app = FabAuthManager().get_fastapi_app()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from airflow.providers.fab.auth_manager.fab_auth_manage import FabAuthManager
|
||||
/// from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
||||
///
|
||||
/// fab_auth_manager_app = FabAuthManager().get_fastapi_app()
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct Airflow3MovedToProvider<'a> {
|
||||
|
||||
@@ -24,11 +24,31 @@ use ruff_text_size::TextRange;
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from airflow.operators.python import PythonOperator
|
||||
///
|
||||
///
|
||||
/// def print_context(ds=None, **kwargs):
|
||||
/// print(kwargs)
|
||||
/// print(ds)
|
||||
///
|
||||
///
|
||||
/// print_the_context = PythonOperator(
|
||||
/// task_id="print_the_context", python_callable=print_context
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from airflow.providers.standard.operators.python import PythonOperator
|
||||
///
|
||||
///
|
||||
/// def print_context(ds=None, **kwargs):
|
||||
/// print(kwargs)
|
||||
/// print(ds)
|
||||
///
|
||||
///
|
||||
/// print_the_context = PythonOperator(
|
||||
/// task_id="print_the_context", python_callable=print_context
|
||||
/// )
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct Airflow3SuggestedToMoveToProvider<'a> {
|
||||
|
||||
@@ -476,12 +476,18 @@ impl Violation for MissingReturnTypeClassMethod {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def foo(x: Any): ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def foo(x: int): ...
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -16,6 +16,8 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
/// DONE = False
|
||||
///
|
||||
///
|
||||
@@ -26,6 +28,8 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
/// DONE = asyncio.Event()
|
||||
///
|
||||
///
|
||||
|
||||
@@ -20,12 +20,18 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import urllib
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// urllib.request.urlopen("https://example.com/foo/bar").read()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import aiohttp
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// async with aiohttp.ClientSession() as session:
|
||||
/// async with session.get("https://example.com/foo/bar") as resp:
|
||||
|
||||
@@ -21,12 +21,18 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// os.popen(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
@@ -54,12 +60,18 @@ impl Violation for CreateSubprocessInAsyncFunction {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// subprocess.run(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// asyncio.create_subprocess_shell(cmd)
|
||||
/// ```
|
||||
@@ -87,12 +99,19 @@ impl Violation for RunProcessInAsyncFunction {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
///
|
||||
/// async def foo():
|
||||
/// os.waitpid(0)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
/// import os
|
||||
///
|
||||
///
|
||||
/// def wait_for_process():
|
||||
/// os.waitpid(0)
|
||||
///
|
||||
|
||||
@@ -19,12 +19,18 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import time
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// time.sleep(1)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// await asyncio.sleep(1)
|
||||
/// ```
|
||||
|
||||
@@ -21,6 +21,9 @@ use crate::rules::flake8_async::helpers::MethodName;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// async with asyncio.timeout(2):
|
||||
/// do_something()
|
||||
@@ -28,6 +31,9 @@ use crate::rules::flake8_async::helpers::MethodName;
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import asyncio
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// async with asyncio.timeout(2):
|
||||
/// do_something()
|
||||
|
||||
@@ -18,12 +18,18 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import trio
|
||||
///
|
||||
///
|
||||
/// async def double_sleep(x):
|
||||
/// trio.sleep(2 * x)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import trio
|
||||
///
|
||||
///
|
||||
/// async def double_sleep(x):
|
||||
/// await trio.sleep(2 * x)
|
||||
/// ```
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import flask
|
||||
/// from flask import Flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
@@ -27,7 +27,9 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import flask
|
||||
/// import os
|
||||
///
|
||||
/// from flask import Flask
|
||||
///
|
||||
/// app = Flask()
|
||||
///
|
||||
|
||||
@@ -108,10 +108,10 @@ impl Violation for SubprocessWithoutShellEqualsTrue {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
/// import my_custom_subprocess
|
||||
///
|
||||
/// user_input = input("Enter a command: ")
|
||||
/// subprocess.run(user_input, shell=True)
|
||||
/// my_custom_subprocess.run(user_input, shell=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
@@ -265,14 +265,14 @@ impl Violation for StartProcessWithPartialPath {
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["chmod", "777", "*.py"])
|
||||
/// subprocess.Popen(["chmod", "777", "*.py"], shell=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["chmod", "777", "main.py"])
|
||||
/// subprocess.Popen(["chmod", "777", "main.py"], shell=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -20,16 +20,22 @@ use crate::{FixAvailability, Violation};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import itertools
|
||||
///
|
||||
/// itertools.batched(iterable, n)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead if the batches must be of uniform length:
|
||||
/// ```python
|
||||
/// import itertools
|
||||
///
|
||||
/// itertools.batched(iterable, n, strict=True)
|
||||
/// ```
|
||||
///
|
||||
/// Or if the batches can be of non-uniform length:
|
||||
/// ```python
|
||||
/// import itertools
|
||||
///
|
||||
/// itertools.batched(iterable, n, strict=False)
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -20,11 +20,15 @@ use crate::{checkers::ast::Checker, fix::edits::add_argument};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import warnings
|
||||
///
|
||||
/// warnings.warn("This is a warning")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import warnings
|
||||
///
|
||||
/// warnings.warn("This is a warning", stacklevel=2)
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -24,6 +24,7 @@ mod tests {
|
||||
#[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419_2.py"))]
|
||||
#[test_case(Rule::UnnecessaryDictComprehensionForIterable, Path::new("C420.py"))]
|
||||
#[test_case(Rule::UnnecessaryDictComprehensionForIterable, Path::new("C420_1.py"))]
|
||||
#[test_case(Rule::UnnecessaryDictComprehensionForIterable, Path::new("C420_2.py"))]
|
||||
#[test_case(Rule::UnnecessaryDoubleCastOrProcess, Path::new("C414.py"))]
|
||||
#[test_case(Rule::UnnecessaryGeneratorDict, Path::new("C402.py"))]
|
||||
#[test_case(Rule::UnnecessaryGeneratorList, Path::new("C400.py"))]
|
||||
|
||||
@@ -7,6 +7,7 @@ use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprCall, Exp
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::pad_start;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
@@ -136,12 +137,16 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable(
|
||||
|
||||
if checker.semantic().has_builtin_binding("dict") {
|
||||
let edit = Edit::range_replacement(
|
||||
checker
|
||||
.generator()
|
||||
.expr(&fix_unnecessary_dict_comprehension(
|
||||
dict_comp.value.as_ref(),
|
||||
generator,
|
||||
)),
|
||||
pad_start(
|
||||
checker
|
||||
.generator()
|
||||
.expr(&fix_unnecessary_dict_comprehension(
|
||||
dict_comp.value.as_ref(),
|
||||
generator,
|
||||
)),
|
||||
dict_comp.start(),
|
||||
checker.locator(),
|
||||
),
|
||||
dict_comp.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
---
|
||||
C420_2.py:1:7: C420 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead
|
||||
|
|
||||
1 | foo or{x: None for x in bar}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ C420
|
||||
|
|
||||
= help: Replace with `dict.fromkeys(iterable, value)`)
|
||||
|
||||
ℹ Safe fix
|
||||
1 |-foo or{x: None for x in bar}
|
||||
1 |+foo or dict.fromkeys(bar)
|
||||
2 2 |
|
||||
3 3 |
|
||||
4 4 | # C420 fix must make sure to insert a leading space if needed,
|
||||
@@ -24,7 +24,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.today()
|
||||
/// datetime.date.today()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -18,19 +18,25 @@ use crate::checkers::ast::Checker;
|
||||
/// unexpectedly, as in:
|
||||
///
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// # Timezone: UTC-14
|
||||
/// datetime.min.timestamp() # ValueError: year 0 is out of range
|
||||
/// datetime.max.timestamp() # ValueError: year 10000 is out of range
|
||||
/// datetime.datetime.min.timestamp() # ValueError: year 0 is out of range
|
||||
/// datetime.datetime.max.timestamp() # ValueError: year 10000 is out of range
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// datetime.max
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.max
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// datetime.max.replace(tzinfo=datetime.UTC)
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.max.replace(tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DatetimeMinMax {
|
||||
|
||||
@@ -22,6 +22,8 @@ mod tests {
|
||||
#[test_case(Path::new("EXE002_3.py"))]
|
||||
#[test_case(Path::new("EXE003.py"))]
|
||||
#[test_case(Path::new("EXE003_uv.py"))]
|
||||
#[test_case(Path::new("EXE003_uv_tool.py"))]
|
||||
#[test_case(Path::new("EXE003_uvx.py"))]
|
||||
#[test_case(Path::new("EXE004_1.py"))]
|
||||
#[test_case(Path::new("EXE004_2.py"))]
|
||||
#[test_case(Path::new("EXE004_3.py"))]
|
||||
|
||||
@@ -47,7 +47,12 @@ pub(crate) fn shebang_missing_python(
|
||||
shebang: &ShebangDirective,
|
||||
context: &LintContext,
|
||||
) {
|
||||
if shebang.contains("python") || shebang.contains("pytest") || shebang.contains("uv run") {
|
||||
if shebang.contains("python")
|
||||
|| shebang.contains("pytest")
|
||||
|| shebang.contains("uv run")
|
||||
|| shebang.contains("uvx")
|
||||
|| shebang.contains("uv tool run")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_executable/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_executable/mod.rs
|
||||
---
|
||||
|
||||
@@ -11,6 +11,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pep8_naming;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -172,4 +173,23 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_pyi").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
/// ## Example
|
||||
///
|
||||
/// ```pyi
|
||||
/// from typing import Any
|
||||
///
|
||||
/// class Foo:
|
||||
/// def __eq__(self, obj: typing.Any) -> bool: ...
|
||||
/// def __eq__(self, obj: Any) -> bool: ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -19,11 +19,15 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// foo: Literal["a", "b", "a"]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Literal
|
||||
///
|
||||
/// foo: Literal["a", "b"]
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::{AtomicNodeIndex, Expr, ExprBinOp, ExprNoneLiteral, Operator, PythonVersion};
|
||||
use ruff_python_semantic::analyze::typing::{traverse_union, traverse_union_and_optional};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use super::generate_union_fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_optional_as_none_in_union_enabled;
|
||||
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate union members.
|
||||
@@ -71,21 +70,35 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||
union_type = UnionKind::PEP604;
|
||||
}
|
||||
|
||||
let virtual_expr = if is_optional_as_none_in_union_enabled(checker.settings())
|
||||
&& is_optional_type(checker, expr)
|
||||
{
|
||||
// If the union member is an `Optional`, add a virtual `None` literal.
|
||||
&VIRTUAL_NONE_LITERAL
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
// If we've already seen this union member, raise a violation.
|
||||
if seen_nodes.insert(expr.into()) {
|
||||
unique_nodes.push(expr);
|
||||
if seen_nodes.insert(virtual_expr.into()) {
|
||||
unique_nodes.push(virtual_expr);
|
||||
} else {
|
||||
diagnostics.push(checker.report_diagnostic(
|
||||
DuplicateUnionMember {
|
||||
duplicate_name: checker.generator().expr(expr),
|
||||
duplicate_name: checker.generator().expr(virtual_expr),
|
||||
},
|
||||
// Use the real expression's range for diagnostics,
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Traverse the union, collect all diagnostic members
|
||||
traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
if is_optional_as_none_in_union_enabled(checker.settings()) {
|
||||
traverse_union_and_optional(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
} else {
|
||||
traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
}
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
return;
|
||||
@@ -178,3 +191,12 @@ fn generate_pep604_fix(
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
|
||||
static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::new(TextSize::new(0), TextSize::new(0)),
|
||||
});
|
||||
|
||||
fn is_optional_type(checker: &Checker, expr: &Expr) -> bool {
|
||||
checker.semantic().match_typing_expr(expr, "Optional")
|
||||
}
|
||||
|
||||
@@ -21,27 +21,47 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// from collections.abc import Container, Iterable, Sized
|
||||
/// from typing import Generic, TypeVar
|
||||
///
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
/// K = TypeVar("K")
|
||||
/// V = TypeVar("V")
|
||||
///
|
||||
///
|
||||
/// class LinkedList(Generic[T], Sized):
|
||||
/// def push(self, item: T) -> None:
|
||||
/// self._items.append(item)
|
||||
///
|
||||
///
|
||||
/// class MyMapping(
|
||||
/// Generic[K, V],
|
||||
/// Iterable[Tuple[K, V]],
|
||||
/// Container[Tuple[K, V]],
|
||||
/// Iterable[tuple[K, V]],
|
||||
/// Container[tuple[K, V]],
|
||||
/// ):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from collections.abc import Container, Iterable, Sized
|
||||
/// from typing import Generic, TypeVar
|
||||
///
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
/// K = TypeVar("K")
|
||||
/// V = TypeVar("V")
|
||||
///
|
||||
///
|
||||
/// class LinkedList(Sized, Generic[T]):
|
||||
/// def push(self, item: T) -> None:
|
||||
/// self._items.append(item)
|
||||
///
|
||||
///
|
||||
/// class MyMapping(
|
||||
/// Iterable[Tuple[K, V]],
|
||||
/// Container[Tuple[K, V]],
|
||||
/// Iterable[tuple[K, V]],
|
||||
/// Container[tuple[K, V]],
|
||||
/// Generic[K, V],
|
||||
/// ):
|
||||
/// ...
|
||||
|
||||
@@ -75,7 +75,7 @@ impl AlwaysFixableViolation for TypedArgumentDefaultInStub {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```pyi
|
||||
/// def foo(arg=[]) -> None: ...
|
||||
/// def foo(arg=bar()) -> None: ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
@@ -120,7 +120,7 @@ impl AlwaysFixableViolation for ArgumentDefaultInStub {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```pyi
|
||||
/// foo: str = "..."
|
||||
/// foo: str = bar()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -14,11 +14,15 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```pyi
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// type_alias_name: TypeAlias = int
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```pyi
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// TypeAliasName: TypeAlias = int
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -914,4 +914,79 @@ PYI016.py:115:23: PYI016 [*] Duplicate union member `int`
|
||||
115 |+field35: "int | str" # Error
|
||||
116 116 |
|
||||
117 117 |
|
||||
118 118 |
|
||||
118 118 |
|
||||
|
||||
PYI016.py:134:45: PYI016 [*] Duplicate union member `typing.Optional[int]`
|
||||
|
|
||||
132 | field40: typing.Union[typing.Optional[int], None]
|
||||
133 | field41: typing.Optional[typing.Union[int, None]]
|
||||
134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PYI016
|
||||
135 | field43: typing.Optional[int] | None
|
||||
136 | field44: typing.Optional[int | None]
|
||||
|
|
||||
= help: Remove duplicate union member `typing.Optional[int]`
|
||||
|
||||
ℹ Safe fix
|
||||
131 131 | # equivalent to int | None
|
||||
132 132 | field40: typing.Union[typing.Optional[int], None]
|
||||
133 133 | field41: typing.Optional[typing.Union[int, None]]
|
||||
134 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
134 |+field42: typing.Optional[int]
|
||||
135 135 | field43: typing.Optional[int] | None
|
||||
136 136 | field44: typing.Optional[int | None]
|
||||
137 137 | field45: typing.Optional[int] | typing.Optional[int]
|
||||
|
||||
PYI016.py:137:33: PYI016 [*] Duplicate union member `typing.Optional[int]`
|
||||
|
|
||||
135 | field43: typing.Optional[int] | None
|
||||
136 | field44: typing.Optional[int | None]
|
||||
137 | field45: typing.Optional[int] | typing.Optional[int]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PYI016
|
||||
138 | # equivalent to int | dict | None
|
||||
139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
|
|
||||
= help: Remove duplicate union member `typing.Optional[int]`
|
||||
|
||||
ℹ Safe fix
|
||||
134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
135 135 | field43: typing.Optional[int] | None
|
||||
136 136 | field44: typing.Optional[int | None]
|
||||
137 |-field45: typing.Optional[int] | typing.Optional[int]
|
||||
137 |+field45: typing.Optional[int]
|
||||
138 138 | # equivalent to int | dict | None
|
||||
139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
140 140 | field47: typing.Optional[int] | typing.Optional[dict]
|
||||
|
||||
PYI016.py:143:61: PYI016 [*] Duplicate union member `complex`
|
||||
|
|
||||
142 | # avoid reporting twice
|
||||
143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
| ^^^^^^^ PYI016
|
||||
144 | field49: typing.Optional[complex | complex] | complex
|
||||
|
|
||||
= help: Remove duplicate union member `complex`
|
||||
|
||||
ℹ Safe fix
|
||||
140 140 | field47: typing.Optional[int] | typing.Optional[dict]
|
||||
141 141 |
|
||||
142 142 | # avoid reporting twice
|
||||
143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
143 |+field48: typing.Union[typing.Optional[complex], complex]
|
||||
144 144 | field49: typing.Optional[complex | complex] | complex
|
||||
|
||||
PYI016.py:144:36: PYI016 [*] Duplicate union member `complex`
|
||||
|
|
||||
142 | # avoid reporting twice
|
||||
143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
144 | field49: typing.Optional[complex | complex] | complex
|
||||
| ^^^^^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `complex`
|
||||
|
||||
ℹ Safe fix
|
||||
141 141 |
|
||||
142 142 | # avoid reporting twice
|
||||
143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
144 |-field49: typing.Optional[complex | complex] | complex
|
||||
144 |+field49: typing.Optional[complex] | complex
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PYI016.pyi:7:15: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
@@ -883,6 +882,8 @@ PYI016.pyi:113:61: PYI016 [*] Duplicate union member `list[int]`
|
||||
112 | # Test case for mixed union type
|
||||
113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error
|
||||
| ^^^^^^^^^ PYI016
|
||||
114 |
|
||||
115 | # https://github.com/astral-sh/ruff/issues/18546
|
||||
|
|
||||
= help: Remove duplicate union member `list[int]`
|
||||
|
||||
@@ -892,3 +893,81 @@ PYI016.pyi:113:61: PYI016 [*] Duplicate union member `list[int]`
|
||||
112 112 | # Test case for mixed union type
|
||||
113 |-field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error
|
||||
113 |+field34: typing.Union[list[int], str, bytes] # Error
|
||||
114 114 |
|
||||
115 115 | # https://github.com/astral-sh/ruff/issues/18546
|
||||
116 116 | # Expand Optional[T] to Union[T, None]
|
||||
|
||||
PYI016.pyi:125:45: PYI016 [*] Duplicate union member `typing.Optional[int]`
|
||||
|
|
||||
123 | field40: typing.Union[typing.Optional[int], None]
|
||||
124 | field41: typing.Optional[typing.Union[int, None]]
|
||||
125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PYI016
|
||||
126 | field43: typing.Optional[int] | None
|
||||
127 | field44: typing.Optional[int | None]
|
||||
|
|
||||
= help: Remove duplicate union member `typing.Optional[int]`
|
||||
|
||||
ℹ Safe fix
|
||||
122 122 | # equivalent to int | None
|
||||
123 123 | field40: typing.Union[typing.Optional[int], None]
|
||||
124 124 | field41: typing.Optional[typing.Union[int, None]]
|
||||
125 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
125 |+field42: typing.Optional[int]
|
||||
126 126 | field43: typing.Optional[int] | None
|
||||
127 127 | field44: typing.Optional[int | None]
|
||||
128 128 | field45: typing.Optional[int] | typing.Optional[int]
|
||||
|
||||
PYI016.pyi:128:33: PYI016 [*] Duplicate union member `typing.Optional[int]`
|
||||
|
|
||||
126 | field43: typing.Optional[int] | None
|
||||
127 | field44: typing.Optional[int | None]
|
||||
128 | field45: typing.Optional[int] | typing.Optional[int]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PYI016
|
||||
129 | # equivalent to int | dict | None
|
||||
130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
|
|
||||
= help: Remove duplicate union member `typing.Optional[int]`
|
||||
|
||||
ℹ Safe fix
|
||||
125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]]
|
||||
126 126 | field43: typing.Optional[int] | None
|
||||
127 127 | field44: typing.Optional[int | None]
|
||||
128 |-field45: typing.Optional[int] | typing.Optional[int]
|
||||
128 |+field45: typing.Optional[int]
|
||||
129 129 | # equivalent to int | dict | None
|
||||
130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
|
||||
131 131 | field47: typing.Optional[int] | typing.Optional[dict]
|
||||
|
||||
PYI016.pyi:134:61: PYI016 [*] Duplicate union member `complex`
|
||||
|
|
||||
133 | # avoid reporting twice
|
||||
134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
| ^^^^^^^ PYI016
|
||||
135 | field49: typing.Optional[complex | complex] | complex
|
||||
|
|
||||
= help: Remove duplicate union member `complex`
|
||||
|
||||
ℹ Safe fix
|
||||
131 131 | field47: typing.Optional[int] | typing.Optional[dict]
|
||||
132 132 |
|
||||
133 133 | # avoid reporting twice
|
||||
134 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
134 |+field48: typing.Union[typing.Optional[complex], complex]
|
||||
135 135 | field49: typing.Optional[complex | complex] | complex
|
||||
|
||||
PYI016.pyi:135:36: PYI016 [*] Duplicate union member `complex`
|
||||
|
|
||||
133 | # avoid reporting twice
|
||||
134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
135 | field49: typing.Optional[complex | complex] | complex
|
||||
| ^^^^^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `complex`
|
||||
|
||||
ℹ Safe fix
|
||||
132 132 |
|
||||
133 133 | # avoid reporting twice
|
||||
134 134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
135 |-field49: typing.Optional[complex | complex] | complex
|
||||
135 |+field49: typing.Optional[complex] | complex
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user