Compare commits
100 Commits
0.13.0
...
amy/missin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4447bcf468 | ||
|
|
c790c1d957 | ||
|
|
bfb0902446 | ||
|
|
05622ae757 | ||
|
|
3fcbe8bde6 | ||
|
|
64a4e2889e | ||
|
|
5816985ecd | ||
|
|
b6a29592e7 | ||
|
|
bcc8d6910b | ||
|
|
02ee22db78 | ||
|
|
0a2325c5fe | ||
|
|
6c3c963f8a | ||
|
|
6ec52991cb | ||
|
|
cf16fc4aa4 | ||
|
|
61a49c89eb | ||
|
|
da5eb85087 | ||
|
|
a47a50e6e2 | ||
|
|
880a867696 | ||
|
|
ec2720c814 | ||
|
|
cb3c3ba94d | ||
|
|
c585c9f6d4 | ||
|
|
ac5488086f | ||
|
|
7e464b8150 | ||
|
|
ffd650e5fd | ||
|
|
99ec4d2c69 | ||
|
|
9f0b942b9e | ||
|
|
c2fa449954 | ||
|
|
681ad2fd92 | ||
|
|
98071b49c2 | ||
|
|
d121a76aef | ||
|
|
c3f2187fda | ||
|
|
8a027b0d74 | ||
|
|
aa63c24b8f | ||
|
|
1f46c18921 | ||
|
|
0d424d8e78 | ||
|
|
e6b321eccc | ||
|
|
2a6dde4acb | ||
|
|
25cbf38a47 | ||
|
|
9a9ebc316c | ||
|
|
e8b4450125 | ||
|
|
eb71536dce | ||
|
|
8341da7f63 | ||
|
|
1f1365a0fa | ||
|
|
25c13ea91c | ||
|
|
6581f9bf2a | ||
|
|
c4b0d10438 | ||
|
|
3adb478c6b | ||
|
|
ac8ac2c677 | ||
|
|
2c8aa6e9e3 | ||
|
|
093fa72656 | ||
|
|
02c58f1beb | ||
|
|
276ee1bb1e | ||
|
|
9e4acd8bdd | ||
|
|
e061c39119 | ||
|
|
9299bb42ca | ||
|
|
876d800205 | ||
|
|
89d83282b9 | ||
|
|
97ebb1492f | ||
|
|
afc207ec0f | ||
|
|
326c878adb | ||
|
|
b9a96535bc | ||
|
|
59330be95d | ||
|
|
963974146d | ||
|
|
4dc28a483e | ||
|
|
850eefc0c4 | ||
|
|
fc3e571804 | ||
|
|
0a36b1e4d7 | ||
|
|
72e6698550 | ||
|
|
9edbeb44dd | ||
|
|
1fa64a24b8 | ||
|
|
1745554809 | ||
|
|
98708976e4 | ||
|
|
c7f6b85fb3 | ||
|
|
151ba49b36 | ||
|
|
82796df9b5 | ||
|
|
dfec94608c | ||
|
|
ff677a96e4 | ||
|
|
b6bd32d9dc | ||
|
|
ec863bcde7 | ||
|
|
7be11b496d | ||
|
|
33b3d44ebd | ||
|
|
bb9be263c7 | ||
|
|
1cd8ab3f26 | ||
|
|
36888198a6 | ||
|
|
c4cd5c00fd | ||
|
|
5bf6977ded | ||
|
|
abb705aa4e | ||
|
|
0e3697a643 | ||
|
|
ffd4340dce | ||
|
|
89f17467ef | ||
|
|
59c8fda3f8 | ||
|
|
c6b92b918e | ||
|
|
a3ec8ca9df | ||
|
|
12c337c948 | ||
|
|
4c64ba4ee1 | ||
|
|
cde5e4e343 | ||
|
|
8a0edf0da8 | ||
|
|
d23cae870e | ||
|
|
2ac4147435 | ||
|
|
ffead90410 |
32
.github/workflows/build-binaries.yml
vendored
32
.github/workflows/build-binaries.yml
vendored
@@ -39,11 +39,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -68,11 +68,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -110,11 +110,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: arm64
|
||||
@@ -166,11 +166,11 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.platform.arch }}
|
||||
@@ -219,11 +219,11 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -296,11 +296,11 @@ jobs:
|
||||
arch: riscv64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -361,11 +361,11 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -427,11 +427,11 @@ jobs:
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
|
||||
6
.github/workflows/build-docker.yml
vendored
6
.github/workflows/build-docker.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
|
||||
117
.github/workflows/ci.yaml
vendored
117
.github/workflows/ci.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
# Flag that is set to "true" when code related to the playground changes.
|
||||
playground: ${{ steps.check_playground.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -252,15 +252,15 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -305,7 +305,7 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -314,15 +314,15 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -338,18 +338,18 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -369,15 +369,15 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
|
||||
@@ -398,7 +398,7 @@ jobs:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -416,7 +416,7 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: SebRollen/toml-action@b1b3628f55fc3a28208d4203ada8b737e9687876 # v1.2.0
|
||||
@@ -444,7 +444,7 @@ jobs:
|
||||
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' || needs.determine_changes.outputs.code == 'true' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -453,7 +453,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
|
||||
uses: cargo-bins/cargo-binstall@20aa316bab4942180bbbabe93237858e8d77f1ed # v1.15.5
|
||||
- name: "Install cargo-fuzz"
|
||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||
@@ -470,11 +470,11 @@ jobs:
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
with:
|
||||
@@ -504,7 +504,7 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -534,14 +534,14 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -658,10 +658,10 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
name: Download new ty binary
|
||||
id: ty-new
|
||||
with:
|
||||
@@ -674,7 +674,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -701,10 +701,10 @@ jobs:
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
|
||||
- uses: cargo-bins/cargo-binstall@20aa316bab4942180bbbabe93237858e8d77f1ed # v1.15.5
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -714,10 +714,10 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -741,12 +741,12 @@ jobs:
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
- name: "Cache pre-commit"
|
||||
@@ -772,10 +772,10 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -787,7 +787,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -814,7 +814,7 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
@@ -840,18 +840,18 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: "Download ruff-lsp source"
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: "astral-sh/ruff-lsp"
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
# installation fails on 3.13 and newer
|
||||
python-version: "3.12"
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
name: Download development ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -882,13 +882,13 @@ jobs:
|
||||
- determine_changes
|
||||
if: ${{ (needs.determine_changes.outputs.playground == 'true') }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
@@ -914,18 +914,18 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -933,8 +933,9 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c # v3.8.1
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
@@ -947,18 +948,18 @@ jobs:
|
||||
TY_LOG: ruff_benchmark=debug
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -966,7 +967,13 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c # v3.8.1
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
env:
|
||||
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
||||
# appear to provide much useful insight for our walltime benchmarks right now
|
||||
# (see https://github.com/astral-sh/ruff/pull/20419)
|
||||
CODSPEED_PERF_ENABLED: false
|
||||
with:
|
||||
mode: walltime
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
6
.github/workflows/daily_fuzz.yaml
vendored
6
.github/workflows/daily_fuzz.yaml
vendored
@@ -31,10 +31,10 @@ jobs:
|
||||
# Don't run the cron job on forks:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
8
.github/workflows/mypy_primer.yaml
vendored
8
.github/workflows/mypy_primer.yaml
vendored
@@ -32,14 +32,14 @@ jobs:
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -75,14 +75,14 @@ jobs:
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/notify-dependents.yml
vendored
2
.github/workflows/notify-dependents.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
|
||||
script: |
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -23,12 +23,12 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: true
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
|
||||
4
.github/workflows/publish-playground.yml
vendored
4
.github/workflows/publish-playground.yml
vendored
@@ -24,12 +24,12 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
|
||||
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
|
||||
4
.github/workflows/publish-ty-playground.yml
vendored
4
.github/workflows/publish-ty-playground.yml
vendored
@@ -30,12 +30,12 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
|
||||
6
.github/workflows/publish-wasm.yml
vendored
6
.github/workflows/publish-wasm.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
target: [web, bundler, nodejs]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -45,9 +45,9 @@ jobs:
|
||||
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
||||
mv /tmp/package.json crates/ruff_wasm/pkg
|
||||
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: "Publish (dry-run)"
|
||||
if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
|
||||
16
.github/workflows/sync_typeshed.yaml
vendored
16
.github/workflows/sync_typeshed.yaml
vendored
@@ -50,12 +50,12 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
path: ruff
|
||||
persist-credentials: true
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout typeshed
|
||||
with:
|
||||
repository: python/typeshed
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -112,12 +112,12 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -150,12 +150,12 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -192,7 +192,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
6
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
6
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -26,14 +26,14 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
if: contains(github.event.label.name, 'ecosystem-analyzer')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@1f560d07d672effae250e3d271da53d96c5260ff"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@fc0f612798710b0dd69bb7528bc9b361dc60bd43"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
|
||||
4
.github/workflows/ty-ecosystem-report.yaml
vendored
4
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -22,14 +22,14 @@ jobs:
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
4
.github/workflows/typing_conformance.yaml
vendored
4
.github/workflows/typing_conformance.yaml
vendored
@@ -32,13 +32,13 @@ jobs:
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
repository: python/typing
|
||||
ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
|
||||
|
||||
194
Cargo.lock
generated
194
Cargo.lock
generated
@@ -322,11 +322,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.12"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5"
|
||||
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -376,7 +376,7 @@ dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -603,7 +603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -612,7 +612,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -826,12 +826,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.7"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
||||
checksum = "881c5d0a13b2f1498e2306e82cbada78390e152d4b1378fb28a84f4dcd0dc4f3"
|
||||
dependencies = [
|
||||
"dispatch",
|
||||
"nix 0.30.1",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -958,6 +959,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -1035,7 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1486,9 +1493,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
@@ -1617,7 +1624,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1681,7 +1688,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2126,9 +2133,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd6fedcd996c8c97932075cc3811d83f53280f48d5620e4e3cab7f6a12678c4"
|
||||
checksum = "0dcd63f1ae4b091e314a26627c467dd8810d674ba798abc0e566679955776c63"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2475,16 +2482,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.13.5"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0f6160dc48298b9260d9b958ad1d7f96f6cd0b9df200b22329204e09334663"
|
||||
checksum = "ec768e063102b426e8962989758115e8659485124de9207bc365fab524125d65"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.8.23",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2635,9 +2642,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -2645,9 +2652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
@@ -2773,7 +2780,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.16",
|
||||
"tikv-jemallocator",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
"wild",
|
||||
@@ -2789,7 +2796,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"serde",
|
||||
"snapbox",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -2908,7 +2915,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -3009,6 +3016,7 @@ dependencies = [
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_importer",
|
||||
"ruff_python_index",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
@@ -3028,7 +3036,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -3159,6 +3167,21 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_importer"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
"ruff_diagnostics",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.0"
|
||||
@@ -3286,7 +3309,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
@@ -3376,7 +3399,7 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
@@ -3412,7 +3435,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3430,7 +3453,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3454,12 +3477,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3713cd7eb30821c0c086591832dd6f59f2af7fe7#3713cd7eb30821c0c086591832dd6f59f2af7fe7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3514,10 +3537,11 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
@@ -3533,10 +3557,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
name = "serde_core"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.223"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3556,14 +3589,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3577,15 +3611,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.0"
|
||||
@@ -3797,15 +3822,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
version = "3.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3997,18 +4022,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.5"
|
||||
@@ -4017,7 +4030,7 @@ checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned 1.0.0",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
@@ -4029,9 +4042,6 @@ name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
@@ -4049,8 +4059,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow",
|
||||
]
|
||||
@@ -4192,7 +4200,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
@@ -4226,9 +4234,12 @@ dependencies = [
|
||||
"rayon",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_index",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_codegen",
|
||||
"ruff_python_importer",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
@@ -4271,7 +4282,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
@@ -4399,7 +4410,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -4508,9 +4519,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -4611,9 +4622,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.17.0"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
@@ -4624,9 +4635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.17.0"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7"
|
||||
checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4878,7 +4889,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4889,7 +4900,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -4922,13 +4933,19 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4937,7 +4954,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4967,6 +4984,15 @@ dependencies = [
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
dependencies = [
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -4989,7 +5015,7 @@ version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
|
||||
@@ -29,6 +29,7 @@ ruff_options_metadata = { path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
||||
ruff_python_importer = { path = "crates/ruff_python_importer" }
|
||||
ruff_python_index = { path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { path = "crates/ruff_python_parser" }
|
||||
@@ -143,7 +144,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 = "a3ffa22cb26756473d56f867aedec3fd907c4dd9", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "3713cd7eb30821c0c086591832dd6f59f2af7fe7", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -421,7 +421,7 @@ Ruff is released under the MIT license.
|
||||
|
||||
Ruff is used by a number of major open-source projects and companies, including:
|
||||
|
||||
- [Albumentations](https://github.com/albumentations-team/albumentations)
|
||||
- [Albumentations](https://github.com/albumentations-team/AlbumentationsX)
|
||||
- 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))
|
||||
|
||||
@@ -6,12 +6,14 @@ use std::time::Instant;
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use ignore::Error;
|
||||
use log::{debug, error, warn};
|
||||
use log::{debug, warn};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, Span, SubDiagnostic, SubDiagnosticSeverity,
|
||||
};
|
||||
use ruff_db::panic::catch_unwind;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::registry::Rule;
|
||||
@@ -193,21 +195,24 @@ fn lint_path(
|
||||
match result {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
let message = r"This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
|
||||
";
|
||||
|
||||
error!(
|
||||
"{}{}{} {message}\n{error}",
|
||||
"Panicked while linting ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
let message = match error.payload.as_str() {
|
||||
Some(summary) => format!("Fatal error while linting: {summary}"),
|
||||
_ => "Fatal error while linting".to_owned(),
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DiagnosticId::Panic,
|
||||
ruff_db::diagnostic::Severity::Fatal,
|
||||
message,
|
||||
);
|
||||
|
||||
Ok(Diagnostics::default())
|
||||
let span = Span::from(SourceFileBuilder::new(path.to_string_lossy(), "").finish());
|
||||
let mut annotation = Annotation::primary(span);
|
||||
annotation.set_file_level(true);
|
||||
diagnostic.annotate(annotation);
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
format!("{error}"),
|
||||
));
|
||||
Ok(Diagnostics::new(vec![diagnostic], FxHashMap::default()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use log::{error, warn};
|
||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_linter::logging::{LogLevel, set_up_logging};
|
||||
use ruff_linter::settings::flags::FixMode;
|
||||
use ruff_linter::settings::types::OutputFormat;
|
||||
@@ -444,6 +445,27 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
}
|
||||
|
||||
if !cli.exit_zero {
|
||||
let max_severity = diagnostics
|
||||
.inner
|
||||
.iter()
|
||||
.map(Diagnostic::severity)
|
||||
.max()
|
||||
.unwrap_or(Severity::Info);
|
||||
if max_severity.is_fatal() {
|
||||
// When a panic/fatal error is reported, prompt the user to open an issue on github.
|
||||
// Diagnostics with severity `fatal` will be sorted to the bottom, and printing the
|
||||
// message here instead of attaching it to the diagnostic ensures that we only print
|
||||
// it once instead of repeating it for each diagnostic. Prints to stderr to prevent
|
||||
// the message from being captured by tools parsing the normal output.
|
||||
let message = "Panic during linting indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative!
|
||||
";
|
||||
error!("{message}");
|
||||
return Ok(ExitStatus::Error);
|
||||
}
|
||||
if cli.diff {
|
||||
// If we're printing a diff, we always want to exit non-zero if there are
|
||||
// any fixable violations (since we've printed the diff, but not applied the
|
||||
|
||||
@@ -10,13 +10,12 @@ use ruff_linter::linter::FixTable;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
|
||||
DisplayGithubDiagnostics, GithubRenderer, SecondaryCode,
|
||||
};
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
Emitter, EmitterContext, GithubEmitter, GroupedEmitter, SarifEmitter, TextEmitter,
|
||||
};
|
||||
use ruff_linter::message::{Emitter, EmitterContext, GroupedEmitter, SarifEmitter, TextEmitter};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
|
||||
@@ -226,32 +225,26 @@ impl Printer {
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
let config = DisplayDiagnosticConfig::default().preview(preview);
|
||||
|
||||
match self.format {
|
||||
OutputFormat::Json => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Json)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Json);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Rdjson => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Rdjson)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Rdjson);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::JsonLines => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::JsonLines)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::JsonLines);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Junit => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Junit)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Junit);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
@@ -290,26 +283,22 @@ impl Printer {
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
OutputFormat::Github => {
|
||||
GithubEmitter.emit(writer, &diagnostics.inner, &context)?;
|
||||
let renderer = GithubRenderer::new(&context, "Ruff");
|
||||
let value = DisplayGithubDiagnostics::new(&renderer, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Gitlab => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Gitlab)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Gitlab);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Pylint => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Pylint)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Pylint);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Azure => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Azure)
|
||||
.preview(preview);
|
||||
let config = config.format(DiagnosticFormat::Azure);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,59 @@ fn string_detection() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_detection_from_config() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
let root = ChildPath::new(tempdir.path());
|
||||
|
||||
// Configure string import detection with a lower min-dots via ruff.toml
|
||||
root.child("ruff.toml").write_str(indoc::indoc! {r#"
|
||||
[analyze]
|
||||
detect-string-imports = true
|
||||
string-imports-min-dots = 1
|
||||
"#})?;
|
||||
|
||||
root.child("ruff").child("__init__.py").write_str("")?;
|
||||
root.child("ruff")
|
||||
.child("a.py")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
import ruff.b
|
||||
"#})?;
|
||||
root.child("ruff")
|
||||
.child("b.py")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
import importlib
|
||||
|
||||
importlib.import_module("ruff.c")
|
||||
"#})?;
|
||||
root.child("ruff").child("c.py").write_str("")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn globs() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -5059,6 +5059,59 @@ fn flake8_import_convention_unused_aliased_import_no_conflict() {
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/issues/19842
|
||||
#[test]
|
||||
fn pyupgrade_up026_respects_isort_required_import_fix() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("--isolated")
|
||||
.arg("check")
|
||||
.arg("-")
|
||||
.args(["--select", "I002,UP026"])
|
||||
.arg("--config")
|
||||
.arg(r#"lint.isort.required-imports=["import mock"]"#)
|
||||
.arg("--fix")
|
||||
.arg("--no-cache")
|
||||
.pass_stdin("1\n"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
import mock
|
||||
1
|
||||
|
||||
----- stderr -----
|
||||
Found 1 error (1 fixed, 0 remaining).
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/issues/19842
|
||||
#[test]
|
||||
fn pyupgrade_up026_respects_isort_required_import_from_fix() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("--isolated")
|
||||
.arg("check")
|
||||
.arg("-")
|
||||
.args(["--select", "I002,UP026"])
|
||||
.arg("--config")
|
||||
.arg(r#"lint.isort.required-imports = ["from mock import mock"]"#)
|
||||
.arg("--fix")
|
||||
.arg("--no-cache")
|
||||
.pass_stdin("from mock import mock\n"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
from mock import mock
|
||||
|
||||
----- stderr -----
|
||||
All checks passed!
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
// See: https://github.com/astral-sh/ruff/issues/16177
|
||||
#[test]
|
||||
fn flake8_pyi_redundant_none_literal() {
|
||||
@@ -5838,3 +5891,113 @@ fn show_fixes_in_full_output_with_preview_enabled() {
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_panic_mixed_results_concise() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
// Create python files
|
||||
let file_a_path = tempdir.path().join("normal.py");
|
||||
let file_b_path = tempdir.path().join("panic.py");
|
||||
fs::write(&file_a_path, b"import os")?;
|
||||
fs::write(&file_b_path, b"print('hello, world!')")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![
|
||||
(tempdir_filter(&tempdir).as_str(), "[TMP]/"),
|
||||
(r"\\", r"/"),
|
||||
]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "RUF9", "--preview", "--output-format=concise", "--no-cache"])
|
||||
.args([file_a_path, file_b_path]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
[TMP]/normal.py:1:1: RUF900 Hey this is a stable test rule.
|
||||
[TMP]/normal.py:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
[TMP]/normal.py:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
[TMP]/normal.py:1:1: RUF903 Hey this is a stable test rule with a display only fix.
|
||||
[TMP]/normal.py:1:1: RUF911 Hey this is a preview test rule.
|
||||
[TMP]/normal.py:1:1: RUF950 Hey this is a test rule that was redirected from another.
|
||||
[TMP]/panic.py: panic: Fatal error while linting: This is a fake panic for testing.
|
||||
Found 7 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
error: Panic during linting indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative!
|
||||
");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_panic_mixed_results_full() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
// Create python files
|
||||
let file_a_path = tempdir.path().join("normal.py");
|
||||
let file_b_path = tempdir.path().join("panic.py");
|
||||
fs::write(&file_a_path, b"import os")?;
|
||||
fs::write(&file_b_path, b"print('hello, world!')")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![
|
||||
(tempdir_filter(&tempdir).as_str(), "[TMP]/"),
|
||||
(r"\\", r"/"),
|
||||
]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "RUF9", "--preview", "--output-format=full", "--no-cache"])
|
||||
.args([file_a_path, file_b_path]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
RUF900 Hey this is a stable test rule.
|
||||
--> [TMP]/normal.py:1:1
|
||||
|
||||
RUF901 [*] Hey this is a stable test rule with a safe fix.
|
||||
--> [TMP]/normal.py:1:1
|
||||
1 + # fix from stable-test-rule-safe-fix
|
||||
2 | import os
|
||||
|
||||
RUF902 Hey this is a stable test rule with an unsafe fix.
|
||||
--> [TMP]/normal.py:1:1
|
||||
|
||||
RUF903 Hey this is a stable test rule with a display only fix.
|
||||
--> [TMP]/normal.py:1:1
|
||||
|
||||
RUF911 Hey this is a preview test rule.
|
||||
--> [TMP]/normal.py:1:1
|
||||
|
||||
RUF950 Hey this is a test rule that was redirected from another.
|
||||
--> [TMP]/normal.py:1:1
|
||||
|
||||
panic: Fatal error while linting: This is a fake panic for testing.
|
||||
--> [TMP]/panic.py:1:1
|
||||
info: panicked at crates/ruff_linter/src/rules/ruff/rules/test_rules.rs:511:9:
|
||||
This is a fake panic for testing.
|
||||
run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
Found 7 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
error: Panic during linting indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the stack trace above, we'd be very appreciative!
|
||||
");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::{Db, files::File};
|
||||
|
||||
@@ -494,22 +495,17 @@ impl Diagnostic {
|
||||
self.primary_span()?.range()
|
||||
}
|
||||
|
||||
/// Returns the [`TextRange`] for the diagnostic.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span or if the span has no range.
|
||||
pub fn expect_range(&self) -> TextRange {
|
||||
self.range().expect("Expected a range for the primary span")
|
||||
}
|
||||
|
||||
/// Returns the ordering of diagnostics based on the start of their ranges, if they have any.
|
||||
///
|
||||
/// Panics if either diagnostic has no primary span, or if its file is not a `SourceFile`.
|
||||
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let a = (
|
||||
self.severity().is_fatal(),
|
||||
self.expect_ruff_source_file(),
|
||||
self.range().map(|r| r.start()),
|
||||
);
|
||||
let b = (
|
||||
other.severity().is_fatal(),
|
||||
other.expect_ruff_source_file(),
|
||||
other.range().map(|r| r.start()),
|
||||
);
|
||||
@@ -1454,6 +1450,11 @@ pub enum DiagnosticFormat {
|
||||
/// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
|
||||
#[cfg(feature = "serde")]
|
||||
Gitlab,
|
||||
|
||||
/// Print diagnostics in the format used by [GitHub Actions] workflow error annotations.
|
||||
///
|
||||
/// [GitHub Actions]: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message
|
||||
Github,
|
||||
}
|
||||
|
||||
/// A representation of the kinds of messages inside a diagnostic.
|
||||
|
||||
@@ -25,11 +25,13 @@ use super::{
|
||||
|
||||
use azure::AzureRenderer;
|
||||
use concise::ConciseRenderer;
|
||||
use github::GithubRenderer;
|
||||
use pylint::PylintRenderer;
|
||||
|
||||
mod azure;
|
||||
mod concise;
|
||||
mod full;
|
||||
pub mod github;
|
||||
#[cfg(feature = "serde")]
|
||||
mod gitlab;
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -142,6 +144,9 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
|
||||
DiagnosticFormat::Gitlab => {
|
||||
gitlab::GitlabRenderer::new(self.resolver).render(f, self.diagnostics)?;
|
||||
}
|
||||
DiagnosticFormat::Github => {
|
||||
GithubRenderer::new(self.resolver, "ty").render(f, self.diagnostics)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
136
crates/ruff_db/src/diagnostic/render/github.rs
Normal file
136
crates/ruff_db/src/diagnostic/render/github.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use crate::diagnostic::{Diagnostic, FileResolver, Severity};
|
||||
|
||||
pub struct GithubRenderer<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
program: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> GithubRenderer<'a> {
|
||||
pub fn new(resolver: &'a dyn FileResolver, program: &'a str) -> Self {
|
||||
Self { resolver, program }
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::fmt::Result {
|
||||
for diagnostic in diagnostics {
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => "notice",
|
||||
Severity::Warning => "warning",
|
||||
Severity::Error | Severity::Fatal => "error",
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"::{severity} title={program} ({code})",
|
||||
program = self.program,
|
||||
code = diagnostic.secondary_code_or_id()
|
||||
)?;
|
||||
|
||||
if let Some(span) = diagnostic.primary_span() {
|
||||
let file = span.file();
|
||||
write!(f, ",file={file}", file = file.path(self.resolver))?;
|
||||
|
||||
let (start_location, end_location) = if self.resolver.is_notebook(file) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
None
|
||||
} else {
|
||||
let diagnostic_source = file.diagnostic_source(self.resolver);
|
||||
let source_code = diagnostic_source.as_source_code();
|
||||
|
||||
span.range().map(|range| {
|
||||
(
|
||||
source_code.line_column(range.start()),
|
||||
source_code.line_column(range.end()),
|
||||
)
|
||||
})
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{path}:{row}:{column}: ",
|
||||
path = file.relative_path(self.resolver).display(),
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
)?;
|
||||
} else {
|
||||
write!(f, "::")?;
|
||||
}
|
||||
|
||||
if let Some(code) = diagnostic.secondary_code() {
|
||||
write!(f, "{code}")?;
|
||||
} else {
|
||||
write!(f, "{id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(f, " {}", diagnostic.body())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayGithubDiagnostics<'a> {
|
||||
renderer: &'a GithubRenderer<'a>,
|
||||
diagnostics: &'a [Diagnostic],
|
||||
}
|
||||
|
||||
impl<'a> DisplayGithubDiagnostics<'a> {
|
||||
pub fn new(renderer: &'a GithubRenderer<'a>, diagnostics: &'a [Diagnostic]) -> Self {
|
||||
Self {
|
||||
renderer,
|
||||
diagnostics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DisplayGithubDiagnostics<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.renderer.render(f, self.diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Github);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Github);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_file() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.format(DiagnosticFormat::Github);
|
||||
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@"::error title=ty (test-diagnostic)::test-diagnostic: main diagnostic message",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (F401),file=fib.py,line=1,col=8,endLine=1,endColumn=10::fib.py:1:8: F401 `os` imported but unused
|
||||
::error title=ty (F841),file=fib.py,line=6,col=5,endLine=6,endColumn=6::fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
||||
::error title=ty (F821),file=undef.py,line=1,col=4,endLine=1,endColumn=5::undef.py:1:4: F821 Undefined name `a`
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (invalid-syntax),file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
@@ -20,6 +20,7 @@ ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["serde", "cache"] }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_python_importer = { workspace = true }
|
||||
ruff_python_index = { workspace = true }
|
||||
ruff_python_literal = { workspace = true }
|
||||
ruff_python_semantic = { workspace = true }
|
||||
|
||||
@@ -18,3 +18,18 @@ var_string = "true"
|
||||
Popen(var_string, shell=True)
|
||||
Popen([var_string], shell=True)
|
||||
Popen([var_string, ""], shell=True)
|
||||
|
||||
# Check dict display with only double-starred expressions can be falsey.
|
||||
Popen("true", shell={**{}})
|
||||
Popen("true", shell={**{**{}}})
|
||||
|
||||
# Check pattern with merged defaults/configs
|
||||
class ShellConfig:
|
||||
def __init__(self):
|
||||
self.shell_defaults = {}
|
||||
|
||||
def fetch_shell_config(self, username):
|
||||
return {}
|
||||
|
||||
def run(self, username):
|
||||
Popen("true", shell={**self.shell_defaults, **self.fetch_shell_config(username)})
|
||||
|
||||
@@ -3,3 +3,6 @@ def foo(shell):
|
||||
|
||||
|
||||
foo(shell=True)
|
||||
|
||||
foo(shell={**{}})
|
||||
foo(shell={**{**{}}})
|
||||
|
||||
@@ -6,3 +6,6 @@ subprocess.Popen("/bin/chown root: *", shell=True)
|
||||
subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
|
||||
subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
|
||||
os.system("tar cf foo.tar bar/*")
|
||||
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{}})
|
||||
subprocess.Popen(["chmod", "+w", "*.py"], shell={**{**{}}})
|
||||
|
||||
@@ -39,3 +39,16 @@ hasattr(
|
||||
"__call__", # comment 4
|
||||
# comment 5
|
||||
)
|
||||
|
||||
import operator
|
||||
|
||||
assert hasattr(operator, "__call__")
|
||||
assert callable(operator) is False
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self): self.__call__ = None
|
||||
|
||||
|
||||
assert hasattr(A(), "__call__")
|
||||
assert callable(A()) is False
|
||||
|
||||
@@ -663,4 +663,15 @@ class C[
|
||||
|
||||
type X[T,] = T
|
||||
def f[T,](): pass
|
||||
class C[T,]: pass
|
||||
class C[T,]: pass
|
||||
|
||||
# t-string examples
|
||||
kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
kwargs.pop("remove", t"this {f"{trailing_comma}"}",)
|
||||
|
||||
t"""This is a test. {
|
||||
"Another sentence."
|
||||
if True else
|
||||
"Don't add a trailing comma here ->"
|
||||
}"""
|
||||
|
||||
|
||||
@@ -187,3 +187,24 @@ _ = (
|
||||
# leading comment
|
||||
"end"
|
||||
)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20310
|
||||
# ISC001
|
||||
t"The quick " t"brown fox."
|
||||
|
||||
# ISC002
|
||||
t"The quick brown fox jumps over the lazy "\
|
||||
t"dog."
|
||||
|
||||
# ISC003
|
||||
(
|
||||
t"The quick brown fox jumps over the lazy "
|
||||
+ t"dog"
|
||||
)
|
||||
|
||||
# nested examples with both t and f-strings
|
||||
_ = "a" f"b {t"c" t"d"} e" "f"
|
||||
_ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
_ = f"b {t"abc" \
|
||||
t"def"} g"
|
||||
|
||||
|
||||
@@ -55,3 +55,30 @@ def foo(some_other):
|
||||
def foo():
|
||||
dict = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
age = dict.get("Cat", None)
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20341
|
||||
# Method call as key
|
||||
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
key_source = {"Thomas": "Tom"}
|
||||
age = ages.get(key_source.get("Thomas", "Tom"), None)
|
||||
|
||||
# Property access as key
|
||||
class Data:
|
||||
def __init__(self):
|
||||
self.name = "Tom"
|
||||
|
||||
data = Data()
|
||||
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
age = ages.get(data.name, None)
|
||||
|
||||
# Complex expression as key
|
||||
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
age = ages.get("Tom" if True else "Maria", None)
|
||||
|
||||
# Function call as key
|
||||
def get_key():
|
||||
return "Tom"
|
||||
|
||||
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
age = ages.get(get_key(), None)
|
||||
|
||||
24
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH123.py
vendored
Normal file
24
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH123.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import builtins
|
||||
from pathlib import Path
|
||||
|
||||
_file = "file.txt"
|
||||
_x = ("r+", -1)
|
||||
r_plus = "r+"
|
||||
|
||||
builtins.open(file=_file)
|
||||
|
||||
open(_file, "r+ ", - 1)
|
||||
open(mode="wb", file=_file)
|
||||
open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
open(_file, "r+", - 1, None, None, None, True, None)
|
||||
open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
open(_file, f" {r_plus} ", - 1)
|
||||
open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
||||
# Only diagnostic
|
||||
open()
|
||||
open(_file, *_x)
|
||||
open(_file, "r+", unknown=True)
|
||||
open(_file, "r+", closefd=False)
|
||||
open(_file, "r+", None, None, None, None, None, None, None)
|
||||
@@ -62,4 +62,4 @@ rename(
|
||||
|
||||
rename(file, "file_2.py", src_dir_fd=None, dst_dir_fd=None)
|
||||
|
||||
rename(file, "file_2.py", src_dir_fd=1)
|
||||
rename(file, "file_2.py", src_dir_fd=1)
|
||||
|
||||
@@ -974,3 +974,39 @@ BANANA = 100
|
||||
APPLE = 200
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/19752
|
||||
class foo:
|
||||
async def recv(self, *, length=65536):
|
||||
loop = asyncio.get_event_loop()
|
||||
def callback():
|
||||
loop.remove_reader(self._fd)
|
||||
loop.add_reader(self._fd, callback)
|
||||
# end
|
||||
|
||||
|
||||
# E301
|
||||
class Foo:
|
||||
if True:
|
||||
print("conditional")
|
||||
def test():
|
||||
pass
|
||||
# end
|
||||
|
||||
|
||||
# Test case for nested class scenario
|
||||
class Bar:
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
return 1
|
||||
return 2
|
||||
|
||||
def f():
|
||||
class Baz:
|
||||
x = 1
|
||||
def g():
|
||||
return 1
|
||||
return 2
|
||||
# end
|
||||
|
||||
@@ -125,4 +125,13 @@ class D:
|
||||
|
||||
@dataclass
|
||||
class E:
|
||||
c: C = C()
|
||||
c: C = C()
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20266
|
||||
@dataclass(frozen=True)
|
||||
class C:
|
||||
@dataclass(frozen=True)
|
||||
class D:
|
||||
foo: int = 1
|
||||
|
||||
d: D = D() # OK
|
||||
@@ -113,3 +113,18 @@ var = bytearray(b"abc")["x"]
|
||||
x = "x"
|
||||
var = [1, 2, 3][0:x]
|
||||
var = [1, 2, 3][x:1]
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20204
|
||||
|
||||
# Should not emit for boolean index and slice bounds
|
||||
var = [1, 2, 3][False]
|
||||
var = [1, 2, 3][False:True:True]
|
||||
|
||||
# Should emit for invalid access using t-string
|
||||
var = [1, 2][t"x"]
|
||||
|
||||
# Should emit for invalid access using lambda
|
||||
var = [1, 2, 3][lambda: 0]
|
||||
|
||||
# Should emit for invalid access using generator
|
||||
var = [1, 2, 3][(x for x in ())]
|
||||
|
||||
179
crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py
vendored
Normal file
179
crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
import asyncio
|
||||
|
||||
# Violation cases: RUF065
|
||||
|
||||
|
||||
async def test_coroutine_without_await():
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
coro() # RUF065
|
||||
|
||||
|
||||
async def test_coroutine_without_await():
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
result = coro() # RUF065
|
||||
|
||||
|
||||
async def test_coroutine_without_await():
|
||||
def not_coro():
|
||||
pass
|
||||
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
not_coro()
|
||||
coro() # RUF065
|
||||
|
||||
|
||||
async def test_coroutine_without_await():
|
||||
async def coro():
|
||||
another_coro() # RUF065
|
||||
|
||||
async def another_coro():
|
||||
pass
|
||||
|
||||
await coro()
|
||||
|
||||
|
||||
async def test_asyncio_api_without_await():
|
||||
asyncio.sleep(0.5) # RUF065
|
||||
|
||||
|
||||
async def test_asyncio_api_without_await():
|
||||
async def coro():
|
||||
asyncio.sleep(0.5) # RUF065
|
||||
|
||||
await asyncio.wait(coro)
|
||||
|
||||
|
||||
async def test_asyncio_api_without_await():
|
||||
async def coro():
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
asyncio.wait_for(coro) # RUF065
|
||||
|
||||
|
||||
async def test_asyncio_api_without_await():
|
||||
async def coro1():
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
async def coro2():
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
tasks = [coro1(), coro2()]
|
||||
asyncio.gather(*tasks) # RUF065
|
||||
|
||||
|
||||
# Non-violation cases: RUF065
|
||||
|
||||
|
||||
async def test_coroutine_with_await():
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
await coro() # OK
|
||||
|
||||
|
||||
async def test_coroutine_with_await():
|
||||
def not_coro():
|
||||
pass
|
||||
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
not_coro()
|
||||
await coro() # OK
|
||||
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
# define an asynchronous context manager
|
||||
class AsyncContextManager:
|
||||
# enter the async context manager
|
||||
async def __aenter__(self):
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
# define a simple coroutine
|
||||
async def custom_coroutine():
|
||||
# create and use the asynchronous context manager
|
||||
async with AsyncContextManager(): # OK
|
||||
...
|
||||
|
||||
|
||||
async def test_coroutine_in_func_arg():
|
||||
async def another_coro():
|
||||
pass
|
||||
|
||||
async def coro(cr):
|
||||
await cr
|
||||
|
||||
await coro(another_coro()) # OK
|
||||
|
||||
|
||||
async def test_coroutine_with_yield():
|
||||
async def another_coro():
|
||||
pass
|
||||
|
||||
async def coro():
|
||||
yield another_coro()
|
||||
|
||||
await coro() # OK
|
||||
|
||||
|
||||
async def test_coroutine_with_return():
|
||||
async def another_coro():
|
||||
pass
|
||||
|
||||
async def coro():
|
||||
return another_coro()
|
||||
|
||||
await coro() # OK
|
||||
|
||||
|
||||
async def test_coroutine_with_async_iterator():
|
||||
class Counter:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
pass
|
||||
|
||||
async def main():
|
||||
async for c in Counter(): # OK
|
||||
pass
|
||||
|
||||
|
||||
async def test_asyncio_api_with_await():
|
||||
async def task_coro(value):
|
||||
await asyncio.sleep(1)
|
||||
return value * 10
|
||||
|
||||
# main coroutine
|
||||
async def main():
|
||||
awaitables = [task_coro(i) for i in range(10)]
|
||||
await asyncio.gather(*awaitables) # OK
|
||||
|
||||
|
||||
async def test_coroutine_inside_collections():
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
[coro(), coro()] # OK
|
||||
(coro(), coro()) # OK
|
||||
{coro(), coro()} # OK
|
||||
{"coro": coro()} # OK
|
||||
|
||||
|
||||
async def test_func_used_in_arg_should_not_raise(func):
|
||||
func() # OK
|
||||
@@ -16,3 +16,5 @@ from collections import defaultdict # noqa: INVALID100, INVALID200, F401
|
||||
from itertools import chain # noqa: E402, INVALID300, F401
|
||||
# Test for mixed code types
|
||||
import json # noqa: E402, INVALID400, V100
|
||||
# Test for rule redirects
|
||||
import pandas as pd # noqa: TCH002
|
||||
|
||||
@@ -134,7 +134,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::FunctionCallInDataclassDefaultArgument) {
|
||||
ruff::rules::function_call_in_dataclass_default(checker, class_def);
|
||||
ruff::rules::function_call_in_dataclass_default(checker, class_def, scope_id);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::MutableClassDefault) {
|
||||
ruff::rules::mutable_class_default(checker, class_def);
|
||||
|
||||
@@ -1051,7 +1051,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::OsStat,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
@@ -1135,6 +1134,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::OsSymlink) {
|
||||
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BuiltinOpen) {
|
||||
flake8_use_pathlib::rules::builtin_open(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
@@ -1295,6 +1297,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::NonOctalPermissions) {
|
||||
ruff::rules::non_octal_permissions(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::MissingAwaitForCoroutine) {
|
||||
ruff::rules::missing_await_for_coroutine(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception_call(checker, call);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::types::Node;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::ScopeKind;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -809,17 +808,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pyflakes::rules::future_feature_not_defined(checker, alias);
|
||||
}
|
||||
} else if &alias.name == "*" {
|
||||
// F406
|
||||
if checker.is_rule_enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
||||
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
checker.report_diagnostic(
|
||||
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
||||
name: helpers::format_import_from(level, module).to_string(),
|
||||
},
|
||||
stmt.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// F403
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::UndefinedLocalWithImportStar {
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(crate) fn unresolved_references(checker: &Checker) {
|
||||
|
||||
for reference in checker.semantic.unresolved_references() {
|
||||
if reference.is_wildcard_import() {
|
||||
// F406
|
||||
// F405
|
||||
checker.report_diagnostic_if_enabled(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
name: reference.name(checker.source()).to_string(),
|
||||
|
||||
@@ -69,7 +69,8 @@ use crate::package::PackageRoot;
|
||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pyflakes::rules::{
|
||||
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
|
||||
LateFutureImport, ReturnOutsideFunction, UndefinedLocalWithNestedImportStarUsage,
|
||||
YieldOutsideFunction,
|
||||
};
|
||||
use crate::rules::pylint::rules::{
|
||||
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, YieldFromInAsyncFunction,
|
||||
@@ -276,7 +277,7 @@ impl<'a> Checker<'a> {
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
importer: Importer::new(parsed, locator, stylist),
|
||||
importer: Importer::new(parsed, locator.contents(), stylist),
|
||||
semantic,
|
||||
visit: deferred::Visit::default(),
|
||||
analyze: deferred::Analyze::default(),
|
||||
@@ -659,6 +660,14 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.report_diagnostic(YieldOutsideFunction::new(kind), error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::NonModuleImportStar(name) => {
|
||||
if self.is_rule_enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
||||
self.report_diagnostic(
|
||||
UndefinedLocalWithNestedImportStarUsage { name },
|
||||
error.range,
|
||||
);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
|
||||
// F706
|
||||
if self.is_rule_enabled(Rule::ReturnOutsideFunction) {
|
||||
@@ -3266,6 +3275,11 @@ impl<'a> LintContext<'a> {
|
||||
pub(crate) const fn settings(&self) -> &LinterSettings {
|
||||
self.settings
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
pub(crate) const fn source_file(&self) -> &SourceFile {
|
||||
&self.source_file
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction for mutating a diagnostic.
|
||||
|
||||
@@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::fix::edits::delete_comment;
|
||||
use crate::noqa::{
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn check_noqa(
|
||||
let noqa_offsets = diagnostic
|
||||
.parent()
|
||||
.into_iter()
|
||||
.chain(std::iter::once(diagnostic.expect_range().start()))
|
||||
.chain(diagnostic.range().map(TextRange::start).into_iter())
|
||||
.map(|position| noqa_line_for.resolve(position))
|
||||
.unique();
|
||||
|
||||
|
||||
@@ -944,7 +944,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathSamefile),
|
||||
(Flake8UsePathlib, "122") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::BuiltinOpen),
|
||||
(Flake8UsePathlib, "124") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::PyPath),
|
||||
(Flake8UsePathlib, "201") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::PathConstructorCurrentDirectory),
|
||||
(Flake8UsePathlib, "202") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathGetsize),
|
||||
@@ -1051,6 +1051,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
|
||||
(Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
|
||||
(Ruff, "065") => (RuleGroup::Preview, rules::ruff::rules::MissingAwaitForCoroutine),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),
|
||||
@@ -1080,6 +1081,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "950") => (RuleGroup::Stable, rules::ruff::rules::RedirectedToTestRule),
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Ruff, "960") => (RuleGroup::Removed, rules::ruff::rules::RedirectedFromPrefixTestRule),
|
||||
#[cfg(any(feature = "test-rules", test))]
|
||||
(Ruff, "990") => (RuleGroup::Preview, rules::ruff::rules::PanicyTestRule),
|
||||
|
||||
|
||||
// flake8-django
|
||||
|
||||
@@ -125,26 +125,27 @@ fn extract_noqa_line_for(tokens: &Tokens, locator: &Locator, indexer: &Indexer)
|
||||
}
|
||||
|
||||
// The capacity allocated here might be more than we need if there are
|
||||
// nested f-strings.
|
||||
let mut fstring_mappings = Vec::with_capacity(indexer.fstring_ranges().len());
|
||||
// nested interpolated strings.
|
||||
let mut interpolated_string_mappings =
|
||||
Vec::with_capacity(indexer.interpolated_string_ranges().len());
|
||||
|
||||
// For nested f-strings, we expect `noqa` directives on the last line of the
|
||||
// outermost f-string. The last f-string range will be used to skip over
|
||||
// the inner f-strings.
|
||||
let mut last_fstring_range: TextRange = TextRange::default();
|
||||
for fstring_range in indexer.fstring_ranges().values() {
|
||||
if !locator.contains_line_break(*fstring_range) {
|
||||
// For nested interpolated strings, we expect `noqa` directives on the last line of the
|
||||
// outermost interpolated string. The last interpolated string range will be used to skip over
|
||||
// the inner interpolated strings.
|
||||
let mut last_interpolated_string_range: TextRange = TextRange::default();
|
||||
for interpolated_string_range in indexer.interpolated_string_ranges().values() {
|
||||
if !locator.contains_line_break(*interpolated_string_range) {
|
||||
continue;
|
||||
}
|
||||
if last_fstring_range.contains_range(*fstring_range) {
|
||||
if last_interpolated_string_range.contains_range(*interpolated_string_range) {
|
||||
continue;
|
||||
}
|
||||
let new_range = TextRange::new(
|
||||
locator.line_start(fstring_range.start()),
|
||||
fstring_range.end(),
|
||||
locator.line_start(interpolated_string_range.start()),
|
||||
interpolated_string_range.end(),
|
||||
);
|
||||
fstring_mappings.push(new_range);
|
||||
last_fstring_range = new_range;
|
||||
interpolated_string_mappings.push(new_range);
|
||||
last_interpolated_string_range = new_range;
|
||||
}
|
||||
|
||||
let mut continuation_mappings = Vec::new();
|
||||
@@ -172,11 +173,11 @@ fn extract_noqa_line_for(tokens: &Tokens, locator: &Locator, indexer: &Indexer)
|
||||
|
||||
// Merge the mappings in sorted order
|
||||
let mut mappings = NoqaMapping::with_capacity(
|
||||
continuation_mappings.len() + string_mappings.len() + fstring_mappings.len(),
|
||||
continuation_mappings.len() + string_mappings.len() + interpolated_string_mappings.len(),
|
||||
);
|
||||
|
||||
let string_mappings = SortedMergeIter {
|
||||
left: fstring_mappings.into_iter().peekable(),
|
||||
left: interpolated_string_mappings.into_iter().peekable(),
|
||||
right: string_mappings.into_iter().peekable(),
|
||||
};
|
||||
let all_mappings = SortedMergeIter {
|
||||
@@ -497,12 +498,35 @@ end'''
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(6), TextSize::from(70))])
|
||||
);
|
||||
|
||||
let contents = "x = 1
|
||||
y = t'''abc
|
||||
def {f'''nested
|
||||
interpolated string''' f'another nested'}
|
||||
end'''
|
||||
";
|
||||
assert_eq!(
|
||||
noqa_mappings(contents),
|
||||
NoqaMapping::from_iter([TextRange::new(TextSize::from(6), TextSize::from(82))])
|
||||
);
|
||||
|
||||
let contents = "x = 1
|
||||
y = f'normal'
|
||||
z = f'another but {f'nested but {f'still single line'} nested'}'
|
||||
";
|
||||
assert_eq!(noqa_mappings(contents), NoqaMapping::default());
|
||||
|
||||
let contents = "x = 1
|
||||
y = t'normal'
|
||||
z = t'another but {t'nested but {t'still single line'} nested'}'
|
||||
";
|
||||
assert_eq!(noqa_mappings(contents), NoqaMapping::default());
|
||||
|
||||
let contents = "x = 1
|
||||
y = f'normal'
|
||||
z = f'another but {t'nested but {f'still single line'} nested'}'
|
||||
";
|
||||
assert_eq!(noqa_mappings(contents), NoqaMapping::default());
|
||||
|
||||
let contents = r"x = \
|
||||
1";
|
||||
assert_eq!(
|
||||
|
||||
@@ -14,6 +14,7 @@ use unicode_normalization::UnicodeNormalization;
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_python_ast::name::UnqualifiedName;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Locator;
|
||||
use crate::cst::matchers::match_statement;
|
||||
@@ -127,10 +128,10 @@ pub(crate) fn remove_imports<'a>(
|
||||
pub(crate) fn retain_imports(
|
||||
member_names: &[&str],
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
contents: &str,
|
||||
stylist: &Stylist,
|
||||
) -> Result<String> {
|
||||
let module_text = locator.slice(stmt);
|
||||
let module_text = &contents[stmt.range()];
|
||||
let mut tree = match_statement(module_text)?;
|
||||
|
||||
let Statement::Simple(body) = &mut tree else {
|
||||
|
||||
@@ -370,8 +370,8 @@ pub(crate) fn adjust_indentation(
|
||||
|
||||
// If the range includes a multi-line string, use LibCST to ensure that we don't adjust the
|
||||
// whitespace _within_ the string.
|
||||
let contains_multiline_string =
|
||||
indexer.multiline_ranges().intersects(range) || indexer.fstring_ranges().intersects(range);
|
||||
let contains_multiline_string = indexer.multiline_ranges().intersects(range)
|
||||
|| indexer.interpolated_string_ranges().intersects(range);
|
||||
|
||||
// If the range has mixed indentation, we will use LibCST as well.
|
||||
let mixed_indentation = contents.universal_newlines().any(|line| {
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{ImportAlias, Name as cstName, NameOrAttribute};
|
||||
use libcst_native as cst;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Expr, ModModule, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_importer::Insertion;
|
||||
use ruff_python_parser::{Parsed, Tokens};
|
||||
use ruff_python_semantic::{
|
||||
ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel,
|
||||
@@ -17,22 +19,17 @@ use ruff_python_semantic::{
|
||||
use ruff_python_trivia::textwrap::indent;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Edit;
|
||||
use crate::Locator;
|
||||
use crate::cst::matchers::{match_aliases, match_import_from, match_statement};
|
||||
use crate::fix;
|
||||
use crate::fix::codemods::CodegenStylist;
|
||||
use crate::importer::insertion::Insertion;
|
||||
|
||||
mod insertion;
|
||||
|
||||
pub(crate) struct Importer<'a> {
|
||||
/// The Python AST to which we are adding imports.
|
||||
python_ast: &'a [Stmt],
|
||||
/// The tokens representing the Python AST.
|
||||
tokens: &'a Tokens,
|
||||
/// The [`Locator`] for the Python AST.
|
||||
locator: &'a Locator<'a>,
|
||||
/// The source code text for `python_ast`.
|
||||
source: &'a str,
|
||||
/// The [`Stylist`] for the Python AST.
|
||||
stylist: &'a Stylist<'a>,
|
||||
/// The list of visited, top-level runtime imports in the Python AST.
|
||||
@@ -44,13 +41,13 @@ pub(crate) struct Importer<'a> {
|
||||
impl<'a> Importer<'a> {
|
||||
pub(crate) fn new(
|
||||
parsed: &'a Parsed<ModModule>,
|
||||
locator: &'a Locator<'a>,
|
||||
source: &'a str,
|
||||
stylist: &'a Stylist<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
python_ast: parsed.suite(),
|
||||
tokens: parsed.tokens(),
|
||||
locator,
|
||||
source,
|
||||
stylist,
|
||||
runtime_imports: Vec::default(),
|
||||
type_checking_blocks: Vec::default(),
|
||||
@@ -76,11 +73,10 @@ impl<'a> Importer<'a> {
|
||||
let required_import = import.to_string();
|
||||
if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
.into_edit(&required_import)
|
||||
Insertion::end_of_statement(stmt, self.source, self.stylist).into_edit(&required_import)
|
||||
} else {
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
Insertion::start_of_file(self.python_ast, self.source, self.stylist)
|
||||
.into_edit(&required_import)
|
||||
}
|
||||
}
|
||||
@@ -99,17 +95,17 @@ impl<'a> Importer<'a> {
|
||||
let content = fix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
self.locator,
|
||||
self.source,
|
||||
self.stylist,
|
||||
)?;
|
||||
|
||||
// Add the import to the top-level.
|
||||
let insertion = if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
Insertion::end_of_statement(stmt, self.source, self.stylist)
|
||||
} else {
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
Insertion::start_of_file(self.python_ast, self.source, self.stylist)
|
||||
};
|
||||
let add_import_edit = insertion.into_edit(&content);
|
||||
|
||||
@@ -131,7 +127,7 @@ impl<'a> Importer<'a> {
|
||||
let content = fix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
self.locator,
|
||||
self.source,
|
||||
self.stylist,
|
||||
)?;
|
||||
|
||||
@@ -155,7 +151,7 @@ impl<'a> Importer<'a> {
|
||||
None
|
||||
} else {
|
||||
Some(Edit::range_replacement(
|
||||
self.locator.slice(statement.range()).to_string(),
|
||||
self.source[statement.range()].to_string(),
|
||||
statement.range(),
|
||||
))
|
||||
}
|
||||
@@ -186,7 +182,7 @@ impl<'a> Importer<'a> {
|
||||
None
|
||||
} else {
|
||||
Some(Edit::range_replacement(
|
||||
self.locator.slice(type_checking.range()).to_string(),
|
||||
self.source[type_checking.range()].to_string(),
|
||||
type_checking.range(),
|
||||
))
|
||||
};
|
||||
@@ -362,7 +358,7 @@ impl<'a> Importer<'a> {
|
||||
// By adding this no-op edit, we force the `unused-imports` fix to conflict with the
|
||||
// `sys-exit-alias` fix, and thus will avoid applying both fixes in the same pass.
|
||||
let import_edit = Edit::range_replacement(
|
||||
self.locator.slice(imported_name.range()).to_string(),
|
||||
self.source[imported_name.range()].to_string(),
|
||||
imported_name.range(),
|
||||
);
|
||||
Ok(Some((import_edit, imported_name.into_name())))
|
||||
@@ -469,11 +465,11 @@ impl<'a> Importer<'a> {
|
||||
|
||||
/// Add the given member to an existing `Stmt::ImportFrom` statement.
|
||||
fn add_member(&self, stmt: &Stmt, member: &str) -> Result<Edit> {
|
||||
let mut statement = match_statement(self.locator.slice(stmt))?;
|
||||
let mut statement = match_statement(&self.source[stmt.range()])?;
|
||||
let import_from = match_import_from(&mut statement)?;
|
||||
let aliases = match_aliases(import_from)?;
|
||||
aliases.push(ImportAlias {
|
||||
name: NameOrAttribute::N(Box::new(cstName {
|
||||
aliases.push(cst::ImportAlias {
|
||||
name: cst::NameOrAttribute::N(Box::new(cst::Name {
|
||||
value: member,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
@@ -491,10 +487,10 @@ impl<'a> Importer<'a> {
|
||||
fn add_type_checking_block(&self, content: &str, at: TextSize) -> Result<Edit> {
|
||||
let insertion = if let Some(stmt) = self.preceding_import(at) {
|
||||
// Insert after the last top-level import.
|
||||
Insertion::end_of_statement(stmt, self.locator, self.stylist)
|
||||
Insertion::end_of_statement(stmt, self.source, self.stylist)
|
||||
} else {
|
||||
// Insert at the start of the file.
|
||||
Insertion::start_of_file(self.python_ast, self.locator, self.stylist)
|
||||
Insertion::start_of_file(self.python_ast, self.source, self.stylist)
|
||||
};
|
||||
if insertion.is_inline() {
|
||||
Err(anyhow::anyhow!(
|
||||
@@ -507,7 +503,7 @@ impl<'a> Importer<'a> {
|
||||
|
||||
/// Add an import statement to an existing `TYPE_CHECKING` block.
|
||||
fn add_to_type_checking_block(&self, content: &str, at: TextSize) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist, self.tokens).into_edit(content)
|
||||
Insertion::start_of_block(at, self.source, self.stylist, self.tokens).into_edit(content)
|
||||
}
|
||||
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
|
||||
@@ -319,6 +319,9 @@ pub fn check_path(
|
||||
&context,
|
||||
);
|
||||
}
|
||||
Rule::PanicyTestRule => {
|
||||
test_rules::PanicyTestRule::diagnostic(locator, comment_ranges, &context);
|
||||
}
|
||||
_ => unreachable!("All test rules must have an implementation"),
|
||||
}
|
||||
}
|
||||
@@ -513,10 +516,9 @@ fn diagnostics_to_messages(
|
||||
.map(|error| create_syntax_error_diagnostic(source_file.clone(), error, error)),
|
||||
)
|
||||
.chain(diagnostics.into_iter().map(|mut diagnostic| {
|
||||
let noqa_offset = directives
|
||||
.noqa_line_for
|
||||
.resolve(diagnostic.expect_range().start());
|
||||
diagnostic.set_noqa_offset(noqa_offset);
|
||||
if let Some(range) = diagnostic.range() {
|
||||
diagnostic.set_noqa_offset(directives.noqa_line_for.resolve(range.start()));
|
||||
}
|
||||
diagnostic
|
||||
}))
|
||||
.collect()
|
||||
@@ -983,7 +985,7 @@ mod tests {
|
||||
&parsed,
|
||||
target_version,
|
||||
);
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
||||
diagnostics
|
||||
}
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
|
||||
/// Generate error workflow command in GitHub Actions format.
|
||||
/// See: [GitHub documentation](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message)
|
||||
#[derive(Default)]
|
||||
pub struct GithubEmitter;
|
||||
|
||||
impl Emitter for GithubEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
let source_location = diagnostic.ruff_start_location().unwrap_or_default();
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let location = if context.is_notebook(&filename) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
LineColumn::default()
|
||||
} else {
|
||||
source_location
|
||||
};
|
||||
|
||||
let end_location = diagnostic.ruff_end_location().unwrap_or_default();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"::error title=Ruff ({code}),file={file},line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
code = diagnostic.secondary_code_or_id(),
|
||||
file = filename,
|
||||
row = source_location.line,
|
||||
column = source_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"{path}:{row}:{column}:",
|
||||
path = relativize_path(&filename),
|
||||
row = location.line,
|
||||
column = location.column,
|
||||
)?;
|
||||
|
||||
if let Some(code) = diagnostic.secondary_code() {
|
||||
write!(writer, " {code}")?;
|
||||
} else {
|
||||
write!(writer, " {id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(writer, " {}", diagnostic.body())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::GithubEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let mut emitter = GithubEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = GithubEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ use ruff_db::diagnostic::{
|
||||
};
|
||||
use ruff_db::files::File;
|
||||
|
||||
pub use github::GithubEmitter;
|
||||
pub use grouped::GroupedEmitter;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::SourceFile;
|
||||
@@ -20,7 +19,6 @@ pub use text::TextEmitter;
|
||||
use crate::Fix;
|
||||
use crate::registry::Rule;
|
||||
|
||||
mod github;
|
||||
mod grouped;
|
||||
mod sarif;
|
||||
mod text;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/github.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
---
|
||||
::error title=Ruff (F401),file=fib.py,line=1,col=8,endLine=1,endColumn=10::fib.py:1:8: F401 `os` imported but unused
|
||||
::error title=Ruff (F841),file=fib.py,line=6,col=5,endLine=6,endColumn=6::fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
||||
::error title=Ruff (F821),file=undef.py,line=1,col=4,endLine=1,endColumn=5::undef.py:1:4: F821 Undefined name `a`
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/github.rs
|
||||
expression: content
|
||||
---
|
||||
::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=Ruff (invalid-syntax),file=syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
@@ -886,7 +886,10 @@ fn find_noqa_comments<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
let noqa_offset = noqa_line_for.resolve(message.expect_range().start());
|
||||
let noqa_offset = message
|
||||
.range()
|
||||
.map(|range| noqa_line_for.resolve(range.start()))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Or ignored by the directive itself?
|
||||
if let Some(directive_line) = directives.find_line_with_directive(noqa_offset) {
|
||||
|
||||
@@ -218,3 +218,13 @@ pub(crate) const fn is_unnecessary_default_type_args_stubs_enabled(
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20343
|
||||
pub(crate) const fn is_sim910_expanded_key_support_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20169
|
||||
pub(crate) const fn is_fix_builtin_open_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -124,4 +124,6 @@ S602 `subprocess` call with `shell=True` identified, security issue
|
||||
19 | Popen([var_string], shell=True)
|
||||
20 | Popen([var_string, ""], shell=True)
|
||||
| ^^^^^
|
||||
21 |
|
||||
22 | # Check dict display with only double-starred expressions can be falsey.
|
||||
|
|
||||
|
||||
@@ -6,4 +6,6 @@ S604 Function call with `shell=True` parameter identified, security issue
|
||||
|
|
||||
5 | foo(shell=True)
|
||||
| ^^^
|
||||
6 |
|
||||
7 | foo(shell={**{}})
|
||||
|
|
||||
|
||||
@@ -34,10 +34,12 @@ S609 Possible wildcard injection in call due to `*` usage
|
||||
|
|
||||
|
||||
S609 Possible wildcard injection in call due to `*` usage
|
||||
--> S609.py:8:11
|
||||
|
|
||||
6 | subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
|
||||
7 | subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
|
||||
8 | os.system("tar cf foo.tar bar/*")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
--> S609.py:8:11
|
||||
|
|
||||
6 | subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
|
||||
7 | subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
|
||||
8 | os.system("tar cf foo.tar bar/*")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
9 |
|
||||
10 | subprocess.Popen(["chmod", "+w", "*.py"], shell={**{}})
|
||||
|
|
||||
|
||||
@@ -28,10 +28,29 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe if there's comments in the `hasattr` call
|
||||
/// expression, as comments may be removed.
|
||||
/// This rule's fix is marked as unsafe because the replacement may not be semantically
|
||||
/// equivalent to the original expression, potentially changing the behavior of the code.
|
||||
///
|
||||
/// For example, the fix would be marked as unsafe in the following case:
|
||||
/// For example, an imported module may have a `__call__` attribute but is not considered
|
||||
/// a callable object:
|
||||
/// ```python
|
||||
/// import operator
|
||||
///
|
||||
/// assert hasattr(operator, "__call__")
|
||||
/// assert callable(operator) is False
|
||||
/// ```
|
||||
/// Additionally, `__call__` may be defined only as an instance method:
|
||||
/// ```python
|
||||
/// class A:
|
||||
/// def __init__(self):
|
||||
/// self.__call__ = None
|
||||
///
|
||||
///
|
||||
/// assert hasattr(A(), "__call__")
|
||||
/// assert callable(A()) is False
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, if there are comments in the `hasattr` call expression, they may be removed:
|
||||
/// ```python
|
||||
/// hasattr(
|
||||
/// # comment 1
|
||||
@@ -103,11 +122,7 @@ pub(crate) fn unreliable_callable_check(
|
||||
Ok(Fix::applicable_edits(
|
||||
binding_edit,
|
||||
import_edit,
|
||||
if checker.comment_ranges().intersects(expr.range()) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
Applicability::Unsafe,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ help: Replace with `callable()`
|
||||
4 | print("Ooh, callable! Or is it?")
|
||||
5 | if getattr(o, "__call__", False):
|
||||
6 | print("Ooh, callable! Or is it?")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B004 Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
--> B004.py:5:8
|
||||
@@ -50,6 +51,7 @@ help: Replace with `callable()`
|
||||
13 | print("B U G")
|
||||
14 | if builtins.getattr(o, "__call__", False):
|
||||
15 | print("B U G")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B004 Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
--> B004.py:14:8
|
||||
@@ -85,6 +87,7 @@ help: Replace with `callable()`
|
||||
26 | print("STILL a bug!")
|
||||
27 |
|
||||
28 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
--> B004.py:35:1
|
||||
@@ -99,6 +102,8 @@ B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
|
||||
40 | | # comment 5
|
||||
41 | | )
|
||||
| |_^
|
||||
42 |
|
||||
43 | import operator
|
||||
|
|
||||
help: Replace with `callable()`
|
||||
32 |
|
||||
@@ -112,4 +117,43 @@ help: Replace with `callable()`
|
||||
- # comment 5
|
||||
- )
|
||||
35 + callable(obj)
|
||||
36 |
|
||||
37 | import operator
|
||||
38 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
--> B004.py:45:8
|
||||
|
|
||||
43 | import operator
|
||||
44 |
|
||||
45 | assert hasattr(operator, "__call__")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
46 | assert callable(operator) is False
|
||||
|
|
||||
help: Replace with `callable()`
|
||||
42 |
|
||||
43 | import operator
|
||||
44 |
|
||||
- assert hasattr(operator, "__call__")
|
||||
45 + assert callable(operator)
|
||||
46 | assert callable(operator) is False
|
||||
47 |
|
||||
48 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is unreliable. Use `callable(x)` for consistent results.
|
||||
--> B004.py:53:8
|
||||
|
|
||||
53 | assert hasattr(A(), "__call__")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
54 | assert callable(A()) is False
|
||||
|
|
||||
help: Replace with `callable()`
|
||||
50 | def __init__(self): self.__call__ = None
|
||||
51 |
|
||||
52 |
|
||||
- assert hasattr(A(), "__call__")
|
||||
53 + assert callable(A())
|
||||
54 | assert callable(A()) is False
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -250,23 +250,23 @@ pub(crate) fn trailing_commas(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
let mut fstrings = 0u32;
|
||||
let mut interpolated_strings = 0u32;
|
||||
let simple_tokens = tokens.iter().filter_map(|token| {
|
||||
match token.kind() {
|
||||
// Completely ignore comments -- they just interfere with the logic.
|
||||
TokenKind::Comment => None,
|
||||
// F-strings are handled as `String` token type with the complete range
|
||||
// of the outermost f-string. This means that the expression inside the
|
||||
// f-string is not checked for trailing commas.
|
||||
TokenKind::FStringStart => {
|
||||
fstrings = fstrings.saturating_add(1);
|
||||
// F-strings and t-strings are handled as `String` token type with the complete range
|
||||
// of the outermost interpolated string. This means that the expression inside the
|
||||
// interpolated string is not checked for trailing commas.
|
||||
TokenKind::FStringStart | TokenKind::TStringStart => {
|
||||
interpolated_strings = interpolated_strings.saturating_add(1);
|
||||
None
|
||||
}
|
||||
TokenKind::FStringEnd => {
|
||||
fstrings = fstrings.saturating_sub(1);
|
||||
if fstrings == 0 {
|
||||
TokenKind::FStringEnd | TokenKind::TStringEnd => {
|
||||
interpolated_strings = interpolated_strings.saturating_sub(1);
|
||||
if interpolated_strings == 0 {
|
||||
indexer
|
||||
.fstring_ranges()
|
||||
.interpolated_string_ranges()
|
||||
.outermost(token.start())
|
||||
.map(|range| SimpleToken::new(TokenType::String, range))
|
||||
} else {
|
||||
@@ -274,7 +274,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if fstrings == 0 {
|
||||
if interpolated_strings == 0 {
|
||||
Some(SimpleToken::from(token.as_tuple()))
|
||||
} else {
|
||||
None
|
||||
@@ -362,8 +362,7 @@ fn check_token(
|
||||
if let Some(mut diagnostic) =
|
||||
lint_context.report_diagnostic_if_enabled(ProhibitedTrailingComma, prev.range())
|
||||
{
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(prev.range)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,6 +1016,7 @@ help: Remove trailing comma
|
||||
664 + type X[T] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
667 |
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:665:8
|
||||
@@ -1032,6 +1033,8 @@ help: Remove trailing comma
|
||||
- def f[T,](): pass
|
||||
665 + def f[T](): pass
|
||||
666 | class C[T,]: pass
|
||||
667 |
|
||||
668 | # t-string examples
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:666:10
|
||||
@@ -1040,6 +1043,8 @@ COM819 [*] Trailing comma prohibited
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
| ^
|
||||
667 |
|
||||
668 | # t-string examples
|
||||
|
|
||||
help: Remove trailing comma
|
||||
663 |
|
||||
@@ -1047,3 +1052,44 @@ help: Remove trailing comma
|
||||
665 | def f[T,](): pass
|
||||
- class C[T,]: pass
|
||||
666 + class C[T]: pass
|
||||
667 |
|
||||
668 | # t-string examples
|
||||
669 | kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:669:46
|
||||
|
|
||||
668 | # t-string examples
|
||||
669 | kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
| ^
|
||||
670 | kwargs.pop("remove", t"this {f"{trailing_comma}"}",)
|
||||
|
|
||||
help: Remove trailing comma
|
||||
666 | class C[T,]: pass
|
||||
667 |
|
||||
668 | # t-string examples
|
||||
- kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
669 + kwargs.pop("remove", t"this {trailing_comma}")
|
||||
670 | kwargs.pop("remove", t"this {f"{trailing_comma}"}",)
|
||||
671 |
|
||||
672 | t"""This is a test. {
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:670:51
|
||||
|
|
||||
668 | # t-string examples
|
||||
669 | kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
670 | kwargs.pop("remove", t"this {f"{trailing_comma}"}",)
|
||||
| ^
|
||||
671 |
|
||||
672 | t"""This is a test. {
|
||||
|
|
||||
help: Remove trailing comma
|
||||
667 |
|
||||
668 | # t-string examples
|
||||
669 | kwargs.pop("remove", t"this {trailing_comma}",)
|
||||
- kwargs.pop("remove", t"this {f"{trailing_comma}"}",)
|
||||
670 + kwargs.pop("remove", t"this {f"{trailing_comma}"}")
|
||||
671 |
|
||||
672 | t"""This is a test. {
|
||||
673 | "Another sentence."
|
||||
|
||||
@@ -74,6 +74,7 @@ pub(crate) fn explicit(checker: &Checker, expr: &Expr) {
|
||||
Expr::StringLiteral(_) | Expr::FString(_),
|
||||
Expr::StringLiteral(_) | Expr::FString(_)
|
||||
) | (Expr::BytesLiteral(_), Expr::BytesLiteral(_))
|
||||
| (Expr::TString(_), Expr::TString(_))
|
||||
);
|
||||
if concatable
|
||||
&& checker
|
||||
|
||||
@@ -123,21 +123,32 @@ pub(crate) fn implicit(
|
||||
let (a_range, b_range) = match (a_token.kind(), b_token.kind()) {
|
||||
(TokenKind::String, TokenKind::String) => (a_token.range(), b_token.range()),
|
||||
(TokenKind::String, TokenKind::FStringStart) => {
|
||||
match indexer.fstring_ranges().innermost(b_token.start()) {
|
||||
match indexer
|
||||
.interpolated_string_ranges()
|
||||
.innermost(b_token.start())
|
||||
{
|
||||
Some(b_range) => (a_token.range(), b_range),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(TokenKind::FStringEnd, TokenKind::String) => {
|
||||
match indexer.fstring_ranges().innermost(a_token.start()) {
|
||||
match indexer
|
||||
.interpolated_string_ranges()
|
||||
.innermost(a_token.start())
|
||||
{
|
||||
Some(a_range) => (a_range, b_token.range()),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(TokenKind::FStringEnd, TokenKind::FStringStart) => {
|
||||
(TokenKind::FStringEnd, TokenKind::FStringStart)
|
||||
| (TokenKind::TStringEnd, TokenKind::TStringStart) => {
|
||||
match (
|
||||
indexer.fstring_ranges().innermost(a_token.start()),
|
||||
indexer.fstring_ranges().innermost(b_token.start()),
|
||||
indexer
|
||||
.interpolated_string_ranges()
|
||||
.innermost(a_token.start()),
|
||||
indexer
|
||||
.interpolated_string_ranges()
|
||||
.innermost(b_token.start()),
|
||||
) {
|
||||
(Some(a_range), Some(b_range)) => (a_range, b_range),
|
||||
_ => continue,
|
||||
|
||||
@@ -484,3 +484,104 @@ help: Combine string literals
|
||||
94 |
|
||||
95 |
|
||||
96 | # Mixed literal + non-literal scenarios
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:193:1
|
||||
|
|
||||
191 | # https://github.com/astral-sh/ruff/issues/20310
|
||||
192 | # ISC001
|
||||
193 | t"The quick " t"brown fox."
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
194 |
|
||||
195 | # ISC002
|
||||
|
|
||||
help: Combine string literals
|
||||
190 |
|
||||
191 | # https://github.com/astral-sh/ruff/issues/20310
|
||||
192 | # ISC001
|
||||
- t"The quick " t"brown fox."
|
||||
193 + t"The quick brown fox."
|
||||
194 |
|
||||
195 | # ISC002
|
||||
196 | t"The quick brown fox jumps over the lazy "\
|
||||
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:5
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:9
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:14
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
203 | )
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
- _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
206 + _ = "a" f"b {t"cd"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:207:10
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
|
||||
help: Combine string literals
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
- _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
207 + _ = t"b {f"cd {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
210 |
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:207:20
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
| ^^^^^^^^^
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
|
||||
help: Combine string literals
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
- _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
207 + _ = t"b {f"c" f"d {t"ef"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
210 |
|
||||
|
||||
@@ -26,3 +26,25 @@ ISC002 Implicitly concatenated string literals over multiple lines
|
||||
76 |
|
||||
77 | # Explicitly concatenated nested f-strings
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:196:1
|
||||
|
|
||||
195 | # ISC002
|
||||
196 | / t"The quick brown fox jumps over the lazy "\
|
||||
197 | | t"dog."
|
||||
| |_______^
|
||||
198 |
|
||||
199 | # ISC003
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:208:10
|
||||
|
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
| __________^
|
||||
209 | | t"def"} g"
|
||||
| |__________^
|
||||
|
|
||||
|
||||
@@ -337,3 +337,23 @@ help: Remove redundant '+' operator to implicitly concatenate
|
||||
187 | # leading comment
|
||||
188 | "end"
|
||||
189 | )
|
||||
|
||||
ISC003 [*] Explicitly concatenated string should be implicitly concatenated
|
||||
--> ISC.py:201:5
|
||||
|
|
||||
199 | # ISC003
|
||||
200 | (
|
||||
201 | / t"The quick brown fox jumps over the lazy "
|
||||
202 | | + t"dog"
|
||||
| |____________^
|
||||
203 | )
|
||||
|
|
||||
help: Remove redundant '+' operator to implicitly concatenate
|
||||
199 | # ISC003
|
||||
200 | (
|
||||
201 | t"The quick brown fox jumps over the lazy "
|
||||
- + t"dog"
|
||||
202 + t"dog"
|
||||
203 | )
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
|
||||
@@ -484,3 +484,104 @@ help: Combine string literals
|
||||
94 |
|
||||
95 |
|
||||
96 | # Mixed literal + non-literal scenarios
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:193:1
|
||||
|
|
||||
191 | # https://github.com/astral-sh/ruff/issues/20310
|
||||
192 | # ISC001
|
||||
193 | t"The quick " t"brown fox."
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
194 |
|
||||
195 | # ISC002
|
||||
|
|
||||
help: Combine string literals
|
||||
190 |
|
||||
191 | # https://github.com/astral-sh/ruff/issues/20310
|
||||
192 | # ISC001
|
||||
- t"The quick " t"brown fox."
|
||||
193 + t"The quick brown fox."
|
||||
194 |
|
||||
195 | # ISC002
|
||||
196 | t"The quick brown fox jumps over the lazy "\
|
||||
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:5
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
ISC001 Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:9
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:206:14
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
| ^^^^^^^^^
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
|
|
||||
help: Combine string literals
|
||||
203 | )
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
- _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
206 + _ = "a" f"b {t"cd"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:207:10
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
|
||||
help: Combine string literals
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
- _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
207 + _ = t"b {f"cd {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
210 |
|
||||
|
||||
ISC001 [*] Implicitly concatenated string literals on one line
|
||||
--> ISC.py:207:20
|
||||
|
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
| ^^^^^^^^^
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
|
|
||||
help: Combine string literals
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
- _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
207 + _ = t"b {f"c" f"d {t"ef"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
209 | t"def"} g"
|
||||
210 |
|
||||
|
||||
@@ -67,3 +67,25 @@ ISC002 Implicitly concatenated string literals over multiple lines
|
||||
76 |
|
||||
77 | # Explicitly concatenated nested f-strings
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:196:1
|
||||
|
|
||||
195 | # ISC002
|
||||
196 | / t"The quick brown fox jumps over the lazy "\
|
||||
197 | | t"dog."
|
||||
| |_______^
|
||||
198 |
|
||||
199 | # ISC003
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:208:10
|
||||
|
|
||||
206 | _ = "a" f"b {t"c" t"d"} e" "f"
|
||||
207 | _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
208 | _ = f"b {t"abc" \
|
||||
| __________^
|
||||
209 | | t"def"} g"
|
||||
| |__________^
|
||||
|
|
||||
|
||||
@@ -60,6 +60,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
|
||||
#[test_case(Rule::DictGetWithNoneDefault, Path::new("SIM910.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -264,8 +264,10 @@ pub(crate) fn dict_get_with_none_default(checker: &Checker, expr: &Expr) {
|
||||
let Some(key) = args.first() else {
|
||||
return;
|
||||
};
|
||||
if !(key.is_literal_expr() || key.is_name_expr()) {
|
||||
return;
|
||||
if !crate::preview::is_sim910_expanded_key_support_enabled(checker.settings()) {
|
||||
if !(key.is_literal_expr() || key.is_name_expr()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let Some(default) = args.get(1) else {
|
||||
return;
|
||||
|
||||
@@ -167,3 +167,6 @@ help: Replace `dict.get("Cat", None)` with `dict.get("Cat")`
|
||||
56 | dict = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = dict.get("Cat", None)
|
||||
57 + age = dict.get("Cat")
|
||||
58 |
|
||||
59 |
|
||||
60 | # https://github.com/astral-sh/ruff/issues/20341
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM910 [*] Use `{}.get(key)` instead of `{}.get(key, None)`
|
||||
--> SIM910.py:2:1
|
||||
|
|
||||
1 | # SIM910
|
||||
2 | {}.get(key, None)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | # SIM910
|
||||
|
|
||||
help: Replace `{}.get(key, None)` with `{}.get(key)`
|
||||
1 | # SIM910
|
||||
- {}.get(key, None)
|
||||
2 + {}.get(key)
|
||||
3 |
|
||||
4 | # SIM910
|
||||
5 | {}.get("key", None)
|
||||
|
||||
SIM910 [*] Use `{}.get("key")` instead of `{}.get("key", None)`
|
||||
--> SIM910.py:5:1
|
||||
|
|
||||
4 | # SIM910
|
||||
5 | {}.get("key", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | # OK
|
||||
|
|
||||
help: Replace `{}.get("key", None)` with `{}.get("key")`
|
||||
2 | {}.get(key, None)
|
||||
3 |
|
||||
4 | # SIM910
|
||||
- {}.get("key", None)
|
||||
5 + {}.get("key")
|
||||
6 |
|
||||
7 | # OK
|
||||
8 | {}.get(key)
|
||||
|
||||
SIM910 [*] Use `{}.get(key)` instead of `{}.get(key, None)`
|
||||
--> SIM910.py:20:9
|
||||
|
|
||||
19 | # SIM910
|
||||
20 | if a := {}.get(key, None):
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
21 | pass
|
||||
|
|
||||
help: Replace `{}.get(key, None)` with `{}.get(key)`
|
||||
17 | {}.get("key", False)
|
||||
18 |
|
||||
19 | # SIM910
|
||||
- if a := {}.get(key, None):
|
||||
20 + if a := {}.get(key):
|
||||
21 | pass
|
||||
22 |
|
||||
23 | # SIM910
|
||||
|
||||
SIM910 [*] Use `{}.get(key)` instead of `{}.get(key, None)`
|
||||
--> SIM910.py:24:5
|
||||
|
|
||||
23 | # SIM910
|
||||
24 | a = {}.get(key, None)
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
25 |
|
||||
26 | # SIM910
|
||||
|
|
||||
help: Replace `{}.get(key, None)` with `{}.get(key)`
|
||||
21 | pass
|
||||
22 |
|
||||
23 | # SIM910
|
||||
- a = {}.get(key, None)
|
||||
24 + a = {}.get(key)
|
||||
25 |
|
||||
26 | # SIM910
|
||||
27 | ({}).get(key, None)
|
||||
|
||||
SIM910 [*] Use `({}).get(key)` instead of `({}).get(key, None)`
|
||||
--> SIM910.py:27:1
|
||||
|
|
||||
26 | # SIM910
|
||||
27 | ({}).get(key, None)
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
28 |
|
||||
29 | # SIM910
|
||||
|
|
||||
help: Replace `({}).get(key, None)` with `({}).get(key)`
|
||||
24 | a = {}.get(key, None)
|
||||
25 |
|
||||
26 | # SIM910
|
||||
- ({}).get(key, None)
|
||||
27 + ({}).get(key)
|
||||
28 |
|
||||
29 | # SIM910
|
||||
30 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
|
||||
SIM910 [*] Use `ages.get("Cat")` instead of `ages.get("Cat", None)`
|
||||
--> SIM910.py:31:7
|
||||
|
|
||||
29 | # SIM910
|
||||
30 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
31 | age = ages.get("Cat", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | # OK
|
||||
|
|
||||
help: Replace `ages.get("Cat", None)` with `ages.get("Cat")`
|
||||
28 |
|
||||
29 | # SIM910
|
||||
30 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = ages.get("Cat", None)
|
||||
31 + age = ages.get("Cat")
|
||||
32 |
|
||||
33 | # OK
|
||||
34 | ages = ["Tom", "Maria", "Dog"]
|
||||
|
||||
SIM910 [*] Use `kwargs.get('a')` instead of `kwargs.get('a', None)`
|
||||
--> SIM910.py:39:9
|
||||
|
|
||||
37 | # SIM910
|
||||
38 | def foo(**kwargs):
|
||||
39 | a = kwargs.get('a', None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
40 |
|
||||
41 | # SIM910
|
||||
|
|
||||
help: Replace `kwargs.get('a', None)` with `kwargs.get('a')`
|
||||
36 |
|
||||
37 | # SIM910
|
||||
38 | def foo(**kwargs):
|
||||
- a = kwargs.get('a', None)
|
||||
39 + a = kwargs.get('a')
|
||||
40 |
|
||||
41 | # SIM910
|
||||
42 | def foo(some_dict: dict):
|
||||
|
||||
SIM910 [*] Use `some_dict.get('a')` instead of `some_dict.get('a', None)`
|
||||
--> SIM910.py:43:9
|
||||
|
|
||||
41 | # SIM910
|
||||
42 | def foo(some_dict: dict):
|
||||
43 | a = some_dict.get('a', None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
44 |
|
||||
45 | # OK
|
||||
|
|
||||
help: Replace `some_dict.get('a', None)` with `some_dict.get('a')`
|
||||
40 |
|
||||
41 | # SIM910
|
||||
42 | def foo(some_dict: dict):
|
||||
- a = some_dict.get('a', None)
|
||||
43 + a = some_dict.get('a')
|
||||
44 |
|
||||
45 | # OK
|
||||
46 | def foo(some_other: object):
|
||||
|
||||
SIM910 [*] Use `dict.get("Cat")` instead of `dict.get("Cat", None)`
|
||||
--> SIM910.py:57:11
|
||||
|
|
||||
55 | def foo():
|
||||
56 | dict = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
57 | age = dict.get("Cat", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace `dict.get("Cat", None)` with `dict.get("Cat")`
|
||||
54 | # https://github.com/astral-sh/ruff/issues/18777
|
||||
55 | def foo():
|
||||
56 | dict = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = dict.get("Cat", None)
|
||||
57 + age = dict.get("Cat")
|
||||
58 |
|
||||
59 |
|
||||
60 | # https://github.com/astral-sh/ruff/issues/20341
|
||||
|
||||
SIM910 [*] Use `ages.get(key_source.get("Thomas", "Tom"))` instead of `ages.get(key_source.get("Thomas", "Tom"), None)`
|
||||
--> SIM910.py:64:7
|
||||
|
|
||||
62 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
63 | key_source = {"Thomas": "Tom"}
|
||||
64 | age = ages.get(key_source.get("Thomas", "Tom"), None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
65 |
|
||||
66 | # Property access as key
|
||||
|
|
||||
help: Replace `ages.get(key_source.get("Thomas", "Tom"), None)` with `ages.get(key_source.get("Thomas", "Tom"))`
|
||||
61 | # Method call as key
|
||||
62 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
63 | key_source = {"Thomas": "Tom"}
|
||||
- age = ages.get(key_source.get("Thomas", "Tom"), None)
|
||||
64 + age = ages.get(key_source.get("Thomas", "Tom"))
|
||||
65 |
|
||||
66 | # Property access as key
|
||||
67 | class Data:
|
||||
|
||||
SIM910 [*] Use `ages.get(data.name)` instead of `ages.get(data.name, None)`
|
||||
--> SIM910.py:73:7
|
||||
|
|
||||
71 | data = Data()
|
||||
72 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
73 | age = ages.get(data.name, None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
74 |
|
||||
75 | # Complex expression as key
|
||||
|
|
||||
help: Replace `ages.get(data.name, None)` with `ages.get(data.name)`
|
||||
70 |
|
||||
71 | data = Data()
|
||||
72 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = ages.get(data.name, None)
|
||||
73 + age = ages.get(data.name)
|
||||
74 |
|
||||
75 | # Complex expression as key
|
||||
76 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
|
||||
SIM910 [*] Use `ages.get("Tom" if True else "Maria")` instead of `ages.get("Tom" if True else "Maria", None)`
|
||||
--> SIM910.py:77:7
|
||||
|
|
||||
75 | # Complex expression as key
|
||||
76 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
77 | age = ages.get("Tom" if True else "Maria", None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
78 |
|
||||
79 | # Function call as key
|
||||
|
|
||||
help: Replace `ages.get("Tom" if True else "Maria", None)` with `ages.get("Tom" if True else "Maria")`
|
||||
74 |
|
||||
75 | # Complex expression as key
|
||||
76 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = ages.get("Tom" if True else "Maria", None)
|
||||
77 + age = ages.get("Tom" if True else "Maria")
|
||||
78 |
|
||||
79 | # Function call as key
|
||||
80 | def get_key():
|
||||
|
||||
SIM910 [*] Use `ages.get(get_key())` instead of `ages.get(get_key(), None)`
|
||||
--> SIM910.py:84:7
|
||||
|
|
||||
83 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
84 | age = ages.get(get_key(), None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace `ages.get(get_key(), None)` with `ages.get(get_key())`
|
||||
81 | return "Tom"
|
||||
82 |
|
||||
83 | ages = {"Tom": 23, "Maria": 23, "Dog": 11}
|
||||
- age = ages.get(get_key(), None)
|
||||
84 + age = ages.get(get_key())
|
||||
@@ -205,3 +205,14 @@ pub(crate) fn has_unknown_keywords_or_starred_expr(
|
||||
None => true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if argument `name` is set to a non-default `None` value.
|
||||
pub(crate) fn is_argument_non_default(
|
||||
arguments: &ast::Arguments,
|
||||
name: &str,
|
||||
position: usize,
|
||||
) -> bool {
|
||||
arguments
|
||||
.find_argument_value(name, position)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_1.py"))]
|
||||
#[test_case(Rule::PyPath, Path::new("py_path_2.py"))]
|
||||
#[test_case(Rule::BuiltinOpen, Path::new("PTH123.py"))]
|
||||
#[test_case(Rule::PathConstructorCurrentDirectory, Path::new("PTH201.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
|
||||
@@ -123,6 +124,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BuiltinOpen, Path::new("PTH123.py"))]
|
||||
#[test_case(Rule::PathConstructorCurrentDirectory, Path::new("PTH201.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
use ruff_diagnostics::{Applicability, Edit, Fix};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{ArgOrKeyword, Expr, ExprBooleanLiteral, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_builtin_open_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
has_unknown_keywords_or_starred_expr, is_argument_non_default, is_file_descriptor,
|
||||
is_pathlib_path_call,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `open()` builtin.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation. When possible,
|
||||
/// using `Path` object methods such as `Path.open()` can improve readability
|
||||
/// over the `open` builtin.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// with open("f1.py", "wb") as fp:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// with Path("f1.py").open("wb") as fp:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than working directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BuiltinOpen;
|
||||
|
||||
impl Violation for BuiltinOpen {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`open()` should be replaced by `Path.open()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path.open()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// PTH123
|
||||
pub(crate) fn builtin_open(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they
|
||||
// are set to non-default values.
|
||||
// https://github.com/astral-sh/ruff/issues/7620
|
||||
// Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open):
|
||||
// ```text
|
||||
// builtins.open(
|
||||
// file, 0
|
||||
// mode='r', 1
|
||||
// buffering=-1, 2
|
||||
// encoding=None, 3
|
||||
// errors=None, 4
|
||||
// newline=None, 5
|
||||
// closefd=True, 6 <= not supported
|
||||
// opener=None 7 <= not supported
|
||||
// )
|
||||
// ```
|
||||
// For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open):
|
||||
// ```text
|
||||
// Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
|
||||
// ```
|
||||
let file_arg = call.arguments.find_argument_value("file", 0);
|
||||
|
||||
if call
|
||||
.arguments
|
||||
.find_argument_value("closefd", 6)
|
||||
.is_some_and(|expr| {
|
||||
!matches!(
|
||||
expr,
|
||||
Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. })
|
||||
)
|
||||
})
|
||||
|| is_argument_non_default(&call.arguments, "opener", 7)
|
||||
|| file_arg.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches!(segments, ["" | "builtins", "open"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(BuiltinOpen, call.func.range());
|
||||
|
||||
if !is_fix_builtin_open_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(file) = file_arg else {
|
||||
return;
|
||||
};
|
||||
|
||||
if has_unknown_keywords_or_starred_expr(
|
||||
&call.arguments,
|
||||
&[
|
||||
"file",
|
||||
"mode",
|
||||
"buffering",
|
||||
"encoding",
|
||||
"errors",
|
||||
"newline",
|
||||
"closefd",
|
||||
"opener",
|
||||
],
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let locator = checker.locator();
|
||||
let file_code = locator.slice(file.range());
|
||||
|
||||
let args = |i: usize, arg: ArgOrKeyword| match arg {
|
||||
ArgOrKeyword::Arg(expr) => {
|
||||
if expr.range() == file.range() || i == 6 || i == 7 {
|
||||
None
|
||||
} else {
|
||||
Some(locator.slice(expr.range()))
|
||||
}
|
||||
}
|
||||
ArgOrKeyword::Keyword(kw) => match kw.arg.as_deref() {
|
||||
Some("mode" | "buffering" | "encoding" | "errors" | "newline") => {
|
||||
Some(locator.slice(kw))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
|
||||
let open_args = itertools::join(
|
||||
call.arguments
|
||||
.arguments_source_order()
|
||||
.enumerate()
|
||||
.filter_map(|(i, arg)| args(i, arg)),
|
||||
", ",
|
||||
);
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, file) {
|
||||
format!("{file_code}.open({open_args})")
|
||||
} else {
|
||||
format!("{binding}({file_code}).open({open_args})")
|
||||
};
|
||||
|
||||
let range = call.range();
|
||||
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub(crate) use builtin_open::*;
|
||||
pub(crate) use glob_rule::*;
|
||||
pub(crate) use invalid_pathlib_with_suffix::*;
|
||||
pub(crate) use os_chmod::*;
|
||||
@@ -29,6 +30,7 @@ pub(crate) use os_unlink::*;
|
||||
pub(crate) use path_constructor_current_directory::*;
|
||||
pub(crate) use replaceable_by_pathlib::*;
|
||||
|
||||
mod builtin_open;
|
||||
mod glob_rule;
|
||||
mod invalid_pathlib_with_suffix;
|
||||
mod os_chmod;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
has_unknown_keywords_or_starred_expr, is_pathlib_path_call,
|
||||
check_os_pathlib_single_arg_calls, has_unknown_keywords_or_starred_expr,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
@@ -75,43 +73,17 @@ pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("path", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_code = checker.locator().slice(arg.range());
|
||||
let range = call.range();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(OsPathAbspath, call.func.range());
|
||||
|
||||
if has_unknown_keywords_or_starred_expr(&call.arguments, &["path"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !is_fix_os_path_abspath_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.resolve()")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).resolve()")
|
||||
};
|
||||
|
||||
Ok(Fix::unsafe_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
))
|
||||
});
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"resolve()",
|
||||
"path",
|
||||
is_fix_os_path_abspath_enabled(checker.settings()),
|
||||
OsPathAbspath,
|
||||
Some(Applicability::Unsafe),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
@@ -35,7 +36,10 @@ use crate::{FixAvailability, Violation};
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
/// This rule's fix is always marked as unsafe because the behaviors of
|
||||
/// `os.path.expanduser` and `Path.expanduser` differ when a user's home
|
||||
/// directory can't be resolved: `os.path.expanduser` returns the
|
||||
/// input unchanged, while `Path.expanduser` raises `RuntimeError`.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
@@ -71,6 +75,6 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
|
||||
"path",
|
||||
is_fix_os_path_expanduser_enabled(checker.settings()),
|
||||
OsPathExpanduser,
|
||||
None,
|
||||
Some(Applicability::Unsafe),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Expr, ExprBooleanLiteral, ExprCall};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -7,7 +7,7 @@ use crate::rules::flake8_use_pathlib::helpers::{
|
||||
};
|
||||
use crate::rules::flake8_use_pathlib::{
|
||||
rules::Glob,
|
||||
violations::{BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, PyPath},
|
||||
violations::{Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, PyPath},
|
||||
};
|
||||
|
||||
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
@@ -60,42 +60,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
),
|
||||
// PTH122
|
||||
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range),
|
||||
// PTH123
|
||||
["" | "builtins", "open"] => {
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they
|
||||
// are set to non-default values.
|
||||
// https://github.com/astral-sh/ruff/issues/7620
|
||||
// Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open):
|
||||
// ```text
|
||||
// 0 1 2 3 4 5
|
||||
// open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None,
|
||||
// 6 7
|
||||
// closefd=True, opener=None)
|
||||
// ^^^^ ^^^^
|
||||
// ```
|
||||
// For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open):
|
||||
// ```text
|
||||
// Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
|
||||
// ```
|
||||
if call
|
||||
.arguments
|
||||
.find_argument_value("closefd", 6)
|
||||
.is_some_and(|expr| {
|
||||
!matches!(
|
||||
expr,
|
||||
Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. })
|
||||
)
|
||||
})
|
||||
|| is_argument_non_default(&call.arguments, "opener", 7)
|
||||
|| call
|
||||
.arguments
|
||||
.find_argument_value("file", 0)
|
||||
.is_some_and(|expr| is_file_descriptor(expr, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(BuiltinOpen, range)
|
||||
}
|
||||
// PTH124
|
||||
["py", "path", "local"] => checker.report_diagnostic_if_enabled(PyPath, range),
|
||||
// PTH207
|
||||
@@ -151,10 +115,3 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns `true` if argument `name` is set to a non-default `None` value.
|
||||
fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usize) -> bool {
|
||||
arguments
|
||||
.find_argument_value(name, position)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:8:1
|
||||
|
|
||||
6 | r_plus = "r+"
|
||||
7 |
|
||||
8 | builtins.open(file=_file)
|
||||
| ^^^^^^^^^^^^^
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:10:1
|
||||
|
|
||||
8 | builtins.open(file=_file)
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
| ^^^^
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:11:1
|
||||
|
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
| ^^^^
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:12:1
|
||||
|
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
| ^^^^
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:13:1
|
||||
|
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
| ^^^^
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:14:1
|
||||
|
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
| ^^^^
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:15:1
|
||||
|
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
| ^^^^
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:16:1
|
||||
|
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
| ^^^^
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:17:1
|
||||
|
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
| ^^^^
|
||||
18 |
|
||||
19 | # Only diagnostic
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:20:1
|
||||
|
|
||||
19 | # Only diagnostic
|
||||
20 | open()
|
||||
| ^^^^
|
||||
21 | open(_file, *_x)
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:21:1
|
||||
|
|
||||
19 | # Only diagnostic
|
||||
20 | open()
|
||||
21 | open(_file, *_x)
|
||||
| ^^^^
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
23 | open(_file, "r+", closefd=False)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:22:1
|
||||
|
|
||||
20 | open()
|
||||
21 | open(_file, *_x)
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
| ^^^^
|
||||
23 | open(_file, "r+", closefd=False)
|
||||
24 | open(_file, "r+", None, None, None, None, None, None, None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
@@ -305,6 +305,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:34:1
|
||||
@@ -316,6 +317,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
--> full_name.py:35:1
|
||||
@@ -360,6 +362,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:47:1
|
||||
@@ -370,6 +373,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
| ^^^^
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:65:1
|
||||
@@ -381,6 +385,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:67:1
|
||||
@@ -392,6 +397,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
68 |
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:71:1
|
||||
@@ -403,6 +409,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
72 |
|
||||
73 | # https://github.com/astral-sh/ruff/issues/17693
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
--> full_name.py:108:1
|
||||
|
||||
@@ -305,6 +305,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> import_from.py:36:1
|
||||
@@ -314,6 +315,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
36 | open(p).close()
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> import_from.py:43:10
|
||||
@@ -323,6 +325,7 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
43 | with open(p) as _: ... # Error
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
--> import_from.py:53:1
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:8:1
|
||||
|
|
||||
6 | r_plus = "r+"
|
||||
7 |
|
||||
8 | builtins.open(file=_file)
|
||||
| ^^^^^^^^^^^^^
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
5 | _x = ("r+", -1)
|
||||
6 | r_plus = "r+"
|
||||
7 |
|
||||
- builtins.open(file=_file)
|
||||
8 + Path(_file).open()
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:10:1
|
||||
|
|
||||
8 | builtins.open(file=_file)
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
| ^^^^
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
7 |
|
||||
8 | builtins.open(file=_file)
|
||||
9 |
|
||||
- open(_file, "r+ ", - 1)
|
||||
10 + Path(_file).open("r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:11:1
|
||||
|
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
| ^^^^
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
8 | builtins.open(file=_file)
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
- open(mode="wb", file=_file)
|
||||
11 + Path(_file).open(mode="wb")
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:12:1
|
||||
|
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
| ^^^^
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
9 |
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
- open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
12 + Path(_file).open(mode="r+", buffering=-1, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:13:1
|
||||
|
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
| ^^^^
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
10 | open(_file, "r+ ", - 1)
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
- open(_file, "r+", - 1, None, None, None, True, None)
|
||||
13 + Path(_file).open("r+", - 1, None, None, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:14:1
|
||||
|
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
| ^^^^
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
11 | open(mode="wb", file=_file)
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
- open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
14 + Path(_file).open("r+", -1, None, None, None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:15:1
|
||||
|
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
| ^^^^
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
12 | open(mode="r+", buffering=-1, file=_file, encoding="utf-8")
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
- open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
15 + Path(_file).open(mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
18 |
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:16:1
|
||||
|
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
| ^^^^
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
13 | open(_file, "r+", - 1, None, None, None, True, None)
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
- open(_file, f" {r_plus} ", - 1)
|
||||
16 + Path(_file).open(f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
18 |
|
||||
19 | # Only diagnostic
|
||||
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:17:1
|
||||
|
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
17 | open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
| ^^^^
|
||||
18 |
|
||||
19 | # Only diagnostic
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
14 | open(_file, "r+", -1, None, None, None, closefd=True, opener=None)
|
||||
15 | open(_file, mode="r+", buffering=-1, encoding=None, errors=None, newline=None)
|
||||
16 | open(_file, f" {r_plus} ", - 1)
|
||||
- open(buffering=- 1, file=_file, encoding= "utf-8")
|
||||
17 + Path(_file).open(buffering=- 1, encoding= "utf-8")
|
||||
18 |
|
||||
19 | # Only diagnostic
|
||||
20 | open()
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:20:1
|
||||
|
|
||||
19 | # Only diagnostic
|
||||
20 | open()
|
||||
| ^^^^
|
||||
21 | open(_file, *_x)
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:21:1
|
||||
|
|
||||
19 | # Only diagnostic
|
||||
20 | open()
|
||||
21 | open(_file, *_x)
|
||||
| ^^^^
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
23 | open(_file, "r+", closefd=False)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
--> PTH123.py:22:1
|
||||
|
|
||||
20 | open()
|
||||
21 | open(_file, *_x)
|
||||
22 | open(_file, "r+", unknown=True)
|
||||
| ^^^^
|
||||
23 | open(_file, "r+", closefd=False)
|
||||
24 | open(_file, "r+", None, None, None, None, None, None, None)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
@@ -260,6 +260,7 @@ help: Replace with `Path(...).expanduser()`
|
||||
20 | bbb = os.path.isdir(p)
|
||||
21 | bbbb = os.path.isfile(p)
|
||||
22 | bbbbb = os.path.islink(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
--> full_name.py:19:7
|
||||
@@ -519,7 +520,7 @@ PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, an
|
||||
33 | fp.read()
|
||||
|
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:32:6
|
||||
|
|
||||
30 | os.path.samefile(p)
|
||||
@@ -529,8 +530,24 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
30 | os.path.dirname(p)
|
||||
31 | os.path.samefile(p)
|
||||
32 | os.path.splitext(p)
|
||||
- with open(p) as fp:
|
||||
33 + with pathlib.Path(p).open() as fp:
|
||||
34 | fp.read()
|
||||
35 | open(p).close()
|
||||
36 | os.getcwdb(p)
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:34:1
|
||||
|
|
||||
32 | with open(p) as fp:
|
||||
@@ -540,6 +557,22 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
32 | os.path.splitext(p)
|
||||
33 | with open(p) as fp:
|
||||
34 | fp.read()
|
||||
- open(p).close()
|
||||
35 + pathlib.Path(p).open().close()
|
||||
36 | os.getcwdb(p)
|
||||
37 | os.path.join(p, *q)
|
||||
38 | os.sep.join(p, *q)
|
||||
|
||||
PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
--> full_name.py:35:1
|
||||
@@ -574,7 +607,7 @@ PTH118 `os.sep.join()` should be replaced by `Path.joinpath()`
|
||||
39 | # https://github.com/astral-sh/ruff/issues/7620
|
||||
|
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:46:1
|
||||
|
|
||||
44 | open(p, closefd=False)
|
||||
@@ -584,8 +617,24 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
44 |
|
||||
45 | open(p, closefd=False)
|
||||
46 | open(p, opener=opener)
|
||||
- open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
47 + pathlib.Path(p).open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
|
||||
48 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
49 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
50 |
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:47:1
|
||||
|
|
||||
45 | open(p, opener=opener)
|
||||
@@ -594,8 +643,24 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
| ^^^^
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
45 | open(p, closefd=False)
|
||||
46 | open(p, opener=opener)
|
||||
47 | open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
- open(p, 'r', - 1, None, None, None, True, None)
|
||||
48 + pathlib.Path(p).open('r', - 1, None, None, None)
|
||||
49 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
50 |
|
||||
51 | # Cannot be upgraded `pathlib.Open` does not support fds
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:65:1
|
||||
|
|
||||
63 | open(f())
|
||||
@@ -605,8 +670,24 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
63 | return 1
|
||||
64 | open(f())
|
||||
65 |
|
||||
- open(b"foo")
|
||||
66 + pathlib.Path(b"foo").open()
|
||||
67 | byte_str = b"bar"
|
||||
68 | open(byte_str)
|
||||
69 |
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:67:1
|
||||
|
|
||||
65 | open(b"foo")
|
||||
@@ -616,8 +697,24 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
68 |
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
65 |
|
||||
66 | open(b"foo")
|
||||
67 | byte_str = b"bar"
|
||||
- open(byte_str)
|
||||
68 + pathlib.Path(byte_str).open()
|
||||
69 |
|
||||
70 | def bytes_str_func() -> bytes:
|
||||
71 | return b"foo"
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> full_name.py:71:1
|
||||
|
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
@@ -627,6 +724,22 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
72 |
|
||||
73 | # https://github.com/astral-sh/ruff/issues/17693
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
1 | import os
|
||||
2 | import os.path
|
||||
3 + import pathlib
|
||||
4 |
|
||||
5 | p = "/foo"
|
||||
6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
69 |
|
||||
70 | def bytes_str_func() -> bytes:
|
||||
71 | return b"foo"
|
||||
- open(bytes_str_func())
|
||||
72 + pathlib.Path(bytes_str_func()).open()
|
||||
73 |
|
||||
74 | # https://github.com/astral-sh/ruff/issues/17693
|
||||
75 | os.stat(1)
|
||||
|
||||
PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
--> full_name.py:108:1
|
||||
|
||||
@@ -260,6 +260,7 @@ help: Replace with `Path(...).expanduser()`
|
||||
20 | bbb = foo_p.isdir(p)
|
||||
21 | bbbb = foo_p.isfile(p)
|
||||
22 | bbbbb = foo_p.islink(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
--> import_as.py:19:7
|
||||
|
||||
@@ -268,6 +268,7 @@ help: Replace with `Path(...).expanduser()`
|
||||
22 | bbb = isdir(p)
|
||||
23 | bbbb = isfile(p)
|
||||
24 | bbbbb = islink(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
--> import_from.py:21:7
|
||||
@@ -534,7 +535,7 @@ PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, an
|
||||
35 | fp.read()
|
||||
|
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> import_from.py:34:6
|
||||
|
|
||||
32 | samefile(p)
|
||||
@@ -544,8 +545,25 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 + import pathlib
|
||||
6 |
|
||||
7 | p = "/foo"
|
||||
8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
32 | dirname(p)
|
||||
33 | samefile(p)
|
||||
34 | splitext(p)
|
||||
- with open(p) as fp:
|
||||
35 + with pathlib.Path(p).open() as fp:
|
||||
36 | fp.read()
|
||||
37 | open(p).close()
|
||||
38 |
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> import_from.py:36:1
|
||||
|
|
||||
34 | with open(p) as fp:
|
||||
@@ -553,8 +571,25 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
36 | open(p).close()
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 + import pathlib
|
||||
6 |
|
||||
7 | p = "/foo"
|
||||
8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
34 | splitext(p)
|
||||
35 | with open(p) as fp:
|
||||
36 | fp.read()
|
||||
- open(p).close()
|
||||
37 + pathlib.Path(p).open().close()
|
||||
38 |
|
||||
39 |
|
||||
40 | # https://github.com/astral-sh/ruff/issues/15442
|
||||
|
||||
PTH123 `open()` should be replaced by `Path.open()`
|
||||
PTH123 [*] `open()` should be replaced by `Path.open()`
|
||||
--> import_from.py:43:10
|
||||
|
|
||||
41 | from builtins import open
|
||||
@@ -562,6 +597,23 @@ PTH123 `open()` should be replaced by `Path.open()`
|
||||
43 | with open(p) as _: ... # Error
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Path.open()`
|
||||
2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 + import pathlib
|
||||
6 |
|
||||
7 | p = "/foo"
|
||||
8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
41 | def _():
|
||||
42 | from builtins import open
|
||||
43 |
|
||||
- with open(p) as _: ... # Error
|
||||
44 + with pathlib.Path(p).open() as _: ... # Error
|
||||
45 |
|
||||
46 |
|
||||
47 | def _():
|
||||
|
||||
PTH104 [*] `os.rename()` should be replaced by `Path.rename()`
|
||||
--> import_from.py:53:1
|
||||
|
||||
@@ -268,6 +268,7 @@ help: Replace with `Path(...).expanduser()`
|
||||
27 | bbb = xisdir(p)
|
||||
28 | bbbb = xisfile(p)
|
||||
29 | bbbbb = xislink(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
--> import_from_as.py:26:7
|
||||
|
||||
@@ -174,50 +174,6 @@ impl Violation for OsPathSplitext {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `open()` builtin.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation. When possible,
|
||||
/// using `Path` object methods such as `Path.open()` can improve readability
|
||||
/// over the `open` builtin.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// with open("f1.py", "wb") as fp:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// with Path("f1.py").open("wb") as fp:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than working directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BuiltinOpen;
|
||||
|
||||
impl Violation for BuiltinOpen {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`open()` should be replaced by `Path.open()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `py.path` library.
|
||||
///
|
||||
|
||||
@@ -642,7 +642,7 @@ mod tests {
|
||||
#[test_case(Path::new("order_by_type.py"))]
|
||||
fn order_by_type(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("order_by_type_false_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -653,7 +653,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -664,7 +663,7 @@ mod tests {
|
||||
"order_by_type_with_custom_classes_{}",
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -681,7 +680,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -692,7 +690,7 @@ mod tests {
|
||||
"order_by_type_with_custom_constants_{}",
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -711,7 +709,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -722,7 +719,7 @@ mod tests {
|
||||
"order_by_type_with_custom_variables_{}",
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -739,7 +736,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -749,7 +745,7 @@ mod tests {
|
||||
#[test_case(Path::new("force_sort_within_sections_future.py"))]
|
||||
fn force_sort_within_sections(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("force_sort_within_sections_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -761,7 +757,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -769,7 +764,7 @@ mod tests {
|
||||
#[test_case(Path::new("force_sort_within_sections_lines_between.py"))]
|
||||
fn force_sort_within_sections_lines_between(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("force_sort_within_sections_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -781,7 +776,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1114,7 +1108,7 @@ mod tests {
|
||||
#[test_case(Path::new("no_lines_before.py"))]
|
||||
fn no_lines_before(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("no_lines_before.py_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -1131,7 +1125,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1142,7 +1135,7 @@ mod tests {
|
||||
"no_lines_before_with_empty_sections.py_{}",
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -1156,7 +1149,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1167,7 +1159,7 @@ mod tests {
|
||||
#[test_case(Path::new("lines_after_imports_class_after.py"))]
|
||||
fn lines_after_imports(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("lines_after_imports_{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -1178,7 +1170,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1188,7 +1179,7 @@ mod tests {
|
||||
#[test_case(Path::new("lines_after_imports_class_after.py"))]
|
||||
fn lines_after_imports_default_settings(path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy();
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
@@ -1199,7 +1190,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(&*snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1207,7 +1197,7 @@ mod tests {
|
||||
#[test_case(Path::new("lines_between_types.py"))]
|
||||
fn lines_between_types(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("lines_between_types{}", path.to_string_lossy());
|
||||
let mut diagnostics = test_path(
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
isort: super::settings::Settings {
|
||||
@@ -1218,7 +1208,6 @@ mod tests {
|
||||
..LinterSettings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -125,7 +125,8 @@ fn add_required_import(
|
||||
TextRange::default(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(
|
||||
Importer::new(parsed, locator, stylist).add_import(required_import, TextSize::default()),
|
||||
Importer::new(parsed, locator.contents(), stylist)
|
||||
.add_import(required_import, TextSize::default()),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -836,7 +836,10 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
// Allow groups of one-liners.
|
||||
&& !(state.follows.is_any_def() && line.last_token != TokenKind::Colon)
|
||||
&& !state.follows.follows_def_with_dummy_body()
|
||||
// Only for class scope: we must be inside a class block
|
||||
&& matches!(state.class_status, Status::Inside(_))
|
||||
// But NOT inside a function body; nested defs inside methods are handled by E306
|
||||
&& matches!(state.fn_status, Status::Outside | Status::CommentAfter(_))
|
||||
// The class/parent method's docstring can directly precede the def.
|
||||
// Allow following a decorator (if there is an error it will be triggered on the first decorator).
|
||||
&& !matches!(state.follows, Follows::Docstring | Follows::Decorator)
|
||||
|
||||
@@ -168,11 +168,11 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
{
|
||||
let (trailing, trailing_len) = line.trailing_whitespace(token);
|
||||
if !matches!(trailing, Whitespace::None) {
|
||||
let range = TextRange::at(token.end(), trailing_len);
|
||||
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(
|
||||
WhitespaceAfterOpenBracket { symbol },
|
||||
TextRange::at(token.end(), trailing_len),
|
||||
range,
|
||||
) {
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||
}
|
||||
}
|
||||
@@ -184,11 +184,11 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) =
|
||||
line.leading_whitespace(token)
|
||||
{
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforeCloseBracket { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
) {
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||
}
|
||||
}
|
||||
@@ -210,13 +210,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
// If we're in the second half of a double colon, disallow
|
||||
// any whitespace (e.g., `foo[1: :2]` or `foo[1 : : 2]`).
|
||||
if matches!(prev_token, Some(TokenKind::Colon)) {
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context
|
||||
.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforePunctuation { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
)
|
||||
{
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic
|
||||
.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||
}
|
||||
@@ -227,13 +227,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
// Or `foo[index :, 2]`, but not `foo[index :, 2]`.
|
||||
if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace
|
||||
{
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context
|
||||
.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforePunctuation { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
)
|
||||
{
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(
|
||||
Edit::range_deletion(range),
|
||||
));
|
||||
@@ -255,13 +255,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
// whitespace before the colon and so should the fix
|
||||
if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace
|
||||
{
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context
|
||||
.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforePunctuation { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
)
|
||||
{
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edits(
|
||||
Edit::range_deletion(range),
|
||||
[Edit::insertion(
|
||||
@@ -278,13 +278,13 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
.filter(|next| matches!(next.kind(), TokenKind::Colon))
|
||||
.unwrap_or(&token);
|
||||
if line.trailing_whitespace(token) != whitespace {
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context
|
||||
.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforePunctuation { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
)
|
||||
{
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(
|
||||
Edit::range_deletion(range),
|
||||
));
|
||||
@@ -299,11 +299,11 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
// Avoid removing any whitespace for f-string debug expressions.
|
||||
continue;
|
||||
}
|
||||
let range = TextRange::at(token.start() - offset, offset);
|
||||
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(
|
||||
WhitespaceBeforePunctuation { symbol },
|
||||
TextRange::at(token.start() - offset, offset),
|
||||
range,
|
||||
) {
|
||||
let range = diagnostic.expect_range();
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,3 +94,22 @@ help: Add missing blank line
|
||||
527 | def bar(self, x: int | str) -> int | str:
|
||||
528 | return x
|
||||
529 | # end
|
||||
|
||||
E301 [*] Expected 1 blank line, found 0
|
||||
--> E30.py:993:9
|
||||
|
|
||||
991 | if True:
|
||||
992 | print("conditional")
|
||||
993 | def test():
|
||||
| ^^^
|
||||
994 | pass
|
||||
995 | # end
|
||||
|
|
||||
help: Add missing blank line
|
||||
990 | class Foo:
|
||||
991 | if True:
|
||||
992 | print("conditional")
|
||||
993 +
|
||||
994 | def test():
|
||||
995 | pass
|
||||
996 | # end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user