Compare commits
186 Commits
charlie/cl
...
v0.0.290
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c030b5bf3 | ||
|
|
1b082ce67e | ||
|
|
f936d319cc | ||
|
|
85d8b6228f | ||
|
|
7594dadc1d | ||
|
|
de37fbfac9 | ||
|
|
4e2769a16c | ||
|
|
75b5c314e3 | ||
|
|
450fb9b99a | ||
|
|
6163c99551 | ||
|
|
3112202a5b | ||
|
|
64ea00048b | ||
|
|
067a4acd54 | ||
|
|
c88376f468 | ||
|
|
9b7c29853d | ||
|
|
3e21d32b79 | ||
|
|
f9e3ea23ba | ||
|
|
6856d0b44b | ||
|
|
21539f1663 | ||
|
|
b9bb6bf780 | ||
|
|
d39eae2713 | ||
|
|
5d21b9c22e | ||
|
|
ec2f229a45 | ||
|
|
34c1cb7d11 | ||
|
|
2ddea7c657 | ||
|
|
45eabdd2c3 | ||
|
|
675c86c175 | ||
|
|
1f8e2b8f14 | ||
|
|
58b3040342 | ||
|
|
6a9b8aede1 | ||
|
|
f48126ad00 | ||
|
|
11287f944f | ||
|
|
a65efcf459 | ||
|
|
04183b0299 | ||
|
|
36fa1fe359 | ||
|
|
6e625bd93d | ||
|
|
ebd1b296fd | ||
|
|
1373e1c395 | ||
|
|
4bff397318 | ||
|
|
5347df4728 | ||
|
|
ebe9c03545 | ||
|
|
b0cbcd3dfa | ||
|
|
4df9e07a79 | ||
|
|
f0f7ea7502 | ||
|
|
4f26002dd5 | ||
|
|
d1a9c198e3 | ||
|
|
7a4f699fba | ||
|
|
3fb5418c2c | ||
|
|
9fcc009a0c | ||
|
|
bf8e5a167b | ||
|
|
8a001dfc3d | ||
|
|
0823394525 | ||
|
|
e15047815c | ||
|
|
7531bb3b21 | ||
|
|
2d9b39871f | ||
|
|
e122a96d27 | ||
|
|
f4c7bff36b | ||
|
|
56440ad835 | ||
|
|
179128dc54 | ||
|
|
e7a2779402 | ||
|
|
008da95b29 | ||
|
|
5d4dd3e38e | ||
|
|
e561f5783b | ||
|
|
ee0f1270cf | ||
|
|
e7b7e4a18d | ||
|
|
b4419c34ea | ||
|
|
08f19226b9 | ||
|
|
1e6df19a35 | ||
|
|
c21b960fc7 | ||
|
|
73ad2affa1 | ||
|
|
40c936922e | ||
|
|
874db4fb86 | ||
|
|
a41bb2733f | ||
|
|
24b848a4ea | ||
|
|
773ba5f816 | ||
|
|
f5701fcc63 | ||
|
|
ff0feb191c | ||
|
|
6566d00295 | ||
|
|
7c9bbcf4e2 | ||
|
|
babf8d718e | ||
|
|
878813f277 | ||
|
|
71c8a02ebd | ||
|
|
3a2c3a7398 | ||
|
|
7440e54ec6 | ||
|
|
0357e801ed | ||
|
|
69d0caabe7 | ||
|
|
9cb5ce750e | ||
|
|
0a07a2ca62 | ||
|
|
47a253fb62 | ||
|
|
d9544a2d37 | ||
|
|
41f0aad7b3 | ||
|
|
05951dd338 | ||
|
|
c260762900 | ||
|
|
1d5c4b0a14 | ||
|
|
a352f2f092 | ||
|
|
e376c3ff7e | ||
|
|
9671922e40 | ||
|
|
45f9fca228 | ||
|
|
6661be2c30 | ||
|
|
97f945651d | ||
|
|
5cea43731e | ||
|
|
7971e0b0ee | ||
|
|
842ff0212e | ||
|
|
f1a4eb9c28 | ||
|
|
041cdb95e0 | ||
|
|
fda48afc23 | ||
|
|
c1af1c291d | ||
|
|
171b66cb43 | ||
|
|
fa0b6f4813 | ||
|
|
eab85aea1a | ||
|
|
5b31524920 | ||
|
|
6d0469638c | ||
|
|
29ba2bb943 | ||
|
|
f0ea40a68d | ||
|
|
a3a531e0d4 | ||
|
|
b3e8eca871 | ||
|
|
447b7cb0e2 | ||
|
|
e3114a144c | ||
|
|
2e58ad437e | ||
|
|
04f2842e4f | ||
|
|
31990b8d3f | ||
|
|
5f59101811 | ||
|
|
fa6bff0078 | ||
|
|
ea7c394817 | ||
|
|
264d9159f8 | ||
|
|
37dfb205b1 | ||
|
|
f8e4e1d562 | ||
|
|
89be850b73 | ||
|
|
d68041ba24 | ||
|
|
b60b37e866 | ||
|
|
5a95edab45 | ||
|
|
1adde24133 | ||
|
|
e02d76f070 | ||
|
|
7ead2c17b1 | ||
|
|
e428099e4c | ||
|
|
7a83fd9926 | ||
|
|
e8f78fa2cf | ||
|
|
955501f267 | ||
|
|
175b3702c3 | ||
|
|
40ee4909b5 | ||
|
|
10a8e4a225 | ||
|
|
0465b03282 | ||
|
|
154fe7bdcc | ||
|
|
ece30e7c69 | ||
|
|
7be28a38c5 | ||
|
|
5ec73a6137 | ||
|
|
1067261a55 | ||
|
|
93ca8ebbc0 | ||
|
|
834566f34f | ||
|
|
a56121672c | ||
|
|
32f4a96c64 | ||
|
|
c004e03395 | ||
|
|
b57ddd54d2 | ||
|
|
af189db5eb | ||
|
|
c7217e34d7 | ||
|
|
d70247959c | ||
|
|
911d4f2918 | ||
|
|
d9cf31f355 | ||
|
|
7da99cc756 | ||
|
|
9c3b2c3c3c | ||
|
|
37d244d178 | ||
|
|
dbb34804a4 | ||
|
|
3c7486817b | ||
|
|
3c3c5b5c57 | ||
|
|
b0d171ac19 | ||
|
|
b70dde4a77 | ||
|
|
4eaa412370 | ||
|
|
577280c8be | ||
|
|
45680bbb44 | ||
|
|
71ff6f911d | ||
|
|
c05e4628b1 | ||
|
|
2f3a950f6f | ||
|
|
dea65536e9 | ||
|
|
fbc9b5a604 | ||
|
|
253a241f5d | ||
|
|
33806b8b7c | ||
|
|
afcd00da56 | ||
|
|
08e246764f | ||
|
|
0489bbc54c | ||
|
|
60132da7bb | ||
|
|
17a44c0078 | ||
|
|
376d3caf47 | ||
|
|
51d69b448c | ||
|
|
f7dca3d958 | ||
|
|
7c1aa98f43 | ||
|
|
68f605e80a |
@@ -10,7 +10,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py}]
|
||||
[*.{rs,py,pyi}]
|
||||
indent_size = 4
|
||||
|
||||
[*.snap]
|
||||
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -4,8 +4,10 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "12:00"
|
||||
timezone: "America/New_York"
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
labels: ["internal"]
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels: ["internal"]
|
||||
|
||||
3
.github/release.yml
vendored
3
.github/release.yml
vendored
@@ -20,6 +20,9 @@ changelog:
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Preview
|
||||
labels:
|
||||
- preview
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
30
.github/workflows/ci.yaml
vendored
30
.github/workflows/ci.yaml
vendored
@@ -26,11 +26,11 @@ jobs:
|
||||
linter: ${{ steps.changed.outputs.linter_any_changed }}
|
||||
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v38
|
||||
- uses: tj-actions/changed-files@v39
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
- run: cargo fmt --all --check
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "cargo test | ${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo insta"
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo fuzz"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
name: "test scripts"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: github.event_name == 'pull_request' && needs.determine_changes.outputs.linter == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -227,7 +227,7 @@ jobs:
|
||||
name: "cargo udeps"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install nightly Rust toolchain"
|
||||
# Only pinned to make caching work, update freely
|
||||
run: rustup toolchain install nightly-2023-06-08
|
||||
@@ -241,7 +241,7 @@ jobs:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
name: "pre-commit"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Cache rust"
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
14
.github/workflows/docs.yaml
vendored
14
.github/workflows/docs.yaml
vendored
@@ -2,6 +2,11 @@ name: mkdocs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
|
||||
default: ""
|
||||
type: string
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@@ -12,7 +17,9 @@ jobs:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -40,8 +47,9 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.1.0
|
||||
uses: cloudflare/wrangler-action@v3.1.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages publish site --project-name=ruff-docs --branch ${GITHUB_HEAD_REF} --commit-hash ${GITHUB_SHA}
|
||||
# `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
|
||||
command: pages deploy site --project-name=ruff-docs --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
14
.github/workflows/flake8-to-ruff.yaml
vendored
14
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
matrix:
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
arch: armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
4
.github/workflows/playground.yaml
vendored
4
.github/workflows/playground.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.1.0
|
||||
uses: cloudflare/wrangler-action@v3.1.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
54
.github/workflows/release.yaml
vendored
54
.github/workflows/release.yaml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
|
||||
type: string
|
||||
sha:
|
||||
description: "Optionally, the full sha of the commit to be released"
|
||||
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
|
||||
default: ""
|
||||
type: string
|
||||
pull_request:
|
||||
paths:
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work
|
||||
- pyproject.toml
|
||||
# And when we change this workflow itself...
|
||||
- .github/workflows/release.yaml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -30,7 +33,9 @@ jobs:
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -56,7 +61,9 @@ jobs:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -94,7 +101,9 @@ jobs:
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -140,7 +149,9 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -186,7 +197,9 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -243,7 +256,9 @@ jobs:
|
||||
arch: ppc64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -296,7 +311,9 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -350,7 +367,9 @@ jobs:
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@@ -398,7 +417,9 @@ jobs:
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
@@ -410,6 +431,15 @@ jobs:
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
- name: Check main branch
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
# Fetch the main branch since a shallow checkout is used by default
|
||||
git fetch origin main --unshallow
|
||||
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check SHA consistency
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
@@ -464,7 +494,9 @@ jobs:
|
||||
# For git tag
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.sha }}
|
||||
- name: git tag
|
||||
run: |
|
||||
git config user.email "hey@astral.sh"
|
||||
|
||||
@@ -23,8 +23,6 @@ repos:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs
|
||||
- mdformat-black
|
||||
- black==23.1.0 # Must be the latest version of Black
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.33.0
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.288
|
||||
|
||||
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
|
||||
|
||||
Previously, Ruff supported the non-standard compliant emoji identifiers e.g. `📦 = 1`.
|
||||
We decided to remove this non-standard language extension, and Ruff now reports syntax errors for emoji identifiers in your code, the same as CPython.
|
||||
|
||||
### Improved GitLab fingerprints ([#7203](https://github.com/astral-sh/ruff/pull/7203))
|
||||
|
||||
GitLab uses fingerprints to identify new, existing, or fixed violations. Previously, Ruff included the violation's position in the fingerprint. Using the location has the downside that changing any code before the violation causes the fingerprint to change, resulting in GitLab reporting one fixed and one new violation even though it is a pre-existing violation.
|
||||
|
||||
Ruff now uses a more stable location-agnostic fingerprint to minimize that existing violations incorrectly get marked as fixed and re-reported as new violations.
|
||||
|
||||
Expect GitLab to report each pre-existing violation in your project as fixed and a new violation in your Ruff upgrade PR.
|
||||
|
||||
## 0.0.283 / 0.284
|
||||
|
||||
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
|
||||
|
||||
@@ -129,6 +129,7 @@ At time of writing, the repository includes the following crates:
|
||||
intermediate representation. The backend for `ruff_python_formatter`.
|
||||
- `crates/ruff_index`: library crate inspired by `rustc_index`.
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_notebook`: library crate for parsing and manipulating Jupyter notebooks.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
|
||||
798
Cargo.lock
generated
798
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
30
Cargo.toml
@@ -14,47 +14,49 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.3", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.31.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.2.2" }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
memchr = "2.6.3"
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.0.14" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
regex = { version = "1.9.5" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93" }
|
||||
serde_json = { version = "1.0.106" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1", features = ["inline"] }
|
||||
smallvec = { version = "1.10.0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
syn = { version = "2.0.15" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.33" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
thiserror = { version = "1.0.48" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = "1.0.11"
|
||||
unicode-width = "0.1.10"
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f05707e50703b49fe3dd860aa839", default-features = false }
|
||||
libcst = { version = "0.1.0", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -1224,6 +1224,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-logging, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Adam Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- Pyright, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.286
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -274,6 +274,7 @@ quality tools, including:
|
||||
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
|
||||
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
|
||||
- [flake8-pie](https://pypi.org/project/flake8-pie/)
|
||||
@@ -398,6 +399,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Pylint](https://github.com/PyCQA/pylint)
|
||||
- [Reflex](https://github.com/reflex-dev/reflex)
|
||||
- [Rippling](https://rippling.com)
|
||||
- [Robyn](https://github.com/sansyrox/robyn)
|
||||
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
|
||||
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.286"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::registry::Linter;
|
||||
use ruff::settings::types::PreviewMode;
|
||||
use ruff::RuleSelector;
|
||||
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
@@ -331,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
|
||||
.filter(|plugin| {
|
||||
for selector in selectors {
|
||||
if selector
|
||||
.into_iter()
|
||||
.rules(PreviewMode::Disabled)
|
||||
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.286"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -18,6 +18,7 @@ name = "ruff"
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
@@ -55,7 +56,7 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.6.0" }
|
||||
pyproject-toml = { version = "0.7.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
@@ -64,17 +65,15 @@ schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { version = "3.0.0" }
|
||||
similar = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { version = "1.0.43" }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unicode-width = { workspace = true }
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_1.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_1.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Docstring followed by a newline
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_2.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_2.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Docstring followed by whitespace with no newline
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/7155
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_3.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_bugbear/B006_3.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Docstring with no newline
|
||||
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
"""
|
||||
"""
|
||||
@@ -308,3 +308,7 @@ def single_line_func_wrong(value: dict[str, str] = {
|
||||
def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
: \
|
||||
"""Docstring"""
|
||||
|
||||
|
||||
def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring without newline"""
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Should emit:
|
||||
B009 - Line 19, 20, 21, 22, 23, 24
|
||||
B010 - Line 40, 41, 42, 43, 44, 45
|
||||
B009 - Lines 19-31
|
||||
B010 - Lines 40-45
|
||||
"""
|
||||
|
||||
# Valid getattr usage
|
||||
@@ -24,6 +24,16 @@ getattr(foo, r"abc123")
|
||||
_ = lambda x: getattr(x, "bar")
|
||||
if getattr(x, "bar"):
|
||||
pass
|
||||
getattr(1, "real")
|
||||
getattr(1., "real")
|
||||
getattr(1.0, "real")
|
||||
getattr(1j, "real")
|
||||
getattr(True, "real")
|
||||
getattr(x := 1, "real")
|
||||
getattr(x + y, "real")
|
||||
getattr("foo"
|
||||
"bar", "real")
|
||||
|
||||
|
||||
# Valid setattr usage
|
||||
setattr(foo, bar, None)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
retriable_exceptions = (FileExistsError, FileNotFoundError)
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
@@ -6,3 +8,7 @@ except AttributeError:
|
||||
pass
|
||||
except (ImportError, TypeError):
|
||||
pass
|
||||
except (*retriable_exceptions,):
|
||||
pass
|
||||
except(ValueError,):
|
||||
pass
|
||||
|
||||
@@ -16,3 +16,6 @@ def f(x):
|
||||
return x
|
||||
|
||||
print(f'Hello {dict((x,f(x)) for x in "abc")} World')
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7086
|
||||
dict((k,v)for k,v in d.iteritems() if k in only_args)
|
||||
|
||||
@@ -11,3 +11,6 @@ f"{dict([(s,f(s)) for s in 'ab'])}"
|
||||
|
||||
f'{dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"])}'
|
||||
f'{ dict([(s,s) for s in "ab"]) | dict([(s,s) for s in "ab"]) }'
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7087
|
||||
saved.append(dict([(k, v)for k,v in list(unique_instance.__dict__.items()) if k in [f.name for f in unique_instance._meta.fields]]))
|
||||
|
||||
@@ -7,6 +7,13 @@ reversed(sorted(x, reverse=True))
|
||||
reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=False))
|
||||
reversed(sorted(x, reverse=x))
|
||||
reversed(sorted(x, reverse=not x))
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
reversed(sorted(i for i in range(42)))
|
||||
reversed(sorted((i for i in range(42)), reverse=True))
|
||||
|
||||
|
||||
def reversed(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@@ -7,6 +7,8 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{i for i in x}
|
||||
{k: v for k, v in y}
|
||||
{k: v for k, v in d.items()}
|
||||
[(k, v) for k, v in d.items()]
|
||||
{k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
[i for i, in z]
|
||||
[i for i, j in y]
|
||||
@@ -17,3 +19,6 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{k.foo: k for k in y}
|
||||
{k["foo"]: k for k in y}
|
||||
{k: v if v else None for k, v in y}
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
|
||||
@@ -5,11 +5,13 @@ map(lambda x: str(x), nums)
|
||||
list(map(lambda x: x * 2, nums))
|
||||
set(map(lambda x: x % 2 == 0, nums))
|
||||
dict(map(lambda v: (v, v**2), nums))
|
||||
dict(map(lambda v: [v, v**2], nums))
|
||||
map(lambda: "const", nums)
|
||||
map(lambda _: 3.0, nums)
|
||||
_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
all(map(lambda v: isinstance(v, dict), nums))
|
||||
filter(func, map(lambda v: v, nums))
|
||||
list(map(lambda x, y: x * y, nums))
|
||||
|
||||
# When inside f-string, then the fix should be surrounded by whitespace
|
||||
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
@@ -40,3 +42,8 @@ map(lambda **kwargs: len(kwargs), range(4))
|
||||
|
||||
# Ok because multiple arguments are allowed.
|
||||
dict(map(lambda k, v: (k, v), keys, values))
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7121
|
||||
map(lambda x: x, y if y else z)
|
||||
map(lambda x: x, (y if y else z))
|
||||
map(lambda x: x, (x, y, z))
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import logging
|
||||
|
||||
logging.Logger(__name__)
|
||||
logging.Logger()
|
||||
logging.getLogger(__name__)
|
||||
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import logging
|
||||
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
@@ -3,16 +3,20 @@ def bar():
|
||||
|
||||
|
||||
def foo():
|
||||
"""foo""" # OK
|
||||
"""foo""" # OK, docstrings are handled by another rule
|
||||
|
||||
|
||||
def buzz():
|
||||
print("buzz") # OK, not in stub file
|
||||
print("buzz") # ERROR PYI010
|
||||
|
||||
|
||||
def foo2():
|
||||
123 # OK, not in a stub file
|
||||
123 # ERROR PYI010
|
||||
|
||||
|
||||
def bizz():
|
||||
x = 123 # OK, not in a stub file
|
||||
x = 123 # ERROR PYI010
|
||||
|
||||
|
||||
def foo3():
|
||||
pass # OK, pass is handled by another rule
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
def bar(): ... # OK
|
||||
def foo():
|
||||
"""foo""" # OK, strings are handled by another rule
|
||||
"""foo""" # OK, docstrings are handled by another rule
|
||||
|
||||
def buzz():
|
||||
print("buzz") # ERROR PYI010
|
||||
@@ -10,3 +10,6 @@ def foo2():
|
||||
|
||||
def bizz():
|
||||
x = 123 # ERROR PYI010
|
||||
|
||||
def foo3():
|
||||
pass # OK, pass is handled by another rule
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
def bar(): # OK
|
||||
...
|
||||
def bar():
|
||||
... # OK
|
||||
|
||||
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
def bar():
|
||||
pass # OK
|
||||
|
||||
|
||||
def bar():
|
||||
"""oof""" # OK
|
||||
|
||||
|
||||
def oof(): # ERROR PYI048
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
|
||||
def foo(): # Ok not in Stub file
|
||||
def foo(): # ERROR PYI048
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
|
||||
def buzz(): # Ok not in Stub file
|
||||
def buzz(): # ERROR PYI048
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
def bar(): ... # OK
|
||||
def bar():
|
||||
... # OK
|
||||
pass # OK
|
||||
|
||||
def bar():
|
||||
"""oof""" # OK
|
||||
|
||||
def oof(): # OK, docstrings are handled by another rule
|
||||
"""oof"""
|
||||
print("foo")
|
||||
def oof(): # ERROR PYI048
|
||||
"""oof"""
|
||||
print("foo")
|
||||
|
||||
|
||||
|
||||
def foo(): # ERROR PYI048
|
||||
def foo(): # ERROR PYI048
|
||||
"""foo"""
|
||||
print("foo")
|
||||
print("foo")
|
||||
|
||||
|
||||
def buzz(): # ERROR PYI048
|
||||
def buzz(): # ERROR PYI048
|
||||
print("fizz")
|
||||
print("buzz")
|
||||
print("test")
|
||||
|
||||
@@ -52,3 +52,21 @@ def test_multiline():
|
||||
|
||||
x = 1; \
|
||||
assert something and something_else
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7143
|
||||
def test_parenthesized_not():
|
||||
assert not (
|
||||
self.find_graph_output(node.output[0])
|
||||
or self.find_graph_input(node.input[0])
|
||||
or self.find_graph_output(node.input[0])
|
||||
)
|
||||
|
||||
assert (not (
|
||||
self.find_graph_output(node.output[0])
|
||||
or self.find_graph_input(node.input[0])
|
||||
or self.find_graph_output(node.input[0])
|
||||
))
|
||||
|
||||
assert (not self.find_graph_output(node.output[0]) or
|
||||
self.find_graph_input(node.input[0]))
|
||||
|
||||
@@ -357,3 +357,9 @@ def foo():
|
||||
def foo():
|
||||
a = 1 # Comment
|
||||
return a
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7098
|
||||
def mavko_debari(P_kbar):
|
||||
D=0.4853881 + 3.6006116*P - 0.0117368*(P-1.3822)**2
|
||||
return D
|
||||
|
||||
10
crates/ruff/resources/test/fixtures/flake8_self/SLF001_extended.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_self/SLF001_extended.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def foo(obj):
|
||||
obj._meta # OK
|
||||
|
||||
|
||||
def foo(obj):
|
||||
obj._asdict # SLF001
|
||||
|
||||
|
||||
def foo(obj):
|
||||
obj._bar # SLF001
|
||||
@@ -8,6 +8,7 @@ try:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# SIM105
|
||||
try:
|
||||
foo()
|
||||
@@ -110,3 +111,20 @@ try:
|
||||
print()
|
||||
except "not an exception":
|
||||
pass
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7123
|
||||
def write_models(directory, Models):
|
||||
try:
|
||||
os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass;
|
||||
|
||||
try: os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass;
|
||||
|
||||
try: os.makedirs(model_dir);
|
||||
except OSError:
|
||||
pass; \
|
||||
\
|
||||
#
|
||||
|
||||
@@ -42,3 +42,17 @@ class Foo:
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self.keys() # OK
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
key in obj.keys()and foo
|
||||
(key in obj.keys())and foo
|
||||
key in (obj.keys())and foo
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
for key in (
|
||||
self.experiment.surveys[0]
|
||||
.stations[0]
|
||||
.keys()
|
||||
):
|
||||
continue
|
||||
|
||||
@@ -13,3 +13,8 @@ def f():
|
||||
return False
|
||||
|
||||
a = True if b else False
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7076
|
||||
samesld = True if (psl.privatesuffix(urlparse(response.url).netloc) ==
|
||||
psl.privatesuffix(src.netloc)) else False
|
||||
|
||||
@@ -152,3 +152,11 @@ if (a or [1] or True or [2]) == (a or [1]): # SIM222
|
||||
|
||||
if f(a or [1] or True or [2]): # SIM222
|
||||
pass
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7099
|
||||
def secondToTime(s0: int) -> (int, int, int) or str:
|
||||
m, s = divmod(s0, 60)
|
||||
|
||||
|
||||
def secondToTime(s0: int) -> ((int, int, int) or str):
|
||||
m, s = divmod(s0, 60)
|
||||
|
||||
@@ -14,6 +14,8 @@ YODA >= age # SIM300
|
||||
JediOrder.YODA == age # SIM300
|
||||
0 < (number - 100) # SIM300
|
||||
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # SIM300
|
||||
B<A[0][0]or B
|
||||
B or(B)<A[0][0]
|
||||
|
||||
# OK
|
||||
compare == "yoda"
|
||||
|
||||
@@ -16,3 +16,8 @@ nok4 = "a".join([a, a, *a]) # Not OK (not a static length)
|
||||
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
||||
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||
def create_file_public_url(url, filename):
|
||||
return''.join([url, filename])
|
||||
|
||||
@@ -18,3 +18,8 @@ pdf = pd.DataFrame(
|
||||
)
|
||||
|
||||
_ = arr.astype(np.int)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6952
|
||||
from numpy import float
|
||||
|
||||
float(1)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import numpy as np
|
||||
def func():
|
||||
import numpy as np
|
||||
|
||||
np.round_(np.random.rand(5, 5), 2)
|
||||
np.product(np.random.rand(5, 5))
|
||||
np.cumproduct(np.random.rand(5, 5))
|
||||
np.sometrue(np.random.rand(5, 5))
|
||||
np.alltrue(np.random.rand(5, 5))
|
||||
np.round_(np.random.rand(5, 5), 2)
|
||||
np.product(np.random.rand(5, 5))
|
||||
np.cumproduct(np.random.rand(5, 5))
|
||||
np.sometrue(np.random.rand(5, 5))
|
||||
np.alltrue(np.random.rand(5, 5))
|
||||
|
||||
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||
|
||||
round_(np.random.rand(5, 5), 2)
|
||||
product(np.random.rand(5, 5))
|
||||
cumproduct(np.random.rand(5, 5))
|
||||
sometrue(np.random.rand(5, 5))
|
||||
alltrue(np.random.rand(5, 5))
|
||||
def func():
|
||||
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||
|
||||
round_(np.random.rand(5, 5), 2)
|
||||
product(np.random.rand(5, 5))
|
||||
cumproduct(np.random.rand(5, 5))
|
||||
sometrue(np.random.rand(5, 5))
|
||||
alltrue(np.random.rand(5, 5))
|
||||
|
||||
@@ -29,3 +29,5 @@ x.apply(lambda x: x.sort_values("a", inplace=True))
|
||||
import torch
|
||||
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
from typing import TypeVar
|
||||
from typing import NewType
|
||||
from typing import NamedTuple, TypedDict
|
||||
from typing import TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
|
||||
|
||||
GLOBAL: str = "foo"
|
||||
|
||||
@@ -21,9 +19,11 @@ def assign():
|
||||
T = TypeVar("T")
|
||||
UserId = NewType("UserId", int)
|
||||
|
||||
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
|
||||
Employee = NamedTuple("Employee", [("name", str), ("id", int)])
|
||||
|
||||
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})
|
||||
Point2D = TypedDict("Point2D", {"in": int, "x-y": int})
|
||||
|
||||
IntOrStr: TypeAlias = int | str
|
||||
|
||||
|
||||
def aug_assign(rank, world_size):
|
||||
|
||||
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # Ok (false negative: edge case where `else` is same as `if`)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = []
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (result is not a dictionary)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (if/elif/else isn't replaceable)
|
||||
elif idx % 3:
|
||||
result[idx] = name
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {1: "banana"}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx in result:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for name in fruit:
|
||||
result[name] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[name] = idx # PERF403
|
||||
@@ -138,3 +138,12 @@ def scope():
|
||||
class TemperatureScales(Enum):
|
||||
CELSIUS = (lambda deg_c: deg_c)
|
||||
FAHRENHEIT = (lambda deg_c: deg_c * 9 / 5 + 32)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7141
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
f = lambda: (
|
||||
i := 1,
|
||||
)
|
||||
|
||||
@@ -639,3 +639,27 @@ def starts_with_space_then_this():
|
||||
class SameLine: """This is a docstring on the same line"""
|
||||
|
||||
def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
|
||||
def single_line_docstring_with_an_escaped_backslash():
|
||||
"\
|
||||
"
|
||||
|
||||
class StatementOnSameLineAsDocstring:
|
||||
"After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
def sort_services(self):
|
||||
pass
|
||||
|
||||
class StatementOnSameLineAsDocstring:
|
||||
"After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
|
||||
|
||||
class CommentAfterDocstring:
|
||||
"After this docstring there's a comment." # priorities=1
|
||||
def sort_services(self):
|
||||
pass
|
||||
|
||||
|
||||
def newline_after_closing_quote(self):
|
||||
"We enforce a newline after the closing quote for a multi-line docstring \
|
||||
but continuations shouldn't be considered multi-line"
|
||||
|
||||
@@ -128,6 +128,19 @@ def f(x, *, y, z):
|
||||
"""
|
||||
return x, y, z
|
||||
|
||||
def f(x):
|
||||
"""Do something with valid description.
|
||||
|
||||
Args:
|
||||
----
|
||||
x: the value
|
||||
|
||||
Returns:
|
||||
-------
|
||||
the value
|
||||
"""
|
||||
return x
|
||||
|
||||
|
||||
class Test:
|
||||
def f(self, /, arg1: int) -> None:
|
||||
|
||||
@@ -529,3 +529,16 @@ def replace_equals_with_dash2():
|
||||
Parameters
|
||||
===========
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
def non_empty_blank_line_before_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
The function's description.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
@@ -112,3 +112,11 @@ match *0, 1, *2:
|
||||
import b1
|
||||
|
||||
import b2
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7244
|
||||
from datameta_client_lib.model_utils import ( # noqa: F401
|
||||
noqa )
|
||||
|
||||
from datameta_client_lib.model_helpers import (
|
||||
noqa )
|
||||
|
||||
@@ -9,3 +9,8 @@ hidden = {"a": "!"}
|
||||
"%(a)s" % {'a': 1, u"b": "!"} # F504 ("b" not used)
|
||||
|
||||
'' % {'a''b' : ''} # F504 ("ab" not used)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/4899
|
||||
"" % {
|
||||
'test1': '',
|
||||
'test2': '',
|
||||
|
||||
@@ -165,3 +165,9 @@ def f():
|
||||
x = 1
|
||||
|
||||
y = 2
|
||||
|
||||
|
||||
def f():
|
||||
(x) = foo()
|
||||
((x)) = foo()
|
||||
(x) = (y.z) = foo()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from typing import override
|
||||
|
||||
|
||||
class Apples:
|
||||
def _init_(self): # [bad-dunder-name]
|
||||
pass
|
||||
@@ -21,6 +24,11 @@ class Apples:
|
||||
# author likely meant to call the invert dunder method
|
||||
pass
|
||||
|
||||
@override
|
||||
def _ignore__(self): # [bad-dunder-name]
|
||||
# overridden dunder methods should be ignored
|
||||
pass
|
||||
|
||||
def hello(self):
|
||||
print("hello")
|
||||
|
||||
|
||||
@@ -36,3 +36,6 @@ tuples_list = [
|
||||
|
||||
min(min(tuples_list))
|
||||
max(max(tuples_list))
|
||||
|
||||
# Starred argument should be copied as it is.
|
||||
max(1, max(*a))
|
||||
|
||||
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
class Everything:
|
||||
foo = 1
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
|
||||
def method7(self):
|
||||
pass
|
||||
|
||||
def method8(self):
|
||||
pass
|
||||
|
||||
def method9(self):
|
||||
pass
|
||||
|
||||
class Small:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
@@ -91,3 +91,20 @@ def f(x: Optional[int : float]) -> None:
|
||||
|
||||
def f(x: Optional[str, int : float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: Optional[int, float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7131
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[
|
||||
list[ServiceSpecificationRef]
|
||||
| list[ServiceSpecification]
|
||||
] = None
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7201
|
||||
class ServiceRefOrValue:
|
||||
service_specification: Optional[str]is not True = None
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import subprocess
|
||||
import subprocess as somename
|
||||
from subprocess import run
|
||||
from subprocess import run as anothername
|
||||
|
||||
# Errors
|
||||
subprocess.run(["foo"], universal_newlines=True, check=True)
|
||||
somename.run(["foo"], universal_newlines=True)
|
||||
|
||||
subprocess.run(["foo"], universal_newlines=True, text=True)
|
||||
run(["foo"], universal_newlines=True, check=False)
|
||||
anothername(["foo"], universal_newlines=True)
|
||||
|
||||
# OK
|
||||
subprocess.run(["foo"], check=True)
|
||||
|
||||
@@ -35,8 +35,19 @@ if output:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, capture_output=True, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Examples that should NOT trigger the rule
|
||||
output = subprocess.run(
|
||||
["foo"], stdout=subprocess.PIPE, capture_output=False, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
output = subprocess.run(
|
||||
["foo"], capture_output=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# OK
|
||||
from foo import PIPE
|
||||
subprocess.run(["foo"], stdout=PIPE, stderr=PIPE)
|
||||
run(["foo"], stdout=None, stderr=PIPE)
|
||||
|
||||
@@ -96,3 +96,11 @@ try:
|
||||
pass
|
||||
except (OSError, KeyError):
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7101
|
||||
def get_owner_id_from_mac_address():
|
||||
try:
|
||||
mac_address = get_primary_mac_address()
|
||||
except(IOError, OSError) as ex:
|
||||
msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
|
||||
|
||||
@@ -72,3 +72,12 @@ def f():
|
||||
for x, y in z():
|
||||
yield x, y
|
||||
x = 1
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7103
|
||||
def _serve_method(fn):
|
||||
for h in (
|
||||
TaggedText.from_file(args.input)
|
||||
.markup(highlight=args.region)
|
||||
):
|
||||
yield h
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# These should NOT change
|
||||
# OK
|
||||
def f():
|
||||
for x in z:
|
||||
yield
|
||||
|
||||
@@ -74,6 +74,8 @@ from typing import Collection
|
||||
from typing import AsyncGenerator
|
||||
from typing import Reversible
|
||||
from typing import Generator
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
@@ -178,3 +178,9 @@ if True:
|
||||
if True:
|
||||
if sys.version_info > (3, 0): \
|
||||
expected_error = []
|
||||
|
||||
if sys.version_info < (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info <= (3,12):
|
||||
print("py3")
|
||||
|
||||
@@ -13,18 +13,19 @@ x: typing.TypeAlias = list[T]
|
||||
T = typing.TypeVar("T")
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 bounded generic (todo)
|
||||
# UP040 bounded generic
|
||||
T = typing.TypeVar("T", bound=int)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 constrained generic
|
||||
T = typing.TypeVar("T", int, str)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 contravariant generic (todo)
|
||||
# UP040 contravariant generic
|
||||
T = typing.TypeVar("T", contravariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 covariant generic (todo)
|
||||
# UP040 covariant generic
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
21
crates/ruff/resources/test/fixtures/refurb/FURB145.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
l = [1, 2, 3, 4, 5]
|
||||
|
||||
# Errors.
|
||||
a = l[:]
|
||||
b, c = 1, l[:]
|
||||
d, e = l[:], 1
|
||||
m = l[::]
|
||||
l[:]
|
||||
print(l[:])
|
||||
|
||||
# False negatives.
|
||||
aa = a[:] # Type inference.
|
||||
|
||||
# OK.
|
||||
t = (1, 2, 3, 4, 5)
|
||||
f = t[:] # t.copy() is not supported.
|
||||
g = l[1:3]
|
||||
h = l[1:]
|
||||
i = l[:3]
|
||||
j = l[1:3:2]
|
||||
k = l[::2]
|
||||
@@ -12,3 +12,10 @@ sum([[1, 2, 3], [4, 5, 6]],
|
||||
# OK
|
||||
sum([x, y])
|
||||
sum([[1, 2, 3], [4, 5, 6]])
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7059
|
||||
def func():
|
||||
import functools, operator
|
||||
|
||||
sum([x, y], [])
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
|
||||
};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::autofix::codemods;
|
||||
|
||||
@@ -92,10 +92,8 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::range)
|
||||
.chain(arguments.keywords.iter().map(Keyword::range))
|
||||
.arguments_source_order()
|
||||
.map(|arg| arg.range())
|
||||
.filter(|range| argument.range() != *range)
|
||||
.partition(|range| range.start() < argument.start());
|
||||
|
||||
@@ -249,6 +247,44 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
locator.line_end(start_location)
|
||||
}
|
||||
|
||||
/// Add leading whitespace to a snippet, if it's immediately preceded an identifier or keyword.
|
||||
pub(crate) fn pad_start(mut content: String, start: TextSize, locator: &Locator) -> String {
|
||||
// Ex) When converting `except(ValueError,)` from a tuple to a single argument, we need to
|
||||
// insert a space before the fix, to achieve `except ValueError`.
|
||||
if locator
|
||||
.up_to(start)
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|char| char.is_ascii_alphabetic())
|
||||
{
|
||||
content.insert(0, ' ');
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Add trailing whitespace to a snippet, if it's immediately followed by an identifier or keyword.
|
||||
pub(crate) fn pad_end(mut content: String, end: TextSize, locator: &Locator) -> String {
|
||||
if locator
|
||||
.after(end)
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|char| char.is_ascii_alphabetic())
|
||||
{
|
||||
content.push(' ');
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Add leading or trailing whitespace to a snippet, if it's immediately preceded or followed by
|
||||
/// an identifier or keyword.
|
||||
pub(crate) fn pad(content: String, range: TextRange, locator: &Locator) -> String {
|
||||
pad_start(
|
||||
pad_end(content, range.end(), locator),
|
||||
range.start(),
|
||||
locator,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -4,17 +4,15 @@ use std::collections::BTreeSet;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMap;
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
pub(crate) mod codemods;
|
||||
pub(crate) mod edits;
|
||||
pub(crate) mod snippet;
|
||||
pub(crate) mod source_map;
|
||||
|
||||
pub(crate) struct FixResult {
|
||||
/// The resulting source code, after applying all fixes.
|
||||
@@ -140,10 +138,9 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi
|
||||
mod tests {
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::source_map::SourceMarker;
|
||||
use crate::autofix::{apply_fixes, FixResult};
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
|
||||
@@ -207,14 +204,8 @@ print("hello world")
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker {
|
||||
source: 10.into(),
|
||||
dest: 10.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 10.into(),
|
||||
dest: 21.into(),
|
||||
},
|
||||
SourceMarker::new(10.into(), 10.into(),),
|
||||
SourceMarker::new(10.into(), 21.into(),),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -250,14 +241,8 @@ class A(Bar):
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker {
|
||||
source: 8.into(),
|
||||
dest: 8.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 14.into(),
|
||||
dest: 11.into(),
|
||||
},
|
||||
SourceMarker::new(8.into(), 8.into(),),
|
||||
SourceMarker::new(14.into(), 11.into(),),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -289,14 +274,8 @@ class A:
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker {
|
||||
source: 7.into(),
|
||||
dest: 7.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 15.into(),
|
||||
dest: 7.into()
|
||||
}
|
||||
SourceMarker::new(7.into(), 7.into()),
|
||||
SourceMarker::new(15.into(), 7.into()),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -332,22 +311,10 @@ class A(object):
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker {
|
||||
source: 8.into(),
|
||||
dest: 8.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 16.into(),
|
||||
dest: 8.into()
|
||||
},
|
||||
SourceMarker {
|
||||
source: 22.into(),
|
||||
dest: 14.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 30.into(),
|
||||
dest: 14.into(),
|
||||
}
|
||||
SourceMarker::new(8.into(), 8.into()),
|
||||
SourceMarker::new(16.into(), 8.into()),
|
||||
SourceMarker::new(22.into(), 14.into(),),
|
||||
SourceMarker::new(30.into(), 14.into(),),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -382,14 +349,8 @@ class A:
|
||||
assert_eq!(
|
||||
source_map.markers(),
|
||||
&[
|
||||
SourceMarker {
|
||||
source: 7.into(),
|
||||
dest: 7.into(),
|
||||
},
|
||||
SourceMarker {
|
||||
source: 15.into(),
|
||||
dest: 7.into(),
|
||||
}
|
||||
SourceMarker::new(7.into(), 7.into(),),
|
||||
SourceMarker::new(15.into(), 7.into(),),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
|
||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
|
||||
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
|
||||
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
|
||||
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -113,10 +113,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::InvalidIndexType) {
|
||||
ruff::rules::invalid_index_type(checker, subscript);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::SliceCopy) {
|
||||
refurb::rules::slice_copy(checker, subscript);
|
||||
}
|
||||
|
||||
pandas_vet::rules::subscript(checker, value, expr);
|
||||
}
|
||||
@@ -258,6 +260,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SixPY3) {
|
||||
flake8_2020::rules::name_or_attribute(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::LoadBeforeGlobalDeclaration) {
|
||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||
}
|
||||
@@ -324,6 +329,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attribute);
|
||||
}
|
||||
Expr::Call(
|
||||
@@ -884,6 +892,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::DirectLoggerInstantiation) {
|
||||
flake8_logging::rules::direct_logger_instantiation(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(ast::ExprDict {
|
||||
keys,
|
||||
@@ -1195,7 +1206,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
@@ -1204,7 +1214,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bytes(_),
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
@@ -1213,7 +1222,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
kind,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
|
||||
@@ -1227,7 +1235,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, value.unicode);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
@@ -1253,14 +1261,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.enabled(Rule::IfExprWithTrueFalse) {
|
||||
flake8_simplify::rules::explicit_true_false_in_ifexpr(
|
||||
checker, expr, test, body, orelse,
|
||||
);
|
||||
flake8_simplify::rules::if_expr_with_true_false(checker, expr, test, body, orelse);
|
||||
}
|
||||
if checker.enabled(Rule::IfExprWithFalseTrue) {
|
||||
flake8_simplify::rules::explicit_false_true_in_ifexpr(
|
||||
checker, expr, test, body, orelse,
|
||||
);
|
||||
flake8_simplify::rules::if_expr_with_false_true(checker, expr, test, body, orelse);
|
||||
}
|
||||
if checker.enabled(Rule::IfExprWithTwistedArms) {
|
||||
flake8_simplify::rules::twisted_arms_in_ifexpr(checker, expr, test, body, orelse);
|
||||
@@ -1358,8 +1362,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ExprAndFalse) {
|
||||
flake8_simplify::rules::expr_and_false(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RepeatedEqualityComparisonTarget) {
|
||||
pylint::rules::repeated_equality_comparison_target(checker, bool_op);
|
||||
if checker.enabled(Rule::RepeatedEqualityComparison) {
|
||||
pylint::rules::repeated_equality_comparison(checker, bool_op);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -411,6 +411,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EqWithoutHash) {
|
||||
pylint::rules::object_without_hash_method(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyPublicMethods) {
|
||||
pylint::rules::too_many_public_methods(
|
||||
checker,
|
||||
class_def,
|
||||
checker.settings.pylint.max_public_methods,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(checker, name);
|
||||
}
|
||||
@@ -1177,7 +1184,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
is_async,
|
||||
..
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -1211,6 +1218,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ManualListCopy) {
|
||||
perflint::rules::manual_list_copy(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ManualDictComprehension) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter);
|
||||
}
|
||||
|
||||
@@ -528,11 +528,7 @@ where
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
|
||||
self.semantic.push_scope(match &stmt {
|
||||
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef"),
|
||||
});
|
||||
self.semantic.push_scope(ScopeKind::Function(function_def));
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
|
||||
@@ -1192,7 +1188,6 @@ where
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if self.semantic.in_type_definition()
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) fn check_imports(
|
||||
stylist: &Stylist,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
// Extract all import blocks from the AST.
|
||||
|
||||
@@ -9,6 +9,7 @@ use strum_macros::{AsRefStr, EnumIter};
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use crate::registry::{AsRule, Linter};
|
||||
use crate::rule_selector::is_single_rule_selector;
|
||||
use crate::rules;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -51,7 +52,10 @@ impl PartialEq<&str> for NoqaCode {
|
||||
pub enum RuleGroup {
|
||||
/// The rule has not been assigned to any specific group.
|
||||
Unspecified,
|
||||
/// The rule is still under development, and must be enabled explicitly.
|
||||
/// The rule is unstable, and preview mode must be enabled for usage.
|
||||
Preview,
|
||||
/// Legacy category for unstable rules, supports backwards compatible selection.
|
||||
#[deprecated(note = "Use `RuleGroup::Preview` for new rules instead")]
|
||||
Nursery,
|
||||
}
|
||||
|
||||
@@ -64,38 +68,71 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
Some(match (linter, code) {
|
||||
// pycodestyle errors
|
||||
(Pycodestyle, "E101") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MixedSpacesAndTabs),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E111") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultiple),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E113") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E114") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::IndentationWithInvalidMultipleComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E115") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E116") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedIndentationComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E117") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::OverIndented),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E201") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceAfterOpenBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E202") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeCloseBracket),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E203") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforePunctuation),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E211") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::WhitespaceBeforeParameters),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E221") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E222") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E223") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E224") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E225") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E226") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundArithmeticOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E262") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterInlineComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E265") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoSpaceAfterBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E266") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleLeadingHashesForBlockComment),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E271") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E272") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E273") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E274") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabBeforeKeyword),
|
||||
#[allow(deprecated)]
|
||||
(Pycodestyle, "E275") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAfterKeyword),
|
||||
(Pycodestyle, "E401") => (RuleGroup::Unspecified, rules::pycodestyle::rules::MultipleImportsOnOneLine),
|
||||
(Pycodestyle, "E402") => (RuleGroup::Unspecified, rules::pycodestyle::rules::ModuleImportNotAtTopOfFile),
|
||||
@@ -176,6 +213,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
|
||||
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
|
||||
@@ -212,10 +250,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0915") => (RuleGroup::Unspecified, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R1701") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1711") => (RuleGroup::Unspecified, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparisonTarget),
|
||||
(Pylint, "R1714") => (RuleGroup::Unspecified, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1722") => (RuleGroup::Unspecified, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R2004") => (RuleGroup::Unspecified, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Unspecified, rules::pylint::rules::CollapsibleElseIf),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "R6301") => (RuleGroup::Nursery, rules::pylint::rules::NoSelfUse),
|
||||
(Pylint, "W0120") => (RuleGroup::Unspecified, rules::pylint::rules::UselessElseOnLoop),
|
||||
(Pylint, "W0127") => (RuleGroup::Unspecified, rules::pylint::rules::SelfAssigningVariable),
|
||||
@@ -228,8 +267,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
@@ -403,6 +445,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
|
||||
|
||||
// flake8-copyright
|
||||
#[allow(deprecated)]
|
||||
(Flake8Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
|
||||
// pyupgrade
|
||||
@@ -815,9 +858,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
|
||||
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
|
||||
#[cfg(feature = "unreachable-code")] // When removing this feature gate, also update rules_selector.rs
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
|
||||
(Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement),
|
||||
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
|
||||
#[allow(deprecated)]
|
||||
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
|
||||
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
||||
@@ -853,6 +898,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
||||
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
|
||||
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
|
||||
(Perflint, "403") => (RuleGroup::Preview, rules::perflint::rules::ManualDictComprehension),
|
||||
|
||||
// flake8-fixme
|
||||
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
||||
@@ -866,9 +912,17 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
|
||||
|
||||
// refurb
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "113") => (RuleGroup::Nursery, rules::refurb::rules::RepeatedAppend),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use libcst_native::{Expression, NameOrAttribute};
|
||||
use libcst_native::{
|
||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
||||
};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
match expr {
|
||||
@@ -36,3 +38,55 @@ pub(crate) fn compose_module_path(module: &NameOrAttribute) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [`ParenthesizableWhitespace`] containing a single space.
|
||||
pub(crate) fn space() -> ParenthesizableWhitespace<'static> {
|
||||
ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" "))
|
||||
}
|
||||
|
||||
/// Ensure that a [`ParenthesizableWhitespace`] contains at least one space.
|
||||
pub(crate) fn or_space(whitespace: ParenthesizableWhitespace) -> ParenthesizableWhitespace {
|
||||
if whitespace == ParenthesizableWhitespace::default() {
|
||||
space()
|
||||
} else {
|
||||
whitespace
|
||||
}
|
||||
}
|
||||
|
||||
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
|
||||
pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
||||
if let Expression::UnaryOperation(ref expression) = expression {
|
||||
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
|
||||
return *expression.expression.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Expression::Name(ref expression) = expression {
|
||||
match expression.value {
|
||||
"True" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "False",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
"False" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::UnaryOperation(Box::new(UnaryOperation {
|
||||
operator: libcst_native::UnaryOp::Not {
|
||||
whitespace_after: space(),
|
||||
},
|
||||
expression: Box::new(expression.clone()),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ impl<'a> Docstring<'a> {
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
|
||||
pub(crate) fn triple_quoted(&self) -> bool {
|
||||
let leading_quote = self.leading_quote();
|
||||
leading_quote.ends_with("\"\"\"") || leading_quote.ends_with("'''")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for Docstring<'_> {
|
||||
|
||||
@@ -171,10 +171,8 @@ impl<'a> Importer<'a> {
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
match self.get_symbol(symbol, at, semantic) {
|
||||
Some(result) => result,
|
||||
None => self.import_symbol(symbol, at, semantic),
|
||||
}
|
||||
self.get_symbol(symbol, at, semantic)?
|
||||
.map_or_else(|| self.import_symbol(symbol, at, semantic), Ok)
|
||||
}
|
||||
|
||||
/// Return an [`Edit`] to reference an existing symbol, if it's present in the given [`SemanticModel`].
|
||||
@@ -183,9 +181,13 @@ impl<'a> Importer<'a> {
|
||||
symbol: &ImportRequest,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Option<Result<(Edit, String), ResolutionError>> {
|
||||
) -> Result<Option<(Edit, String)>, ResolutionError> {
|
||||
// If the symbol is already available in the current scope, use it.
|
||||
let imported_name = semantic.resolve_qualified_import_name(symbol.module, symbol.member)?;
|
||||
let Some(imported_name) =
|
||||
semantic.resolve_qualified_import_name(symbol.module, symbol.member)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// If the symbol source (i.e., the import statement) comes after the current location,
|
||||
// abort. For example, we could be generating an edit within a function, and the import
|
||||
@@ -195,13 +197,13 @@ impl<'a> Importer<'a> {
|
||||
// unclear whether should add an import statement at the start of the file, since it could
|
||||
// be shadowed between the import and the current location.
|
||||
if imported_name.start() > at {
|
||||
return Some(Err(ResolutionError::ImportAfterUsage));
|
||||
return Err(ResolutionError::ImportAfterUsage);
|
||||
}
|
||||
|
||||
// If the symbol source (i.e., the import statement) is in a typing-only context, but we're
|
||||
// in a runtime context, abort.
|
||||
if imported_name.context().is_typing() && semantic.execution_context().is_runtime() {
|
||||
return Some(Err(ResolutionError::IncompatibleContext));
|
||||
return Err(ResolutionError::IncompatibleContext);
|
||||
}
|
||||
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
@@ -224,7 +226,7 @@ impl<'a> Importer<'a> {
|
||||
self.locator.slice(imported_name.range()).to_string(),
|
||||
imported_name.range(),
|
||||
);
|
||||
Some(Ok((import_edit, imported_name.into_name())))
|
||||
Ok(Some((import_edit, imported_name.into_name())))
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
//!
|
||||
//! [Ruff]: https://github.com/astral-sh/ruff
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub use rule_selector::clap_completion::RuleSelectorParser;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
pub use rules::pycodestyle::rules::{IOError, SyntaxError};
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -20,7 +22,6 @@ mod doc_lines;
|
||||
mod docstrings;
|
||||
pub mod fs;
|
||||
mod importer;
|
||||
pub mod jupyter;
|
||||
mod lex;
|
||||
pub mod line_width;
|
||||
pub mod linter;
|
||||
|
||||
@@ -6,8 +6,6 @@ use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -15,7 +13,8 @@ use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use ruff_source_file::{Locator, SourceFileBuilder};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -37,9 +36,6 @@ use crate::settings::{flags, Settings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::{directives, fs};
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
|
||||
/// A [`Result`]-like type that returns both data and an error. Used to return
|
||||
/// diagnostics even in the face of parse errors, since many diagnostics can be
|
||||
/// generated without a full AST.
|
||||
@@ -82,7 +78,7 @@ pub fn check_path(
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
@@ -271,17 +267,17 @@ const MAX_ITERATIONS: usize = 100;
|
||||
pub fn add_noqa_to_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
settings: &Settings,
|
||||
) -> Result<usize> {
|
||||
// Read the file from disk.
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
let contents = source_kind.source_code();
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&contents);
|
||||
let locator = Locator::new(contents);
|
||||
|
||||
// Detect the current code style (lazily).
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
@@ -311,21 +307,20 @@ pub fn add_noqa_to_path(
|
||||
&directives,
|
||||
settings,
|
||||
flags::Noqa::Disabled,
|
||||
None,
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
if let Some(err) = error {
|
||||
// TODO(dhruvmanila): This should use `SourceKind`, update when
|
||||
// `--add-noqa` is supported for Jupyter notebooks.
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::new(err, locator.to_source_code(), None)
|
||||
DisplayParseError::new(err, locator.to_source_code(), source_kind)
|
||||
);
|
||||
}
|
||||
|
||||
// Add any missing `# noqa` pragmas.
|
||||
// TODO(dhruvmanila): Add support for Jupyter Notebooks
|
||||
add_noqa(
|
||||
path,
|
||||
&diagnostics.0,
|
||||
@@ -378,7 +373,7 @@ pub fn lint_only(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
Some(source_kind),
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
|
||||
@@ -472,7 +467,7 @@ pub fn lint_fix<'a>(
|
||||
&directives,
|
||||
settings,
|
||||
noqa,
|
||||
Some(source_kind),
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
|
||||
@@ -545,8 +540,9 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"debug error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
@@ -555,18 +551,17 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Failed to converge after {} iterations.
|
||||
{}{} Failed to converge after {} iterations.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BInfinite%20loop%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
codes
|
||||
);
|
||||
@@ -583,8 +578,9 @@ fn report_autofix_syntax_error(
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"{}{} Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
error,
|
||||
@@ -593,19 +589,148 @@ fn report_autofix_syntax_error(
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Autofix introduced a syntax error. Reverting all changes.
|
||||
{}{} Autofix introduced a syntax error. Reverting all changes.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BAutofix%20error%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BAutofix%20error%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{test_contents, test_notebook_path, TestedNotebook};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||
fn notebook_path(path: impl AsRef<Path>) -> std::path::PathBuf {
|
||||
Path::new("../ruff_notebook/resources/test/fixtures/jupyter").join(path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("isort.ipynb");
|
||||
let expected = notebook_path("isort_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipy_escape_command() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("ipy_escape_command.ipynb");
|
||||
let expected = notebook_path("ipy_escape_command_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<(), NotebookError> {
|
||||
let actual = notebook_path("unused_variable.ipynb");
|
||||
let expected = notebook_path("unused_variable_expected.ipynb");
|
||||
let TestedNotebook {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let actual_path = notebook_path("before_fix.ipynb");
|
||||
let expected_path = notebook_path("after_fix.ipynb");
|
||||
|
||||
let TestedNotebook {
|
||||
linted_notebook: fixed_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
actual_path,
|
||||
&expected_path,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
let mut writer = Vec::new();
|
||||
fixed_notebook.write(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
let expected = std::fs::read_to_string(expected_path)?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("before_fix.ipynb"), true; "trailing_newline")]
|
||||
#[test_case(Path::new("no_trailing_newline.ipynb"), false; "no_trailing_newline")]
|
||||
fn test_trailing_newline(path: &Path, trailing_newline: bool) -> Result<()> {
|
||||
let notebook = Notebook::from_path(¬ebook_path(path))?;
|
||||
assert_eq!(notebook.trailing_newline(), trailing_newline);
|
||||
|
||||
let mut writer = Vec::new();
|
||||
notebook.write(&mut writer)?;
|
||||
let string = String::from_utf8(writer)?;
|
||||
assert_eq!(string.ends_with('\n'), trailing_newline);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Version <4.5, don't emit cell ids
|
||||
#[test_case(Path::new("no_cell_id.ipynb"), false; "no_cell_id")]
|
||||
// Version 4.5, cell ids are missing and need to be added
|
||||
#[test_case(Path::new("add_missing_cell_id.ipynb"), true; "add_missing_cell_id")]
|
||||
fn test_cell_id(path: &Path, has_id: bool) -> Result<()> {
|
||||
let source_notebook = Notebook::from_path(¬ebook_path(path))?;
|
||||
let source_kind = SourceKind::IpyNotebook(source_notebook);
|
||||
let (_, transformed) = test_contents(
|
||||
&source_kind,
|
||||
path,
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
);
|
||||
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
||||
let mut writer = Vec::new();
|
||||
linted_notebook.write(&mut writer)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
if has_id {
|
||||
assert!(actual.contains(r#""id": ""#));
|
||||
} else {
|
||||
assert!(!actual.contains(r#""id":"#));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ use ruff_python_parser::{ParseError, ParseErrorType};
|
||||
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
|
||||
|
||||
use crate::fs;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::source_kind::SourceKind;
|
||||
use ruff_notebook::Notebook;
|
||||
|
||||
pub static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
|
||||
|
||||
@@ -139,14 +139,14 @@ pub fn set_up_logging(level: &LogLevel) -> Result<()> {
|
||||
pub struct DisplayParseError<'a> {
|
||||
error: ParseError,
|
||||
source_code: SourceCode<'a, 'a>,
|
||||
source_kind: Option<&'a SourceKind>,
|
||||
source_kind: &'a SourceKind,
|
||||
}
|
||||
|
||||
impl<'a> DisplayParseError<'a> {
|
||||
pub fn new(
|
||||
error: ParseError,
|
||||
source_code: SourceCode<'a, 'a>,
|
||||
source_kind: Option<&'a SourceKind>,
|
||||
source_kind: &'a SourceKind,
|
||||
) -> Self {
|
||||
Self {
|
||||
error,
|
||||
@@ -171,32 +171,29 @@ impl Display for DisplayParseError<'_> {
|
||||
// If we're working on a Jupyter notebook, translate the positions
|
||||
// with respect to the cell and row in the cell. This is the same
|
||||
// format as the `TextEmitter`.
|
||||
let error_location = if let Some(jupyter_index) = self
|
||||
.source_kind
|
||||
.and_then(SourceKind::notebook)
|
||||
.map(Notebook::index)
|
||||
{
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{colon}",
|
||||
cell = jupyter_index
|
||||
.cell(source_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
colon = ":".cyan(),
|
||||
)?;
|
||||
let error_location =
|
||||
if let Some(jupyter_index) = self.source_kind.as_ipy_notebook().map(Notebook::index) {
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{colon}",
|
||||
cell = jupyter_index
|
||||
.cell(source_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
colon = ":".cyan(),
|
||||
)?;
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
.cell_row(source_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
column: source_location.column,
|
||||
}
|
||||
} else {
|
||||
source_location
|
||||
};
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
.cell_row(source_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
column: source_location.column,
|
||||
}
|
||||
} else {
|
||||
source_location
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
|
||||
@@ -6,8 +7,6 @@ use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::json;
|
||||
|
||||
use ruff_source_file::SourceLocation;
|
||||
|
||||
use crate::fs::{relativize_path, relativize_path_to};
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
@@ -58,6 +57,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
|
||||
let mut fingerprints = HashSet::<u64>::with_capacity(self.messages.len());
|
||||
|
||||
for message in self.messages {
|
||||
let start_location = message.compute_start_location();
|
||||
@@ -82,10 +82,19 @@ impl Serialize for SerializedMessages<'_> {
|
||||
|project_dir| relativize_path_to(message.filename(), project_dir),
|
||||
);
|
||||
|
||||
let mut message_fingerprint = fingerprint(message, 0);
|
||||
|
||||
// Make sure that we do not get a fingerprint that is already in use
|
||||
// by adding in the previously generated one.
|
||||
while fingerprints.contains(&message_fingerprint) {
|
||||
message_fingerprint = fingerprint(message, message_fingerprint);
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let value = json!({
|
||||
"description": format!("({}) {}", message.kind.rule().noqa_code(), message.kind.body),
|
||||
"severity": "major",
|
||||
"fingerprint": fingerprint(message, &start_location, &end_location),
|
||||
"fingerprint": format!("{:x}", message_fingerprint),
|
||||
"location": {
|
||||
"path": path,
|
||||
"lines": lines
|
||||
@@ -100,11 +109,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
|
||||
/// Generate a unique fingerprint to identify a violation.
|
||||
fn fingerprint(
|
||||
message: &Message,
|
||||
start_location: &SourceLocation,
|
||||
end_location: &SourceLocation,
|
||||
) -> String {
|
||||
fn fingerprint(message: &Message, salt: u64) -> u64 {
|
||||
let Message {
|
||||
kind,
|
||||
range: _,
|
||||
@@ -115,12 +120,11 @@ fn fingerprint(
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
kind.rule().hash(&mut hasher);
|
||||
start_location.hash(&mut hasher);
|
||||
end_location.hash(&mut hasher);
|
||||
salt.hash(&mut hasher);
|
||||
kind.name.hash(&mut hasher);
|
||||
file.name().hash(&mut hasher);
|
||||
|
||||
format!("{:x}", hasher.finish())
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::num::NonZeroUsize;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::{Notebook, NotebookIndex};
|
||||
use crate::message::diff::calculate_print_width;
|
||||
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
|
||||
use crate::message::{
|
||||
@@ -65,7 +65,7 @@ impl Emitter for GroupedEmitter {
|
||||
writer,
|
||||
"{}",
|
||||
DisplayGroupedMessage {
|
||||
jupyter_index: context.notebook(message.filename()).map(Notebook::index),
|
||||
notebook_index: context.notebook_index(message.filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
show_source: self.show_source,
|
||||
@@ -92,7 +92,7 @@ struct DisplayGroupedMessage<'a> {
|
||||
show_source: bool,
|
||||
row_length: NonZeroUsize,
|
||||
column_length: NonZeroUsize,
|
||||
jupyter_index: Option<&'a NotebookIndex>,
|
||||
notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for DisplayGroupedMessage<'_> {
|
||||
@@ -110,7 +110,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
)?;
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let (row, col) = if let Some(jupyter_index) = self.jupyter_index {
|
||||
let (row, col) = if let Some(jupyter_index) = self.notebook_index {
|
||||
write!(
|
||||
f,
|
||||
"cell {cell}{sep}",
|
||||
@@ -150,7 +150,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index: self.jupyter_index
|
||||
notebook_index: self.notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@ pub use json_lines::JsonLinesEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{SourceFile, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use crate::jupyter::Notebook;
|
||||
|
||||
mod azure;
|
||||
mod diff;
|
||||
mod github;
|
||||
@@ -128,21 +127,21 @@ pub trait Emitter {
|
||||
|
||||
/// Context passed to [`Emitter`].
|
||||
pub struct EmitterContext<'a> {
|
||||
notebooks: &'a FxHashMap<String, Notebook>,
|
||||
notebook_indexes: &'a FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl<'a> EmitterContext<'a> {
|
||||
pub fn new(notebooks: &'a FxHashMap<String, Notebook>) -> Self {
|
||||
Self { notebooks }
|
||||
pub fn new(notebook_indexes: &'a FxHashMap<String, NotebookIndex>) -> Self {
|
||||
Self { notebook_indexes }
|
||||
}
|
||||
|
||||
/// Tests if the file with `name` is a jupyter notebook.
|
||||
pub fn is_notebook(&self, name: &str) -> bool {
|
||||
self.notebooks.contains_key(name)
|
||||
self.notebook_indexes.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn notebook(&self, name: &str) -> Option<&Notebook> {
|
||||
self.notebooks.get(name)
|
||||
pub fn notebook_index(&self, name: &str) -> Option<&NotebookIndex> {
|
||||
self.notebook_indexes.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,8 +225,8 @@ def fibonacci(n):
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
) -> String {
|
||||
let source_kinds = FxHashMap::default();
|
||||
let context = EmitterContext::new(&source_kinds);
|
||||
let notebook_indexes = FxHashMap::default();
|
||||
let context = EmitterContext::new(¬ebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::{Notebook, NotebookIndex};
|
||||
use crate::line_width::{LineWidthBuilder, TabSize};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
@@ -71,14 +71,14 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
let start_location = message.compute_start_location();
|
||||
let jupyter_index = context.notebook(message.filename()).map(Notebook::index);
|
||||
let notebook_index = context.notebook_index(message.filename());
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = jupyter_index {
|
||||
let diagnostic_location = if let Some(notebook_index) = notebook_index {
|
||||
write!(
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
cell = jupyter_index
|
||||
cell = notebook_index
|
||||
.cell(start_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
sep = ":".cyan(),
|
||||
@@ -86,7 +86,7 @@ impl Emitter for TextEmitter {
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
notebook_index
|
||||
.cell_row(start_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
@@ -115,7 +115,7 @@ impl Emitter for TextEmitter {
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index
|
||||
notebook_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) jupyter_index: Option<&'a NotebookIndex>,
|
||||
pub(crate) notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
@@ -186,14 +186,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_start_cell = jupyter_index
|
||||
.cell(content_start_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_start_cell = index.cell(content_start_index.get()).unwrap_or_default();
|
||||
while start_index < content_start_index {
|
||||
if jupyter_index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
if index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
break;
|
||||
}
|
||||
start_index = start_index.saturating_add(1);
|
||||
@@ -213,14 +211,12 @@ impl Display for MessageCodeFrame<'_> {
|
||||
.saturating_add(2)
|
||||
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_end_cell = jupyter_index
|
||||
.cell(content_end_index.get())
|
||||
.unwrap_or_default();
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_end_cell = index.cell(content_end_index.get()).unwrap_or_default();
|
||||
while end_index > content_end_index {
|
||||
if jupyter_index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
if index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
break;
|
||||
}
|
||||
end_index = end_index.saturating_sub(1);
|
||||
@@ -256,10 +252,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
title: None,
|
||||
slices: vec![Slice {
|
||||
source: &source.text,
|
||||
line_start: self.jupyter_index.map_or_else(
|
||||
line_start: self.notebook_index.map_or_else(
|
||||
|| start_index.get(),
|
||||
|jupyter_index| {
|
||||
jupyter_index
|
||||
|notebook_index| {
|
||||
notebook_index
|
||||
.cell_row(start_index.get())
|
||||
.unwrap_or_default() as usize
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'a> Directive<'a> {
|
||||
if text[..comment_start]
|
||||
.chars()
|
||||
.last()
|
||||
.is_some_and(|c| c != '#')
|
||||
.map_or(true, |c| c != '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,9 @@ pub enum Linter {
|
||||
/// [refurb](https://pypi.org/project/refurb/)
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
|
||||
@@ -9,12 +9,14 @@ use crate::codes::RuleCodePrefix;
|
||||
use crate::codes::RuleIter;
|
||||
use crate::registry::{Linter, Rule, RuleNamespace};
|
||||
use crate::rule_redirects::get_redirect;
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum RuleSelector {
|
||||
/// Select all stable rules.
|
||||
/// Select all rules (includes rules in preview if enabled)
|
||||
All,
|
||||
/// Select all nursery rules.
|
||||
/// Legacy category to select all rules in the "nursery" which predated preview mode
|
||||
#[deprecated(note = "The nursery was replaced with 'preview mode' which has no selector")]
|
||||
Nursery,
|
||||
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
|
||||
/// via a single selector.
|
||||
@@ -29,6 +31,11 @@ pub enum RuleSelector {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
/// Select an individual rule with a given prefix.
|
||||
Rule {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Linter> for RuleSelector {
|
||||
@@ -43,6 +50,7 @@ impl FromStr for RuleSelector {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
@@ -59,16 +67,43 @@ impl FromStr for RuleSelector {
|
||||
return Ok(Self::Linter(linter));
|
||||
}
|
||||
|
||||
Ok(Self::Prefix {
|
||||
prefix: RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?,
|
||||
redirected_from,
|
||||
})
|
||||
// Does the selector select a single rule?
|
||||
let prefix = RuleCodePrefix::parse(&linter, code)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?;
|
||||
|
||||
if is_single_rule_selector(&prefix) {
|
||||
Ok(Self::Rule {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
} else {
|
||||
Ok(Self::Prefix {
|
||||
prefix,
|
||||
redirected_from,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`RuleCodePrefix`] matches a single rule exactly
|
||||
/// (e.g., `E225`, as opposed to `E2`).
|
||||
pub(crate) fn is_single_rule_selector(prefix: &RuleCodePrefix) -> bool {
|
||||
let mut rules = prefix.rules();
|
||||
|
||||
// The selector must match a single rule.
|
||||
let Some(rule) = rules.next() else {
|
||||
return false;
|
||||
};
|
||||
if rules.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rule must match the selector exactly.
|
||||
rule.noqa_code().suffix() == prefix.short_code()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error("Unknown rule selector: `{0}`")]
|
||||
@@ -81,10 +116,11 @@ impl RuleSelector {
|
||||
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
RuleSelector::All => ("", "ALL"),
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => ("", "NURSERY"),
|
||||
RuleSelector::C => ("", "C"),
|
||||
RuleSelector::T => ("", "T"),
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
(prefix.linter().common_prefix(), prefix.short_code())
|
||||
}
|
||||
RuleSelector::Linter(l) => (l.common_prefix(), ""),
|
||||
@@ -135,24 +171,13 @@ impl Visitor<'_> for SelectorVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RuleCodePrefix> for RuleSelector {
|
||||
fn from(prefix: RuleCodePrefix) -> Self {
|
||||
Self::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &RuleSelector {
|
||||
type IntoIter = RuleSelectorIter;
|
||||
type Item = Rule;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
impl RuleSelector {
|
||||
/// Return all matching rules, regardless of whether they're in preview.
|
||||
pub fn all_rules(&self) -> impl Iterator<Item = Rule> + '_ {
|
||||
match self {
|
||||
RuleSelector::All => {
|
||||
RuleSelectorIter::All(Rule::iter().filter(|rule| !rule.is_nursery()))
|
||||
}
|
||||
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
|
||||
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => {
|
||||
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
|
||||
}
|
||||
@@ -167,13 +192,28 @@ impl IntoIterator for &RuleSelector {
|
||||
.chain(Linter::Flake8Print.rules()),
|
||||
),
|
||||
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.rules()),
|
||||
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.clone().rules()),
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
RuleSelectorIter::Vec(prefix.clone().rules())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns rules matching the selector, taking into account whether preview mode is enabled.
|
||||
pub fn rules(&self, preview: PreviewMode) -> impl Iterator<Item = Rule> + '_ {
|
||||
#[allow(deprecated)]
|
||||
self.all_rules().filter(move |rule| {
|
||||
// Always include rules that are not in preview or the nursery
|
||||
!(rule.is_preview() || rule.is_nursery())
|
||||
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|
||||
|| ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|
||||
// Enabling preview includes all preview or nursery rules
|
||||
|| preview.is_enabled()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RuleSelectorIter {
|
||||
All(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
All(RuleIter),
|
||||
Nursery(std::iter::Filter<RuleIter, fn(&Rule) -> bool>),
|
||||
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
|
||||
Vec(std::vec::IntoIter<Rule>),
|
||||
@@ -192,18 +232,6 @@ impl Iterator for RuleSelectorIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
|
||||
/// to let us keep the fields of [`RuleSelector`] private.
|
||||
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
|
||||
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
|
||||
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
|
||||
pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
|
||||
RuleSelector::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schema {
|
||||
use itertools::Itertools;
|
||||
@@ -226,8 +254,9 @@ mod schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
// Include the non-standard "ALL" and "NURSERY" selectors.
|
||||
"ALL".to_string(),
|
||||
"NURSERY".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
@@ -266,18 +295,19 @@ impl RuleSelector {
|
||||
pub fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => Specificity::All,
|
||||
RuleSelector::T => Specificity::LinterGroup,
|
||||
RuleSelector::C => Specificity::LinterGroup,
|
||||
RuleSelector::Linter(..) => Specificity::Linter,
|
||||
RuleSelector::Rule { .. } => Specificity::Rule,
|
||||
RuleSelector::Prefix { prefix, .. } => {
|
||||
let prefix: &'static str = prefix.short_code();
|
||||
match prefix.len() {
|
||||
1 => Specificity::Code1Char,
|
||||
2 => Specificity::Code2Chars,
|
||||
3 => Specificity::Code3Chars,
|
||||
4 => Specificity::Code4Chars,
|
||||
5 => Specificity::Code5Chars,
|
||||
1 => Specificity::Prefix1Char,
|
||||
2 => Specificity::Prefix2Chars,
|
||||
3 => Specificity::Prefix3Chars,
|
||||
4 => Specificity::Prefix4Chars,
|
||||
_ => panic!("RuleSelector::specificity doesn't yet support codes with so many characters"),
|
||||
}
|
||||
}
|
||||
@@ -285,26 +315,35 @@ impl RuleSelector {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||
pub enum Specificity {
|
||||
/// The specificity when selecting all rules (e.g., `--select ALL`).
|
||||
All,
|
||||
/// The specificity when selecting a legacy linter group (e.g., `--select C` or `--select T`).
|
||||
LinterGroup,
|
||||
/// The specificity when selecting a linter (e.g., `--select PLE` or `--select UP`).
|
||||
Linter,
|
||||
Code1Char,
|
||||
Code2Chars,
|
||||
Code3Chars,
|
||||
Code4Chars,
|
||||
Code5Chars,
|
||||
/// The specificity when selecting via a rule prefix with a one-character code (e.g., `--select PLE1`).
|
||||
Prefix1Char,
|
||||
/// The specificity when selecting via a rule prefix with a two-character code (e.g., `--select PLE12`).
|
||||
Prefix2Chars,
|
||||
/// The specificity when selecting via a rule prefix with a three-character code (e.g., `--select PLE123`).
|
||||
Prefix3Chars,
|
||||
/// The specificity when selecting via a rule prefix with a four-character code (e.g., `--select PLE1234`).
|
||||
Prefix4Chars,
|
||||
/// The specificity when selecting an individual rule (e.g., `--select PLE1205`).
|
||||
Rule,
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
mod clap_completion {
|
||||
pub mod clap_completion {
|
||||
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
codes::RuleCodePrefix,
|
||||
registry::{Linter, RuleNamespace},
|
||||
rule_selector::is_single_rule_selector,
|
||||
RuleSelector,
|
||||
};
|
||||
|
||||
@@ -324,17 +363,29 @@ mod clap_completion {
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
_cmd: &clap::Command,
|
||||
_arg: Option<&clap::Arg>,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
value
|
||||
.parse()
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
|
||||
value.parse().map_err(|_| {
|
||||
let mut error =
|
||||
clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidArg,
|
||||
clap::error::ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidValue,
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
@@ -349,27 +400,34 @@ mod clap_completion {
|
||||
RuleCodePrefix::iter()
|
||||
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
|
||||
// off-by-default
|
||||
.filter(|p| {
|
||||
format!("{}{}", p.linter().common_prefix(), p.short_code())
|
||||
!= "RUF014"
|
||||
.filter(|prefix| {
|
||||
format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
) != "RUF014"
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
|
||||
let mut rules_iter = p.rules();
|
||||
let rule1 = rules_iter.next();
|
||||
let rule2 = rules_iter.next();
|
||||
|
||||
let value = PossibleValue::new(format!("{prefix}{code}"));
|
||||
|
||||
if rule2.is_none() {
|
||||
let rule1 = rule1.unwrap();
|
||||
let name: &'static str = rule1.into();
|
||||
value.help(name)
|
||||
} else {
|
||||
value
|
||||
.filter_map(|prefix| {
|
||||
// Ex) `UP`
|
||||
if prefix.short_code().is_empty() {
|
||||
let code = prefix.linter().common_prefix();
|
||||
let name = prefix.linter().name();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
// Ex) `UP004`
|
||||
if is_single_rule_selector(&prefix) {
|
||||
let rule = prefix.rules().next()?;
|
||||
let code = format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
);
|
||||
let name: &'static str = rule.into();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -454,7 +454,6 @@ fn check_dynamically_typed<F>(
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
range,
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
// Quoted annotations
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Violation for StartProcessWithNoShell {
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess.Popen()`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Common Weakness Enumeration: CWE-426](https://cwe.mitre.org/data/definitions/426.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithPartialPath;
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ mod tests {
|
||||
#[test_case(Rule::JumpStatementInFinally, Path::new("B012.py"))]
|
||||
#[test_case(Rule::LoopVariableOverridesIterator, Path::new("B020.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
|
||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
|
||||
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
||||
#[test_case(Rule::RaiseLiteral, Path::new("B016.py"))]
|
||||
#[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"))]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -81,7 +80,17 @@ pub(crate) fn getattr_with_constant(
|
||||
let mut diagnostic = Diagnostic::new(GetAttrWithConstant, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
format!("{}.{}", checker.locator().slice(obj), value),
|
||||
if matches!(
|
||||
obj,
|
||||
Expr::Name(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_)
|
||||
) {
|
||||
format!("{}.{}", checker.locator().slice(obj), value)
|
||||
} else {
|
||||
// Defensively parenthesize any other expressions. For example, attribute accesses
|
||||
// on `int` literals must be parenthesized, e.g., `getattr(1, "real")` becomes
|
||||
// `(1).real`. The same is true for named expressions and others.
|
||||
format!("({}).{}", checker.locator().slice(obj), value)
|
||||
},
|
||||
expr.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl Violation for MutableArgumentDefault {
|
||||
fn message(&self) -> String {
|
||||
format!("Do not use mutable data structures for argument defaults")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `None`; initialize within function"))
|
||||
}
|
||||
@@ -175,8 +176,15 @@ fn move_initialization(
|
||||
return None;
|
||||
}
|
||||
Edit::insertion(content, locator.line_start(statement.start()))
|
||||
} else if locator.full_line_end(statement.end()) == locator.text_len() {
|
||||
// If the statement is at the end of the file, without a trailing newline, insert
|
||||
// _after_ it with an extra newline.
|
||||
Edit::insertion(
|
||||
format!("{}{}", stylist.line_ending().as_str(), content),
|
||||
locator.full_line_end(statement.end()),
|
||||
)
|
||||
} else {
|
||||
// If the docstring is the only statement, insert _before_ it.
|
||||
// If the docstring is the only statement, insert _after_ it.
|
||||
Edit::insertion(content, locator.full_line_end(statement.end()))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::map_starred;
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::autofix::edits::pad;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
@@ -41,11 +42,7 @@ pub struct RedundantTupleInExceptionHandler {
|
||||
impl AlwaysAutofixableViolation for RedundantTupleInExceptionHandler {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let RedundantTupleInExceptionHandler { name } = self;
|
||||
format!(
|
||||
"A length-one tuple literal is redundant. Write `except {name}` instead of `except \
|
||||
({name},)`."
|
||||
)
|
||||
format!("A length-one tuple literal is redundant in exception handlers")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
@@ -70,9 +67,10 @@ pub(crate) fn redundant_tuple_in_exception_handler(
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let [elt] = &elts[..] else {
|
||||
let [elt] = elts.as_slice() else {
|
||||
continue;
|
||||
};
|
||||
let elt = map_starred(elt);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RedundantTupleInExceptionHandler {
|
||||
name: checker.generator().expr(elt),
|
||||
@@ -80,8 +78,19 @@ pub(crate) fn redundant_tuple_in_exception_handler(
|
||||
type_.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// If there's no space between the `except` and the tuple, we need to insert a space,
|
||||
// as in:
|
||||
// ```python
|
||||
// except(ValueError,):
|
||||
// ```
|
||||
// Otherwise, the output will be invalid syntax, since we're removing a set of
|
||||
// parentheses.
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
checker.generator().expr(elt),
|
||||
pad(
|
||||
checker.generator().expr(elt),
|
||||
type_.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
type_.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ use crate::checkers::ast::Checker;
|
||||
/// contains multiple characters, the reader may be misled into thinking that
|
||||
/// a prefix or suffix is being removed, rather than a set of characters.
|
||||
///
|
||||
/// In Python 3.9 and later, you can use `str#removeprefix` and
|
||||
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// In Python 3.9 and later, you can use `str.removeprefix` and
|
||||
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// respectively, which should be preferred when possible.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_1.py:3:22: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
1 | # Docstring followed by a newline
|
||||
2 |
|
||||
3 | def foobar(foor, bar={}):
|
||||
| ^^ B006
|
||||
4 | """
|
||||
5 | """
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
1 1 | # Docstring followed by a newline
|
||||
2 2 |
|
||||
3 |-def foobar(foor, bar={}):
|
||||
3 |+def foobar(foor, bar=None):
|
||||
4 4 | """
|
||||
5 5 | """
|
||||
6 |+
|
||||
7 |+ if bar is None:
|
||||
8 |+ bar = {}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_2.py:4:22: B006 [*] Do not use mutable data structures for argument defaults
|
||||
|
|
||||
2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155
|
||||
3 |
|
||||
4 | def foobar(foor, bar={}):
|
||||
| ^^ B006
|
||||
5 | """
|
||||
6 | """
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Possible fix
|
||||
1 1 | # Docstring followed by whitespace with no newline
|
||||
2 2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155
|
||||
3 3 |
|
||||
4 |-def foobar(foor, bar={}):
|
||||
4 |+def foobar(foor, bar=None):
|
||||
5 5 | """
|
||||
6 |- """
|
||||
6 |+ """
|
||||
7 |+ if bar is None:
|
||||
8 |+ bar = {}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user