Compare commits
407 Commits
v0.0.292
...
schemastor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d65addffa0 | ||
|
|
9d167a1f5c | ||
|
|
9e184a9067 | ||
|
|
9d1027c239 | ||
|
|
f499f0ca60 | ||
|
|
722687ad72 | ||
|
|
4760af3dcb | ||
|
|
4fdf97a95c | ||
|
|
0ea1076f85 | ||
|
|
3956f38999 | ||
|
|
fe9727ac38 | ||
|
|
3ebaca5246 | ||
|
|
7391f74cbc | ||
|
|
71e93a9fa4 | ||
|
|
e2c7b1ece6 | ||
|
|
621e98f452 | ||
|
|
0126f74c29 | ||
|
|
fce9f63418 | ||
|
|
ce549e75bc | ||
|
|
03303a9edd | ||
|
|
7873ca38e5 | ||
|
|
7dabc4598b | ||
|
|
6a1fa4778f | ||
|
|
c3d6d5d006 | ||
|
|
9a8400a287 | ||
|
|
d71c65d0c8 | ||
|
|
7f92bfbc4a | ||
|
|
37301375c8 | ||
|
|
c07947bfac | ||
|
|
72964529a5 | ||
|
|
eab8ca4d7e | ||
|
|
5b3e922050 | ||
|
|
311a7751f9 | ||
|
|
5e2bb8ca07 | ||
|
|
3c8d9d45fb | ||
|
|
82c3c513d2 | ||
|
|
f2dc01e3aa | ||
|
|
5349143fca | ||
|
|
b6f23d57aa | ||
|
|
b7b6e0136e | ||
|
|
218f517487 | ||
|
|
75c669a007 | ||
|
|
2d5ce4532a | ||
|
|
f3e2d12609 | ||
|
|
de2d7e97b1 | ||
|
|
bcb737dd80 | ||
|
|
8c146bbf11 | ||
|
|
4170ef0508 | ||
|
|
72ebde8d38 | ||
|
|
1672a3d3b7 | ||
|
|
8c0d65c98e | ||
|
|
b3c2935fa5 | ||
|
|
e57bccd500 | ||
|
|
75c9be099f | ||
|
|
c4889196e7 | ||
|
|
6e635e99f4 | ||
|
|
260ea41975 | ||
|
|
65effc6666 | ||
|
|
4982694b54 | ||
|
|
536ac550ed | ||
|
|
f2335fe692 | ||
|
|
b0f9a14d9a | ||
|
|
f56bc1983b | ||
|
|
7c12eaf322 | ||
|
|
41e538a748 | ||
|
|
dd2d8cb579 | ||
|
|
a08c5b7fa7 | ||
|
|
9f30ccc1f4 | ||
|
|
31286e1c95 | ||
|
|
b9994dc495 | ||
|
|
f16505d885 | ||
|
|
d04d964ace | ||
|
|
f64c389654 | ||
|
|
2ff1afb15c | ||
|
|
7fa6ac976a | ||
|
|
7dd5137913 | ||
|
|
0d93fbb4a2 | ||
|
|
d350ede992 | ||
|
|
a8a72306f0 | ||
|
|
c8122563a6 | ||
|
|
f8f507cfc8 | ||
|
|
ab6bf50a2d | ||
|
|
df4dc040de | ||
|
|
3a889f4686 | ||
|
|
edc75dc5d6 | ||
|
|
ebad36da06 | ||
|
|
2f7e2a8de3 | ||
|
|
4d23c1fc83 | ||
|
|
29573daef5 | ||
|
|
9558bac64a | ||
|
|
d5abe55b03 | ||
|
|
3fc920cd12 | ||
|
|
e9acb99f7d | ||
|
|
1642f4dbd9 | ||
|
|
38358980f1 | ||
|
|
43691f97d0 | ||
|
|
3076d76b0a | ||
|
|
23ed4e9616 | ||
|
|
97ae617fac | ||
|
|
a8d04cbd88 | ||
|
|
230c93459f | ||
|
|
8977b6ae11 | ||
|
|
982ae6ff08 | ||
|
|
c674db6e51 | ||
|
|
7323c12eee | ||
|
|
161c093c06 | ||
|
|
daea870c3c | ||
|
|
b6c4074836 | ||
|
|
cf74debf42 | ||
|
|
f483ed4240 | ||
|
|
98b3d716c6 | ||
|
|
03df6fa105 | ||
|
|
8cc97f70b4 | ||
|
|
951c59c6ad | ||
|
|
d177df226d | ||
|
|
703e2a9da3 | ||
|
|
bdad5e9a5f | ||
|
|
b21eb1f689 | ||
|
|
b0dc5a86a1 | ||
|
|
b5a4a9a356 | ||
|
|
230c9ce236 | ||
|
|
78bbf6d403 | ||
|
|
ee7d445ef5 | ||
|
|
5776ec1079 | ||
|
|
1f2d4f3ee1 | ||
|
|
221f7cd932 | ||
|
|
c7aa816f17 | ||
|
|
3ccca332bd | ||
|
|
2c84f911c4 | ||
|
|
e799f90782 | ||
|
|
9b89bf7d8a | ||
|
|
d7b966d6cd | ||
|
|
44e21cfada | ||
|
|
cda1c5dd35 | ||
|
|
86cdaea743 | ||
|
|
af4cb34ce2 | ||
|
|
4afff436ff | ||
|
|
f2f2e759c7 | ||
|
|
317b6e8682 | ||
|
|
2f5734d1ac | ||
|
|
c39ea6ef05 | ||
|
|
10a50bf1e2 | ||
|
|
a151e50ad3 | ||
|
|
854f5d09fa | ||
|
|
c2f6c79b3d | ||
|
|
87772c2884 | ||
|
|
aa90a425e0 | ||
|
|
81a2e74fe2 | ||
|
|
3af890f32f | ||
|
|
223873c8c7 | ||
|
|
7b4b004506 | ||
|
|
9f5102d536 | ||
|
|
af95cbaeef | ||
|
|
fc94857a20 | ||
|
|
5f26411577 | ||
|
|
40c886c3bc | ||
|
|
097e703071 | ||
|
|
cd8e1bad64 | ||
|
|
e2b5c6ac5f | ||
|
|
c36efe254e | ||
|
|
3e7b92991b | ||
|
|
25d4ddaa60 | ||
|
|
63a5a12a41 | ||
|
|
c32f943d86 | ||
|
|
d211074f59 | ||
|
|
4ffd4ed61f | ||
|
|
a4dd1e5fad | ||
|
|
be3307e9a6 | ||
|
|
317d3dd612 | ||
|
|
f5e850745c | ||
|
|
a7d1f7e1ec | ||
|
|
88c8b47326 | ||
|
|
133a745de1 | ||
|
|
6983d96d27 | ||
|
|
3c3d9ab173 | ||
|
|
ff9fb0da54 | ||
|
|
9792b1551b | ||
|
|
d1c67f91bd | ||
|
|
dbd84c947b | ||
|
|
c2ec5f0bc9 | ||
|
|
31032f4f70 | ||
|
|
f55b724254 | ||
|
|
fd07a12a52 | ||
|
|
1ee73bdedf | ||
|
|
23b55aea30 | ||
|
|
e36afc3324 | ||
|
|
8304c41714 | ||
|
|
6f31e9c00e | ||
|
|
a6cc56fd98 | ||
|
|
0236e0751c | ||
|
|
2d0769e324 | ||
|
|
80473c3f5c | ||
|
|
4d7f90e045 | ||
|
|
75bd95e58c | ||
|
|
3bbdfee69f | ||
|
|
3127c79b29 | ||
|
|
c91cc29d6d | ||
|
|
8b665f40c8 | ||
|
|
84979f9673 | ||
|
|
c3dabc1933 | ||
|
|
2e81b9c391 | ||
|
|
9feb86caa4 | ||
|
|
2587aef1ea | ||
|
|
7f4ea6690d | ||
|
|
2f32a57cf4 | ||
|
|
802616aac0 | ||
|
|
7100e12cc3 | ||
|
|
5a95b25aa8 | ||
|
|
833814384a | ||
|
|
39e45aa06f | ||
|
|
92baa3591d | ||
|
|
0e96482085 | ||
|
|
b9bff6f5d1 | ||
|
|
2401e91ab9 | ||
|
|
6199590072 | ||
|
|
08519e22e4 | ||
|
|
c704674190 | ||
|
|
2db96067aa | ||
|
|
8cd09c88d3 | ||
|
|
ce9bd19885 | ||
|
|
764304faf1 | ||
|
|
6fc35dd075 | ||
|
|
c0710a1dd4 | ||
|
|
2c2ebf952a | ||
|
|
d6a4283003 | ||
|
|
95702e408f | ||
|
|
bcaac9693b | ||
|
|
d6f59e4131 | ||
|
|
66e6388776 | ||
|
|
8472a7e50f | ||
|
|
7586091437 | ||
|
|
4e07a65c15 | ||
|
|
00fd324c6f | ||
|
|
2414f23abb | ||
|
|
e0f9dbcd10 | ||
|
|
6ddb0fa950 | ||
|
|
df807ff912 | ||
|
|
f6d6200aae | ||
|
|
fa556d1c74 | ||
|
|
860ffb9549 | ||
|
|
90ebea86a4 | ||
|
|
7a5f98835a | ||
|
|
348b649b5c | ||
|
|
ae41d6f30a | ||
|
|
b1072049bf | ||
|
|
bc49492085 | ||
|
|
c8464c3a90 | ||
|
|
f158536fbb | ||
|
|
1dd264b019 | ||
|
|
a525f09008 | ||
|
|
256b98ab9a | ||
|
|
962472da96 | ||
|
|
a00c445580 | ||
|
|
0e58433715 | ||
|
|
22cf451d51 | ||
|
|
ec1be60dcb | ||
|
|
a327b4da87 | ||
|
|
cdc5e2fb58 | ||
|
|
b5d3caf033 | ||
|
|
8f9753f58e | ||
|
|
67b043482a | ||
|
|
693f957b90 | ||
|
|
a85ed309ea | ||
|
|
2e225d7538 | ||
|
|
4786abac7a | ||
|
|
46d5db56cc | ||
|
|
2729c4cacd | ||
|
|
b2d1fcf7b2 | ||
|
|
78d172aad7 | ||
|
|
13d6c8237a | ||
|
|
51aa73f405 | ||
|
|
0c3123e07e | ||
|
|
dda4ceda71 | ||
|
|
195c000f5a | ||
|
|
a62c735f9e | ||
|
|
94b4bb0f57 | ||
|
|
fe485d791c | ||
|
|
d685107638 | ||
|
|
d85950ce5a | ||
|
|
88c0106421 | ||
|
|
f60aa85471 | ||
|
|
d942a777d7 | ||
|
|
8a529925b3 | ||
|
|
dc6b4ad2b4 | ||
|
|
21ea290d6a | ||
|
|
5da0f9111e | ||
|
|
cb6d74c27b | ||
|
|
73049df3ed | ||
|
|
bf0e5788ef | ||
|
|
4113d65836 | ||
|
|
4c2c9bf7e0 | ||
|
|
172ac2c9a2 | ||
|
|
cac9754455 | ||
|
|
134def0119 | ||
|
|
1fabaca5de | ||
|
|
523f542dbd | ||
|
|
ee7575eb5a | ||
|
|
84f7391cc5 | ||
|
|
7da4e28a98 | ||
|
|
5718df638f | ||
|
|
4bb4cd3b37 | ||
|
|
620426de7a | ||
|
|
84ec66a22c | ||
|
|
e58ffa9a7a | ||
|
|
aa6846c78c | ||
|
|
3d03e75a9d | ||
|
|
b6e75e58c9 | ||
|
|
8061894af6 | ||
|
|
e261eb7461 | ||
|
|
bd06cbe0c5 | ||
|
|
ddffadb4b0 | ||
|
|
8255e4ed6c | ||
|
|
60ca6885b1 | ||
|
|
889117ea87 | ||
|
|
c03a693ebc | ||
|
|
6f9c317aa5 | ||
|
|
66179af4f1 | ||
|
|
1e184e69f3 | ||
|
|
f08a5f67eb | ||
|
|
cd564c4200 | ||
|
|
c1fdb9c46d | ||
|
|
48b256bd94 | ||
|
|
3944c42d4c | ||
|
|
cb06b7956c | ||
|
|
4454fbf7e5 | ||
|
|
b243840e4b | ||
|
|
23bbe7336a | ||
|
|
a71c4dfabb | ||
|
|
81275d12e9 | ||
|
|
40cad44f4a | ||
|
|
c38617fa27 | ||
|
|
1835d7bb45 | ||
|
|
f670f9b22c | ||
|
|
7a072cc2ea | ||
|
|
8c4b5d3c90 | ||
|
|
ec9d5cddd6 | ||
|
|
0f759af3cf | ||
|
|
644011fb14 | ||
|
|
a1ee6d28ce | ||
|
|
826868da5b | ||
|
|
5986ff748a | ||
|
|
739a8aa10e | ||
|
|
090c1a4a19 | ||
|
|
2b95d3832b | ||
|
|
d412e8ef74 | ||
|
|
46e45bdf19 | ||
|
|
a3e8e77172 | ||
|
|
ec7395ba69 | ||
|
|
d8c0360fc7 | ||
|
|
097b654ba7 | ||
|
|
d54cabd276 | ||
|
|
7faa43108f | ||
|
|
74b00c9b91 | ||
|
|
97e944003b | ||
|
|
016e16254a | ||
|
|
61a41334a3 | ||
|
|
74971617a1 | ||
|
|
5c68c89566 | ||
|
|
8923eb19e0 | ||
|
|
dad70fff99 | ||
|
|
b72c94b3d1 | ||
|
|
b4b296dca3 | ||
|
|
43883b7a15 | ||
|
|
38f512d588 | ||
|
|
2d6557a51b | ||
|
|
2ba5677700 | ||
|
|
62f1ee08e7 | ||
|
|
bdd925c0f2 | ||
|
|
dd36a2516e | ||
|
|
b6c9cf1c5b | ||
|
|
805fd1bc93 | ||
|
|
0fc76ba276 | ||
|
|
4b537d1297 | ||
|
|
3c25d261fe | ||
|
|
4f95df1b6d | ||
|
|
22e18741bd | ||
|
|
e8d2cbc3f6 | ||
|
|
1dd5deb53d | ||
|
|
b64f403dc2 | ||
|
|
7dc9887ab9 | ||
|
|
709abd534a | ||
|
|
27def479bd | ||
|
|
1eac457c1b | ||
|
|
609a78b13e | ||
|
|
17fba99ed4 | ||
|
|
adb6580270 | ||
|
|
76fcf63052 | ||
|
|
90de108bfa | ||
|
|
ad265fa6bc | ||
|
|
59c00b5298 | ||
|
|
a0c846f9bd | ||
|
|
bb87f75b0c | ||
|
|
e674e87d1b | ||
|
|
600471e45f | ||
|
|
a1509dfc7c | ||
|
|
7b4fb4fb5d | ||
|
|
5d49d268a0 | ||
|
|
f71c80af68 | ||
|
|
90c259beb9 | ||
|
|
37d21c0d54 | ||
|
|
69b8136463 | ||
|
|
c040fac12f | ||
|
|
a6ebbf21c3 | ||
|
|
e129f77bcf | ||
|
|
3ccd1d580d | ||
|
|
f872c3bf0f | ||
|
|
55fa887099 |
29
.github/release.yml
vendored
29
.github/release.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
|
|
||||||
changelog:
|
|
||||||
exclude:
|
|
||||||
labels:
|
|
||||||
- internal
|
|
||||||
- documentation
|
|
||||||
categories:
|
|
||||||
- title: Breaking Changes
|
|
||||||
labels:
|
|
||||||
- breaking
|
|
||||||
- title: Rules
|
|
||||||
labels:
|
|
||||||
- rule
|
|
||||||
- title: Settings
|
|
||||||
labels:
|
|
||||||
- configuration
|
|
||||||
- cli
|
|
||||||
- title: Bug Fixes
|
|
||||||
labels:
|
|
||||||
- bug
|
|
||||||
- title: Formatter
|
|
||||||
labels:
|
|
||||||
- formatter
|
|
||||||
- title: Preview
|
|
||||||
labels:
|
|
||||||
- preview
|
|
||||||
- title: Other Changes
|
|
||||||
labels:
|
|
||||||
- "*"
|
|
||||||
206
.github/workflows/ci.yaml
vendored
206
.github/workflows/ci.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: tj-actions/changed-files@v39
|
- uses: tj-actions/changed-files@v40
|
||||||
id: changed
|
id: changed
|
||||||
with:
|
with:
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
@@ -43,6 +43,7 @@ jobs:
|
|||||||
- "!crates/ruff_dev/**"
|
- "!crates/ruff_dev/**"
|
||||||
- "!crates/ruff_shrinking/**"
|
- "!crates/ruff_shrinking/**"
|
||||||
- scripts/*
|
- scripts/*
|
||||||
|
- .github/workflows/ci.yaml
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
@@ -57,6 +58,7 @@ jobs:
|
|||||||
- crates/ruff_python_parser/**
|
- crates/ruff_python_parser/**
|
||||||
- crates/ruff_dev/**
|
- crates/ruff_dev/**
|
||||||
- scripts/*
|
- scripts/*
|
||||||
|
- .github/workflows/ci.yaml
|
||||||
|
|
||||||
cargo-fmt:
|
cargo-fmt:
|
||||||
name: "cargo fmt"
|
name: "cargo fmt"
|
||||||
@@ -82,12 +84,9 @@ jobs:
|
|||||||
- name: "Clippy (wasm)"
|
- name: "Clippy (wasm)"
|
||||||
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
|
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
|
||||||
|
|
||||||
cargo-test:
|
cargo-test-linux:
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
matrix:
|
name: "cargo test (linux)"
|
||||||
os: [ubuntu-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
name: "cargo test | ${{ matrix.os }}"
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -97,25 +96,54 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: "Run tests (Ubuntu)"
|
- name: "Run tests"
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
|
||||||
run: cargo insta test --all --all-features --unreferenced reject
|
run: cargo insta test --all --all-features --unreferenced reject
|
||||||
- name: "Run tests (Windows)"
|
|
||||||
if: ${{ matrix.os == 'windows-latest' }}
|
|
||||||
shell: bash
|
|
||||||
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
|
||||||
run: cargo insta test --all --all-features
|
|
||||||
# Check for broken links in the documentation.
|
# Check for broken links in the documentation.
|
||||||
- run: cargo doc --all --no-deps
|
- run: cargo doc --all --no-deps
|
||||||
env:
|
env:
|
||||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||||
RUSTDOCFLAGS: "-D warnings"
|
RUSTDOCFLAGS: "-D warnings"
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
|
||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
path: target/debug/ruff
|
path: target/debug/ruff
|
||||||
|
|
||||||
|
cargo-test-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
name: "cargo test (windows)"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: "Install Rust toolchain"
|
||||||
|
run: rustup show
|
||||||
|
- name: "Install cargo insta"
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-insta
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: "Run tests"
|
||||||
|
shell: bash
|
||||||
|
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||||
|
run: cargo insta test --all --all-features
|
||||||
|
|
||||||
|
cargo-test-wasm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: "cargo test (wasm)"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: "Install Rust toolchain"
|
||||||
|
run: rustup target add wasm32-unknown-unknown
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: playground/package-lock.json
|
||||||
|
- uses: jetli/wasm-pack-action@v0.4.0
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: "Run wasm-pack"
|
||||||
|
run: |
|
||||||
|
cd crates/ruff_wasm
|
||||||
|
wasm-pack test --node
|
||||||
|
|
||||||
cargo-fuzz:
|
cargo-fuzz:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: "cargo fuzz"
|
name: "cargo fuzz"
|
||||||
@@ -132,25 +160,6 @@ jobs:
|
|||||||
tool: cargo-fuzz@0.11
|
tool: cargo-fuzz@0.11
|
||||||
- run: cargo fuzz build -s none
|
- run: cargo fuzz build -s none
|
||||||
|
|
||||||
cargo-test-wasm:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: "cargo test (wasm)"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: "Install Rust toolchain"
|
|
||||||
run: rustup target add wasm32-unknown-unknown
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: playground/package-lock.json
|
|
||||||
- uses: jetli/wasm-pack-action@v0.4.0
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "Run wasm-pack"
|
|
||||||
run: |
|
|
||||||
cd crates/ruff_wasm
|
|
||||||
wasm-pack test --node
|
|
||||||
|
|
||||||
scripts:
|
scripts:
|
||||||
name: "test scripts"
|
name: "test scripts"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -172,10 +181,14 @@ jobs:
|
|||||||
name: "ecosystem"
|
name: "ecosystem"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- cargo-test
|
- cargo-test-linux
|
||||||
- determine_changes
|
- determine_changes
|
||||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
# 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'
|
# Ecosystem check needs linter and/or formatter changes.
|
||||||
|
if: github.event_name == 'pull_request' && ${{
|
||||||
|
needs.determine_changes.outputs.linter == 'true' ||
|
||||||
|
needs.determine_changes.outputs.formatter == 'true'
|
||||||
|
}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
@@ -183,27 +196,89 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
name: Download Ruff binary
|
name: Download comparison Ruff binary
|
||||||
id: ruff-target
|
id: ruff-target
|
||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
path: target/debug
|
path: target/debug
|
||||||
|
|
||||||
- uses: dawidd6/action-download-artifact@v2
|
- uses: dawidd6/action-download-artifact@v2
|
||||||
name: Download base results
|
name: Download baseline Ruff binary
|
||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
branch: ${{ github.event.pull_request.base.ref }}
|
||||||
check_artifacts: true
|
check_artifacts: true
|
||||||
|
|
||||||
- name: Run ecosystem check
|
- name: Install ruff-ecosystem
|
||||||
|
run: |
|
||||||
|
pip install ./python/ruff-ecosystem
|
||||||
|
|
||||||
|
- name: Run `ruff check` stable ecosystem check
|
||||||
|
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
# Make executable, since artifact download doesn't preserve this
|
# Make executable, since artifact download doesn't preserve this
|
||||||
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||||
|
|
||||||
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
|
# Set pipefail to avoid hiding errors with tee
|
||||||
cat ecosystem-result > $GITHUB_STEP_SUMMARY
|
set -eo pipefail
|
||||||
|
|
||||||
|
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
|
||||||
|
|
||||||
|
cat ecosystem-result-check-stable > $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Linter (stable)" > ecosystem-result
|
||||||
|
cat ecosystem-result-check-stable >> ecosystem-result
|
||||||
|
echo "" >> ecosystem-result
|
||||||
|
|
||||||
|
- name: Run `ruff check` preview ecosystem check
|
||||||
|
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||||
|
run: |
|
||||||
|
# Make executable, since artifact download doesn't preserve this
|
||||||
|
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||||
|
|
||||||
|
# Set pipefail to avoid hiding errors with tee
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
|
||||||
|
|
||||||
|
cat ecosystem-result-check-preview > $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Linter (preview)" >> ecosystem-result
|
||||||
|
cat ecosystem-result-check-preview >> ecosystem-result
|
||||||
|
echo "" >> ecosystem-result
|
||||||
|
|
||||||
|
- name: Run `ruff format` stable ecosystem check
|
||||||
|
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
|
||||||
|
run: |
|
||||||
|
# Make executable, since artifact download doesn't preserve this
|
||||||
|
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||||
|
|
||||||
|
# Set pipefail to avoid hiding errors with tee
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
|
||||||
|
|
||||||
|
cat ecosystem-result-format-stable > $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Formatter (stable)" >> ecosystem-result
|
||||||
|
cat ecosystem-result-format-stable >> ecosystem-result
|
||||||
|
echo "" >> ecosystem-result
|
||||||
|
|
||||||
|
- name: Run `ruff format` preview ecosystem check
|
||||||
|
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
|
||||||
|
run: |
|
||||||
|
# Make executable, since artifact download doesn't preserve this
|
||||||
|
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||||
|
|
||||||
|
# Set pipefail to avoid hiding errors with tee
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
|
||||||
|
|
||||||
|
cat ecosystem-result-format-preview > $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Formatter (preview)" >> ecosystem-result
|
||||||
|
cat ecosystem-result-format-preview >> ecosystem-result
|
||||||
|
echo "" >> ecosystem-result
|
||||||
|
|
||||||
|
- name: Export pull request number
|
||||||
|
run: |
|
||||||
echo ${{ github.event.number }} > pr-number
|
echo ${{ github.event.number }} > pr-number
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
@@ -225,12 +300,12 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: "Install nightly Rust toolchain"
|
- name: "Install nightly Rust toolchain"
|
||||||
# Only pinned to make caching work, update freely
|
# Only pinned to make caching work, update freely
|
||||||
run: rustup toolchain install nightly-2023-06-08
|
run: rustup toolchain install nightly-2023-10-15
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: "Install cargo-udeps"
|
- name: "Install cargo-udeps"
|
||||||
uses: taiki-e/install-action@cargo-udeps
|
uses: taiki-e/install-action@cargo-udeps
|
||||||
- name: "Run cargo-udeps"
|
- name: "Run cargo-udeps"
|
||||||
run: cargo +nightly-2023-06-08 udeps
|
run: cargo +nightly-2023-10-15 udeps
|
||||||
|
|
||||||
python-package:
|
python-package:
|
||||||
name: "python package"
|
name: "python package"
|
||||||
@@ -319,8 +394,8 @@ jobs:
|
|||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||||
|
|
||||||
check-formatter-ecosystem:
|
check-formatter-instability-and-black-similarity:
|
||||||
name: "Formatter ecosystem and progress checks"
|
name: "formatter instabilities and black similarity"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: determine_changes
|
needs: determine_changes
|
||||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||||
@@ -337,6 +412,45 @@ jobs:
|
|||||||
- name: "Remove checkouts from cache"
|
- name: "Remove checkouts from cache"
|
||||||
run: rm -r target/progress_projects
|
run: rm -r target/progress_projects
|
||||||
|
|
||||||
|
check-ruff-lsp:
|
||||||
|
name: "test ruff-lsp"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: cargo-test-linux
|
||||||
|
steps:
|
||||||
|
- uses: extractions/setup-just@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
name: "Download ruff-lsp source"
|
||||||
|
with:
|
||||||
|
repository: "astral-sh/ruff-lsp"
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
name: Download development ruff binary
|
||||||
|
id: ruff-target
|
||||||
|
with:
|
||||||
|
name: ruff
|
||||||
|
path: target/debug
|
||||||
|
|
||||||
|
- name: Install ruff-lsp dependencies
|
||||||
|
run: |
|
||||||
|
just install
|
||||||
|
|
||||||
|
- name: Run ruff-lsp tests
|
||||||
|
run: |
|
||||||
|
# Setup development binary
|
||||||
|
pip uninstall --yes ruff
|
||||||
|
chmod +x ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||||
|
export PATH=${{ steps.ruff-target.outputs.download-path }}:$PATH
|
||||||
|
ruff version
|
||||||
|
|
||||||
|
just test
|
||||||
|
|
||||||
benchmarks:
|
benchmarks:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||||
- name: "Deploy to Cloudflare Pages"
|
- name: "Deploy to Cloudflare Pages"
|
||||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
uses: cloudflare/wrangler-action@v3.1.1
|
uses: cloudflare/wrangler-action@v3.3.2
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
|||||||
7
.github/workflows/playground.yaml
vendored
7
.github/workflows/playground.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
@@ -40,8 +40,9 @@ jobs:
|
|||||||
working-directory: playground
|
working-directory: playground
|
||||||
- name: "Deploy to Cloudflare Pages"
|
- name: "Deploy to Cloudflare Pages"
|
||||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||||
uses: cloudflare/wrangler-action@v3.1.1
|
uses: cloudflare/wrangler-action@v3.3.2
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
command: pages deploy playground/dist --project-name=ruff-playground --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 playground/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
|
||||||
|
|||||||
21
.github/workflows/pr-comment.yaml
vendored
21
.github/workflows/pr-comment.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: PR Check Comment
|
name: Ecosystem check comment
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
@@ -18,13 +18,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dawidd6/action-download-artifact@v2
|
- uses: dawidd6/action-download-artifact@v2
|
||||||
name: Download PR Number
|
name: Download pull request number
|
||||||
with:
|
with:
|
||||||
name: pr-number
|
name: pr-number
|
||||||
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
|
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
|
||||||
if_no_artifact_found: ignore
|
if_no_artifact_found: ignore
|
||||||
|
|
||||||
- name: Extract PR Number
|
- name: Parse pull request number
|
||||||
id: pr-number
|
id: pr-number
|
||||||
run: |
|
run: |
|
||||||
if [[ -f pr-number ]]
|
if [[ -f pr-number ]]
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- uses: dawidd6/action-download-artifact@v2
|
- uses: dawidd6/action-download-artifact@v2
|
||||||
name: "Download Ecosystem Result"
|
name: "Download ecosystem results"
|
||||||
id: download-ecosystem-result
|
id: download-ecosystem-result
|
||||||
if: steps.pr-number.outputs.pr-number
|
if: steps.pr-number.outputs.pr-number
|
||||||
with:
|
with:
|
||||||
@@ -41,15 +41,18 @@ jobs:
|
|||||||
workflow: ci.yaml
|
workflow: ci.yaml
|
||||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||||
path: pr/ecosystem
|
path: pr/ecosystem
|
||||||
|
workflow_conclusion: completed
|
||||||
if_no_artifact_found: ignore
|
if_no_artifact_found: ignore
|
||||||
|
|
||||||
- name: Generate Comment
|
- name: Generate comment content
|
||||||
id: generate-comment
|
id: generate-comment
|
||||||
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
|
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo '## PR Check Results' >> comment.txt
|
# Note this identifier is used to find the comment to update on
|
||||||
|
# subsequent runs
|
||||||
|
echo '<!-- generated-comment ecosystem -->' >> comment.txt
|
||||||
|
|
||||||
echo "### Ecosystem" >> comment.txt
|
echo '## `ruff-ecosystem` results' >> comment.txt
|
||||||
cat pr/ecosystem/ecosystem-result >> comment.txt
|
cat pr/ecosystem/ecosystem-result >> comment.txt
|
||||||
echo "" >> comment.txt
|
echo "" >> comment.txt
|
||||||
|
|
||||||
@@ -57,14 +60,14 @@ jobs:
|
|||||||
cat comment.txt >> $GITHUB_OUTPUT
|
cat comment.txt >> $GITHUB_OUTPUT
|
||||||
echo 'EOF' >> $GITHUB_OUTPUT
|
echo 'EOF' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Find Comment
|
- name: Find existing comment
|
||||||
uses: peter-evans/find-comment@v2
|
uses: peter-evans/find-comment@v2
|
||||||
if: steps.generate-comment.outcome == 'success'
|
if: steps.generate-comment.outcome == 'success'
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||||
comment-author: "github-actions[bot]"
|
comment-author: "github-actions[bot]"
|
||||||
body-includes: PR Check Results
|
body-includes: "<!-- generated-comment ecosystem -->"
|
||||||
|
|
||||||
- name: Create or update comment
|
- name: Create or update comment
|
||||||
if: steps.find-comment.outcome == 'success'
|
if: steps.find-comment.outcome == 'success'
|
||||||
|
|||||||
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -48,7 +48,6 @@ jobs:
|
|||||||
args: --out dist
|
args: --out dist
|
||||||
- name: "Test sdist"
|
- name: "Test sdist"
|
||||||
run: |
|
run: |
|
||||||
rustup default $(cat rust-toolchain)
|
|
||||||
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
|
|||||||
@@ -13,3 +13,8 @@ MD041: false
|
|||||||
|
|
||||||
# MD013/line-length
|
# MD013/line-length
|
||||||
MD013: false
|
MD013: false
|
||||||
|
|
||||||
|
# MD024/no-duplicate-heading
|
||||||
|
MD024:
|
||||||
|
# Allow when nested under different parents e.g. CHANGELOG.md
|
||||||
|
allow_different_nesting: true
|
||||||
|
|||||||
@@ -13,24 +13,35 @@ exclude: |
|
|||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
rev: v0.12.1
|
rev: v0.15
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
- repo: https://github.com/executablebooks/mdformat
|
- repo: https://github.com/executablebooks/mdformat
|
||||||
rev: 0.7.16
|
rev: 0.7.17
|
||||||
hooks:
|
hooks:
|
||||||
- id: mdformat
|
- id: mdformat
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- mdformat-mkdocs
|
- mdformat-mkdocs
|
||||||
|
- mdformat-admon
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
docs/formatter/black\.md
|
||||||
|
| docs/\w+\.md
|
||||||
|
)$
|
||||||
|
|
||||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||||
rev: v0.33.0
|
rev: v0.37.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: markdownlint-fix
|
- id: markdownlint-fix
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
docs/formatter/black\.md
|
||||||
|
| docs/\w+\.md
|
||||||
|
)$
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.14.12
|
rev: v1.16.22
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|
||||||
@@ -42,10 +53,13 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
types: [rust]
|
types: [rust]
|
||||||
pass_filenames: false # This makes it a lot faster
|
pass_filenames: false # This makes it a lot faster
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.1.4
|
||||||
|
hooks:
|
||||||
|
- id: ruff-format
|
||||||
- id: ruff
|
- id: ruff
|
||||||
name: ruff
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
|
|
||||||
language: system
|
|
||||||
types_or: [python, pyi]
|
types_or: [python, pyi]
|
||||||
require_serial: true
|
require_serial: true
|
||||||
exclude: |
|
exclude: |
|
||||||
@@ -54,15 +68,9 @@ repos:
|
|||||||
crates/ruff_python_formatter/resources/.*
|
crates/ruff_python_formatter/resources/.*
|
||||||
)$
|
)$
|
||||||
|
|
||||||
# Black
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 23.1.0
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
|
|
||||||
# Prettier
|
# Prettier
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.0
|
rev: v3.0.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types: [yaml]
|
types: [yaml]
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
# Breaking Changes
|
# Breaking Changes
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
### The deprecated `format` setting has been removed
|
||||||
|
|
||||||
|
Ruff previously used the `format` setting, `--format` CLI option, and `RUFF_FORMAT` environment variable to
|
||||||
|
configure the output format of the CLI. This usage was deprecated in `v0.0.291` — the `format` setting is now used
|
||||||
|
to control Ruff's code formatting. As of this release:
|
||||||
|
|
||||||
|
- The `format` setting cannot be used to configure the output format, use `output-format` instead
|
||||||
|
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
|
||||||
|
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
|
||||||
|
|
||||||
|
### Unsafe fixes are not applied by default ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||||
|
|
||||||
|
Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe
|
||||||
|
fixes, but the meaning could be changed when applying unsafe fixes. Previously, unsafe fixes were always displayed
|
||||||
|
and applied when fixing was enabled. Now, unsafe fixes are hidden by default and not applied. The `--unsafe-fixes`
|
||||||
|
flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.
|
||||||
|
|
||||||
|
See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.
|
||||||
|
|
||||||
|
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
|
||||||
|
|
||||||
|
Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
|
||||||
|
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,
|
||||||
|
the stable rule set no longer includes `line-too-long` (`E501`) and `mixed-spaces-and-tabs` (`E101`). Other
|
||||||
|
excluded Pycodestyle rules include whitespace enforcement in `E1` and `E2`; these rules are currently in preview, and are already omitted by default.
|
||||||
|
|
||||||
|
This change only affects those using Ruff under its default rule set. Users that include `E` in their `select` will experience no change in behavior.
|
||||||
|
|
||||||
## 0.0.288
|
## 0.0.288
|
||||||
|
|
||||||
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
|
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
|
||||||
|
|||||||
384
CHANGELOG.md
Normal file
384
CHANGELOG.md
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`flake8-bandit`\] Implement `mako-templates` (`S702`) ([#8533](https://github.com/astral-sh/ruff/pull/8533))
|
||||||
|
- \[`flake8-trio`\] Implement `TRIO105` ([#8490](https://github.com/astral-sh/ruff/pull/8490))
|
||||||
|
- \[`flake8-trio`\] Implement `TRIO109` ([#8534](https://github.com/astral-sh/ruff/pull/8534))
|
||||||
|
- \[`flake8-trio`\] Implement `TRIO110` ([#8537](https://github.com/astral-sh/ruff/pull/8537))
|
||||||
|
- \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486))
|
||||||
|
- \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487))
|
||||||
|
- Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491))
|
||||||
|
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- Fix multiline lambda expression statement formatting ([#8466](https://github.com/astral-sh/ruff/pull/8466))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Add hidden `--extension` to override inference of source type from file extension ([#8373](https://github.com/astral-sh/ruff/pull/8373))
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Account for selector specificity when merging `extend_unsafe_fixes` and `override extend_safe_fixes` ([#8444](https://github.com/astral-sh/ruff/pull/8444))
|
||||||
|
- Add support for disabling cache with `RUFF_NO_CACHE` environment variable ([#8538](https://github.com/astral-sh/ruff/pull/8538))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- \[`E721`\] Flag comparisons to `memoryview` ([#8485](https://github.com/astral-sh/ruff/pull/8485))
|
||||||
|
- Allow collapsed-ellipsis bodies in other statements ([#8499](https://github.com/astral-sh/ruff/pull/8499))
|
||||||
|
- Avoid `D301` autofix for `u` prefixed strings ([#8495](https://github.com/astral-sh/ruff/pull/8495))
|
||||||
|
- Only flag `flake8-trio` rules when `trio` import is present ([#8550](https://github.com/astral-sh/ruff/pull/8550))
|
||||||
|
- Reject more syntactically invalid Python programs ([#8524](https://github.com/astral-sh/ruff/pull/8524))
|
||||||
|
- Avoid raising `TRIO115` violations for `trio.sleep(...)` calls with non-number values ([#8532](https://github.com/astral-sh/ruff/pull/8532))
|
||||||
|
- Fix `F841` false negative on assignment to multiple variables ([#8489](https://github.com/astral-sh/ruff/pull/8489))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fix link to isort `known-first-party` ([#8562](https://github.com/astral-sh/ruff/pull/8562))
|
||||||
|
- Add notes on fix safety to a few rules ([#8500](https://github.com/astral-sh/ruff/pull/8500))
|
||||||
|
- Add missing toml config tabs ([#8512](https://github.com/astral-sh/ruff/pull/8512))
|
||||||
|
- Add instructions for configuration of Emacs ([#8488](https://github.com/astral-sh/ruff/pull/8488))
|
||||||
|
- Improve detail link contrast in dark mode ([#8548](https://github.com/astral-sh/ruff/pull/8548))
|
||||||
|
- Fix typo in example ([#8506](https://github.com/astral-sh/ruff/pull/8506))
|
||||||
|
- Added tabs for configuration files in the documentation ([#8480](https://github.com/astral-sh/ruff/pull/8480))
|
||||||
|
- Recommend `project.requires-python` over `target-version` ([#8513](https://github.com/astral-sh/ruff/pull/8513))
|
||||||
|
- Add singleton escape hatch to `B008` documentation ([#8501](https://github.com/astral-sh/ruff/pull/8501))
|
||||||
|
- Fix tab configuration docs ([#8502](https://github.com/astral-sh/ruff/pull/8502))
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`flake8-trio`\] Implement `timeout-without-await` (`TRIO001`) ([#8439](https://github.com/astral-sh/ruff/pull/8439))
|
||||||
|
- \[`numpy`\] Implement NumPy 2.0 migration rule (`NPY200`) ([#7702](https://github.com/astral-sh/ruff/pull/7702))
|
||||||
|
- \[`pylint`\] Implement `bad-open-mode` (`W1501`) ([#8294](https://github.com/astral-sh/ruff/pull/8294))
|
||||||
|
- \[`pylint`\] Implement `import-outside-toplevel` (`C0415`) rule ([#5180](https://github.com/astral-sh/ruff/pull/5180))
|
||||||
|
- \[`pylint`\] Implement `useless-with-lock` (`W2101`) ([#8321](https://github.com/astral-sh/ruff/pull/8321))
|
||||||
|
- \[`pyupgrade`\] Implement `timeout-error-alias` (`UP041`) ([#8476](https://github.com/astral-sh/ruff/pull/8476))
|
||||||
|
- \[`refurb`\] Implement `isinstance-type-none` (`FURB168`) ([#8308](https://github.com/astral-sh/ruff/pull/8308))
|
||||||
|
- Detect confusable Unicode-to-Unicode units in `RUF001`, `RUF002`, and `RUF003` ([#4430](https://github.com/astral-sh/ruff/pull/4430))
|
||||||
|
- Add newline after module docstrings in preview style ([#8283](https://github.com/astral-sh/ruff/pull/8283))
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- Add a note on line-too-long to the formatter docs ([#8314](https://github.com/astral-sh/ruff/pull/8314))
|
||||||
|
- Preserve trailing statement semicolons when using `fmt: skip` ([#8273](https://github.com/astral-sh/ruff/pull/8273))
|
||||||
|
- Preserve trailing semicolons when using `fmt: off` ([#8275](https://github.com/astral-sh/ruff/pull/8275))
|
||||||
|
- Avoid duplicating linter-formatter compatibility warnings ([#8292](https://github.com/astral-sh/ruff/pull/8292))
|
||||||
|
- Avoid inserting a newline after function docstrings ([#8375](https://github.com/astral-sh/ruff/pull/8375))
|
||||||
|
- Insert newline between docstring and following own line comment ([#8216](https://github.com/astral-sh/ruff/pull/8216))
|
||||||
|
- Split tuples in return positions by comma first ([#8280](https://github.com/astral-sh/ruff/pull/8280))
|
||||||
|
- Avoid treating byte strings as docstrings ([#8350](https://github.com/astral-sh/ruff/pull/8350))
|
||||||
|
- Add `--line-length` option to `format` command ([#8363](https://github.com/astral-sh/ruff/pull/8363))
|
||||||
|
- Avoid parenthesizing unsplittable because of comments ([#8431](https://github.com/astral-sh/ruff/pull/8431))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Add `--output-format` to `ruff rule` and `ruff linter` ([#8203](https://github.com/astral-sh/ruff/pull/8203))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Respect `--force-exclude` in `lint.exclude` and `format.exclude` ([#8393](https://github.com/astral-sh/ruff/pull/8393))
|
||||||
|
- Respect `--extend-per-file-ignores` on the CLI ([#8329](https://github.com/astral-sh/ruff/pull/8329))
|
||||||
|
- Extend `bad-dunder-method-name` to permit `__index__` ([#8300](https://github.com/astral-sh/ruff/pull/8300))
|
||||||
|
- Fix panic with 8 in octal escape ([#8356](https://github.com/astral-sh/ruff/pull/8356))
|
||||||
|
- Avoid raising `D300` when both triple quote styles are present ([#8462](https://github.com/astral-sh/ruff/pull/8462))
|
||||||
|
- Consider unterminated f-strings in `FStringRanges` ([#8154](https://github.com/astral-sh/ruff/pull/8154))
|
||||||
|
- Avoid including literal `shell=True` for truthy, non-`True` diagnostics ([#8359](https://github.com/astral-sh/ruff/pull/8359))
|
||||||
|
- Avoid triggering single-element test for starred expressions ([#8433](https://github.com/astral-sh/ruff/pull/8433))
|
||||||
|
- Detect and ignore Jupyter automagics ([#8398](https://github.com/astral-sh/ruff/pull/8398))
|
||||||
|
- Fix invalid E231 error with f-strings ([#8369](https://github.com/astral-sh/ruff/pull/8369))
|
||||||
|
- Avoid triggering `NamedTuple` rewrite with starred annotation ([#8434](https://github.com/astral-sh/ruff/pull/8434))
|
||||||
|
- Avoid un-setting bracket flag in logical lines ([#8380](https://github.com/astral-sh/ruff/pull/8380))
|
||||||
|
- Place 'r' prefix before 'f' for raw format strings ([#8464](https://github.com/astral-sh/ruff/pull/8464))
|
||||||
|
- Remove trailing periods from NumPy 2.0 code actions ([#8475](https://github.com/astral-sh/ruff/pull/8475))
|
||||||
|
- Fix bug where `PLE1307` was raised when formatting `%c` with characters ([#8407](https://github.com/astral-sh/ruff/pull/8407))
|
||||||
|
- Remove unicode flag from comparable ([#8440](https://github.com/astral-sh/ruff/pull/8440))
|
||||||
|
- Improve B015 message ([#8295](https://github.com/astral-sh/ruff/pull/8295))
|
||||||
|
- Use `fixedOverflowWidgets` for playground popover ([#8458](https://github.com/astral-sh/ruff/pull/8458))
|
||||||
|
- Mark `byte_bounds` as a non-backwards-compatible NumPy 2.0 change ([#8474](https://github.com/astral-sh/ruff/pull/8474))
|
||||||
|
|
||||||
|
### Internals
|
||||||
|
|
||||||
|
- Add a dedicated cache directory per Ruff version ([#8333](https://github.com/astral-sh/ruff/pull/8333))
|
||||||
|
- Allow selective caching for `--fix` and `--diff` ([#8316](https://github.com/astral-sh/ruff/pull/8316))
|
||||||
|
- Improve performance of comment parsing ([#8193](https://github.com/astral-sh/ruff/pull/8193))
|
||||||
|
- Improve performance of string parsing ([#8227](https://github.com/astral-sh/ruff/pull/8227))
|
||||||
|
- Use a dedicated sort key for isort import sorting ([#7963](https://github.com/astral-sh/ruff/pull/7963))
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
This release includes a variety of improvements to the Ruff formatter, removing several known and
|
||||||
|
unintentional deviations from Black.
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- Avoid space around pow for `None`, `True` and `False` ([#8189](https://github.com/astral-sh/ruff/pull/8189))
|
||||||
|
- Avoid sorting all paths in the format command ([#8181](https://github.com/astral-sh/ruff/pull/8181))
|
||||||
|
- Insert necessary blank line between class and leading comments ([#8224](https://github.com/astral-sh/ruff/pull/8224))
|
||||||
|
- Avoid introducing new parentheses in annotated assignments ([#8233](https://github.com/astral-sh/ruff/pull/8233))
|
||||||
|
- Refine the warnings about incompatible linter options ([#8196](https://github.com/astral-sh/ruff/pull/8196))
|
||||||
|
- Add test and basic implementation for formatter preview mode ([#8044](https://github.com/astral-sh/ruff/pull/8044))
|
||||||
|
- Refine warning about incompatible `isort` settings ([#8192](https://github.com/astral-sh/ruff/pull/8192))
|
||||||
|
- Only omit optional parentheses for starting or ending with parentheses ([#8238](https://github.com/astral-sh/ruff/pull/8238))
|
||||||
|
- Use source type to determine parser mode for formatting ([#8205](https://github.com/astral-sh/ruff/pull/8205))
|
||||||
|
- Don't warn about magic trailing comma when `isort.force-single-line` is true ([#8244](https://github.com/astral-sh/ruff/pull/8244))
|
||||||
|
- Use `SourceKind::diff` for formatter ([#8240](https://github.com/astral-sh/ruff/pull/8240))
|
||||||
|
- Fix `fmt:off` with trailing child comment ([#8234](https://github.com/astral-sh/ruff/pull/8234))
|
||||||
|
- Formatter parentheses support for `IpyEscapeCommand` ([#8207](https://github.com/astral-sh/ruff/pull/8207))
|
||||||
|
|
||||||
|
### Linter
|
||||||
|
|
||||||
|
- \[`pylint`\] Add buffer methods to `bad-dunder-method-name` (`PLW3201`) exclusions ([#8190](https://github.com/astral-sh/ruff/pull/8190))
|
||||||
|
- Match rule prefixes from `external` codes setting in `unused-noqa` ([#8177](https://github.com/astral-sh/ruff/pull/8177))
|
||||||
|
- Use `line-length` setting for isort in lieu of `pycodestyle.max-line-length` ([#8235](https://github.com/astral-sh/ruff/pull/8235))
|
||||||
|
- Update fix for `unnecessary-paren-on-raise-exception` to unsafe for unknown types ([#8231](https://github.com/astral-sh/ruff/pull/8231))
|
||||||
|
- Correct quick fix message for `W605` ([#8255](https://github.com/astral-sh/ruff/pull/8255))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fix typo in max-doc-length documentation ([#8201](https://github.com/astral-sh/ruff/pull/8201))
|
||||||
|
- Improve documentation around linter-formatter conflicts ([#8257](https://github.com/astral-sh/ruff/pull/8257))
|
||||||
|
- Fix link to error suppression documentation in `unused-noqa` ([#8172](https://github.com/astral-sh/ruff/pull/8172))
|
||||||
|
- Add `external` option to `unused-noqa` documentation ([#8171](https://github.com/astral-sh/ruff/pull/8171))
|
||||||
|
- Add title attribute to icons ([#8060](https://github.com/astral-sh/ruff/pull/8060))
|
||||||
|
- Clarify unsafe case in RSE102 ([#8256](https://github.com/astral-sh/ruff/pull/8256))
|
||||||
|
- Fix skipping formatting examples ([#8210](https://github.com/astral-sh/ruff/pull/8210))
|
||||||
|
- docs: fix name of `magic-trailing-comma` option in README ([#8200](https://github.com/astral-sh/ruff/pull/8200))
|
||||||
|
- Add note about scope of rule changing in versioning policy ([#8169](https://github.com/astral-sh/ruff/pull/8169))
|
||||||
|
- Document: Fix default lint rules ([#8218](https://github.com/astral-sh/ruff/pull/8218))
|
||||||
|
- Fix a wrong setting in configuration.md ([#8186](https://github.com/astral-sh/ruff/pull/8186))
|
||||||
|
- Fix misspelled TOML headers in the tutorial ([#8209](https://github.com/astral-sh/ruff/pull/8209))
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
This release includes the Beta version of the Ruff formatter — an extremely fast, Black-compatible Python formatter.
|
||||||
|
Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blog/the-ruff-formatter) and [read the docs](https://docs.astral.sh/ruff/formatter/).
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`pylint`\] Implement `non-ascii-module-import` (`C2403`) ([#8056](https://github.com/astral-sh/ruff/pull/8056))
|
||||||
|
- \[`pylint`\] implement `non-ascii-name` (`C2401`) ([#8038](https://github.com/astral-sh/ruff/pull/8038))
|
||||||
|
- \[`pylint`\] Implement unnecessary-lambda (W0108) ([#7953](https://github.com/astral-sh/ruff/pull/7953))
|
||||||
|
- \[`refurb`\] Implement `read-whole-file` (`FURB101`) ([#7682](https://github.com/astral-sh/ruff/pull/7682))
|
||||||
|
- Add fix for `E223`, `E224`, and `E242` ([#8143](https://github.com/astral-sh/ruff/pull/8143))
|
||||||
|
- Add fix for `E225`, `E226`, `E227`, and `E228` ([#8136](https://github.com/astral-sh/ruff/pull/8136))
|
||||||
|
- Add fix for `E252` ([#8142](https://github.com/astral-sh/ruff/pull/8142))
|
||||||
|
- Add fix for `E261` ([#8114](https://github.com/astral-sh/ruff/pull/8114))
|
||||||
|
- Add fix for `E273` and `E274` ([#8144](https://github.com/astral-sh/ruff/pull/8144))
|
||||||
|
- Add fix for `E275` ([#8133](https://github.com/astral-sh/ruff/pull/8133))
|
||||||
|
- Update `SIM401` to catch ternary operations ([#7415](https://github.com/astral-sh/ruff/pull/7415))
|
||||||
|
- Update `E721` to allow `is` and `is` not for direct type comparisons ([#7905](https://github.com/astral-sh/ruff/pull/7905))
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113))
|
||||||
|
- Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140))
|
||||||
|
- Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097))
|
||||||
|
- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108))
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- Change `line-ending` default to `auto` ([#8057](https://github.com/astral-sh/ruff/pull/8057))
|
||||||
|
- Respect parenthesized generators in `has_own_parentheses` ([#8100](https://github.com/astral-sh/ruff/pull/8100))
|
||||||
|
- Add caching to formatter ([#8089](https://github.com/astral-sh/ruff/pull/8089))
|
||||||
|
- Remove `--line-length` option from `format` command ([#8131](https://github.com/astral-sh/ruff/pull/8131))
|
||||||
|
- Add formatter to `line-length` documentation ([#8150](https://github.com/astral-sh/ruff/pull/8150))
|
||||||
|
- Warn about incompatible formatter options ([#8088](https://github.com/astral-sh/ruff/pull/8088))
|
||||||
|
- Fix range of unparenthesized tuple subject in match statement ([#8101](https://github.com/astral-sh/ruff/pull/8101))
|
||||||
|
- Remove experimental formatter warning ([#8148](https://github.com/astral-sh/ruff/pull/8148))
|
||||||
|
- Don't move type param opening parenthesis comment ([#8163](https://github.com/astral-sh/ruff/pull/8163))
|
||||||
|
- Update versions in format benchmark script ([#8110](https://github.com/astral-sh/ruff/pull/8110))
|
||||||
|
- Avoid loading files for cached format results ([#8134](https://github.com/astral-sh/ruff/pull/8134))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Show the `ruff format` command in help menus ([#8167](https://github.com/astral-sh/ruff/pull/8167))
|
||||||
|
- Add `ruff version` command with long version display ([#8034](https://github.com/astral-sh/ruff/pull/8034))
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- New `pycodestyle.max-line-length` option ([#8039](https://github.com/astral-sh/ruff/pull/8039))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Detect `sys.version_info` slices in `outdated-version-block` ([#8112](https://github.com/astral-sh/ruff/pull/8112))
|
||||||
|
- Avoid if-else simplification for `TYPE_CHECKING` blocks ([#8072](https://github.com/astral-sh/ruff/pull/8072))
|
||||||
|
- Avoid false-positive print separator diagnostic with starred argument ([#8079](https://github.com/astral-sh/ruff/pull/8079))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fix message for `too-many-arguments` lint ([#8092](https://github.com/astral-sh/ruff/pull/8092))
|
||||||
|
- Fix `extend-unsafe-fixes` and `extend-safe-fixes` example ([#8139](https://github.com/astral-sh/ruff/pull/8139))
|
||||||
|
- Add links to `flake8-import-conventions` options ([#8115](https://github.com/astral-sh/ruff/pull/8115))
|
||||||
|
- Rework the documentation to incorporate the Ruff formatter ([#7732](https://github.com/astral-sh/ruff/pull/7732))
|
||||||
|
- Fix `Options` JSON schema description ([#8081](https://github.com/astral-sh/ruff/pull/8081))
|
||||||
|
- Fix typo (`pytext` -> `pytest`) ([#8117](https://github.com/astral-sh/ruff/pull/8117))
|
||||||
|
- Improve `magic-value-comparison` example in docs ([#8111](https://github.com/astral-sh/ruff/pull/8111))
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- Add unsafe fix for `escape-sequence-in-docstring` (`D301`) ([#7970](https://github.com/astral-sh/ruff/pull/7970))
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Respect `#(deprecated)` attribute in configuration options ([#8035](https://github.com/astral-sh/ruff/pull/8035))
|
||||||
|
- Add `[format|lint].exclude` options ([#8000](https://github.com/astral-sh/ruff/pull/8000))
|
||||||
|
- Respect `tab-size` setting in formatter ([#8006](https://github.com/astral-sh/ruff/pull/8006))
|
||||||
|
- Add `lint.preview` ([#8002](https://github.com/astral-sh/ruff/pull/8002))
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- \[`pylint`\] Implement `literal-membership` (`PLR6201`) ([#7973](https://github.com/astral-sh/ruff/pull/7973))
|
||||||
|
- \[`pylint`\] Implement `too-many-boolean-expressions` (`PLR0916`) ([#7975](https://github.com/astral-sh/ruff/pull/7975))
|
||||||
|
- \[`pylint`\] Implement `misplaced-bare-raise` (`E0704`) ([#7961](https://github.com/astral-sh/ruff/pull/7961))
|
||||||
|
- \[`pylint`\] Implement `global-at-module-level` (`W0604`) ([#8058](https://github.com/astral-sh/ruff/pull/8058))
|
||||||
|
- \[`pylint`\] Implement `unspecified-encoding` (`PLW1514`) ([#7939](https://github.com/astral-sh/ruff/pull/7939))
|
||||||
|
- Add fix for `triple-single-quotes` (`D300`) ([#7967](https://github.com/astral-sh/ruff/pull/7967))
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- New code style badge for `ruff format` ([#7878](https://github.com/astral-sh/ruff/pull/7878))
|
||||||
|
- Fix comments outside expression parentheses ([#7873](https://github.com/astral-sh/ruff/pull/7873))
|
||||||
|
- Add `--target-version` to `ruff format` ([#8055](https://github.com/astral-sh/ruff/pull/8055))
|
||||||
|
- Skip over parentheses when detecting `in` keyword ([#8054](https://github.com/astral-sh/ruff/pull/8054))
|
||||||
|
- Add `--diff` option to `ruff format` ([#7937](https://github.com/astral-sh/ruff/pull/7937))
|
||||||
|
- Insert newline after nested function or class statements ([#7946](https://github.com/astral-sh/ruff/pull/7946))
|
||||||
|
- Use `pass` over ellipsis in non-function/class contexts ([#8049](https://github.com/astral-sh/ruff/pull/8049))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Lazily evaluate all PEP 695 type alias values ([#8033](https://github.com/astral-sh/ruff/pull/8033))
|
||||||
|
- Avoid failed assertion when showing fixes from stdin ([#8029](https://github.com/astral-sh/ruff/pull/8029))
|
||||||
|
- Avoid flagging HTTP and HTTPS literals in urllib-open ([#8046](https://github.com/astral-sh/ruff/pull/8046))
|
||||||
|
- Avoid flagging `bad-dunder-method-name` for `_` ([#8015](https://github.com/astral-sh/ruff/pull/8015))
|
||||||
|
- Remove Python 2-only methods from `URLOpen` audit ([#8047](https://github.com/astral-sh/ruff/pull/8047))
|
||||||
|
- Use set bracket replacement for `iteration-over-set` to preserve whitespace and comments ([#8001](https://github.com/astral-sh/ruff/pull/8001))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Update tutorial to match revised Ruff defaults ([#8066](https://github.com/astral-sh/ruff/pull/8066))
|
||||||
|
- Update rule `B005` docs ([#8028](https://github.com/astral-sh/ruff/pull/8028))
|
||||||
|
- Update GitHub actions example in docs to use `--output-format` ([#8014](https://github.com/astral-sh/ruff/pull/8014))
|
||||||
|
- Document `lint.preview` and `format.preview` ([#8032](https://github.com/astral-sh/ruff/pull/8032))
|
||||||
|
- Clarify that new rules should be added to `RuleGroup::Preview`. ([#7989](https://github.com/astral-sh/ruff/pull/7989))
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
This is the first release which uses the `CHANGELOG` file. See [GitHub Releases](https://github.com/astral-sh/ruff/releases) for prior changelog entries.
|
||||||
|
|
||||||
|
Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
- Unsafe fixes are no longer displayed or applied without opt-in ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||||
|
- Drop formatting specific rules from the default set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
|
||||||
|
- The deprecated `format` setting has been removed ([#7984](https://github.com/astral-sh/ruff/pull/7984))
|
||||||
|
- The `format` setting cannot be used to configure the output format, use `output-format` instead
|
||||||
|
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
|
||||||
|
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
|
||||||
|
|
||||||
|
### Rule changes
|
||||||
|
|
||||||
|
- Extend `reimplemented-starmap` (`FURB140`) to catch calls with a single and starred argument ([#7768](https://github.com/astral-sh/ruff/pull/7768))
|
||||||
|
- Improve cases covered by `RUF015` ([#7848](https://github.com/astral-sh/ruff/pull/7848))
|
||||||
|
- Update `SIM15` to allow `open` followed by `close` ([#7916](https://github.com/astral-sh/ruff/pull/7916))
|
||||||
|
- Respect `msgspec.Struct` default-copy semantics in `RUF012` ([#7786](https://github.com/astral-sh/ruff/pull/7786))
|
||||||
|
- Add `sqlalchemy` methods to \`flake8-boolean-trap\`\` exclusion list ([#7874](https://github.com/astral-sh/ruff/pull/7874))
|
||||||
|
- Add fix for `PLR1714` ([#7910](https://github.com/astral-sh/ruff/pull/7910))
|
||||||
|
- Add fix for `PIE804` ([#7884](https://github.com/astral-sh/ruff/pull/7884))
|
||||||
|
- Add fix for `PLC0208` ([#7887](https://github.com/astral-sh/ruff/pull/7887))
|
||||||
|
- Add fix for `PYI055` ([#7886](https://github.com/astral-sh/ruff/pull/7886))
|
||||||
|
- Update `non-pep695-type-alias` to require `--unsafe-fixes` outside of stub files ([#7836](https://github.com/astral-sh/ruff/pull/7836))
|
||||||
|
- Improve fix message for `UP018` ([#7913](https://github.com/astral-sh/ruff/pull/7913))
|
||||||
|
- Update `PLW3201` to support `Enum` [sunder names](https://docs.python.org/3/library/enum.html#supported-sunder-names) ([#7987](https://github.com/astral-sh/ruff/pull/7987))
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- Only show warnings for empty preview selectors when enabling rules ([#7842](https://github.com/astral-sh/ruff/pull/7842))
|
||||||
|
- Add `unnecessary-key-check` to simplify `key in dct and dct[key]` to `dct.get(key)` ([#7895](https://github.com/astral-sh/ruff/pull/7895))
|
||||||
|
- Add `assignment-in-assert` to prevent walrus expressions in assert statements ([#7856](https://github.com/astral-sh/ruff/pull/7856))
|
||||||
|
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
|
||||||
|
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
|
||||||
|
|
||||||
|
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Add `unsafe-fixes` setting ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||||
|
- Add `extend-safe-fixes` and `extend-unsafe-fixes` for promoting and demoting fixes ([#7841](https://github.com/astral-sh/ruff/pull/7841))
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- Added `--unsafe-fixes` option for opt-in to display and apply unsafe fixes ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||||
|
- Fix use of deprecated `--format` option in warning ([#7837](https://github.com/astral-sh/ruff/pull/7837))
|
||||||
|
- Show changed files when running under `--check` ([#7788](https://github.com/astral-sh/ruff/pull/7788))
|
||||||
|
- Write summary messages to stderr when fixing via stdin instead of omitting them ([#7838](https://github.com/astral-sh/ruff/pull/7838))
|
||||||
|
- Update fix summary message in `check --diff` to include unsafe fix hints ([#7790](https://github.com/astral-sh/ruff/pull/7790))
|
||||||
|
- Add notebook `cell` field to JSON output format ([#7664](https://github.com/astral-sh/ruff/pull/7664))
|
||||||
|
- Rename applicability levels to `Safe`, `Unsafe`, and `Display` ([#7843](https://github.com/astral-sh/ruff/pull/7843))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix bug where f-strings were allowed in match pattern literal ([#7857](https://github.com/astral-sh/ruff/pull/7857))
|
||||||
|
- Fix `SIM110` with a yield in the condition ([#7801](https://github.com/astral-sh/ruff/pull/7801))
|
||||||
|
- Preserve trailing comments in `C414` fixes ([#7775](https://github.com/astral-sh/ruff/pull/7775))
|
||||||
|
- Check sequence type before triggering `unnecessary-enumerate` `len` suggestion ([#7781](https://github.com/astral-sh/ruff/pull/7781))
|
||||||
|
- Use correct start location for class/function clause header ([#7802](https://github.com/astral-sh/ruff/pull/7802))
|
||||||
|
- Fix incorrect fixes for `SIM101` ([#7798](https://github.com/astral-sh/ruff/pull/7798))
|
||||||
|
- Format comment before parameter default correctly ([#7870](https://github.com/astral-sh/ruff/pull/7870))
|
||||||
|
- Fix `E251` false positive inside f-strings ([#7894](https://github.com/astral-sh/ruff/pull/7894))
|
||||||
|
- Allow bindings to be created and referenced within annotations ([#7885](https://github.com/astral-sh/ruff/pull/7885))
|
||||||
|
- Show per-cell diffs when analyzing notebooks over `stdin` ([#7789](https://github.com/astral-sh/ruff/pull/7789))
|
||||||
|
- Avoid curly brace escape in f-string format spec ([#7780](https://github.com/astral-sh/ruff/pull/7780))
|
||||||
|
- Fix lexing single-quoted f-string with multi-line format spec ([#7787](https://github.com/astral-sh/ruff/pull/7787))
|
||||||
|
- Consider nursery rules to be in-preview for `ruff rule` ([#7812](https://github.com/astral-sh/ruff/pull/7812))
|
||||||
|
- Report precise location for invalid conversion flag ([#7809](https://github.com/astral-sh/ruff/pull/7809))
|
||||||
|
- Visit pattern match guard as a boolean test ([#7911](https://github.com/astral-sh/ruff/pull/7911))
|
||||||
|
- Respect `--unfixable` in `ISC` rules ([#7917](https://github.com/astral-sh/ruff/pull/7917))
|
||||||
|
- Fix edge case with `PIE804` ([#7922](https://github.com/astral-sh/ruff/pull/7922))
|
||||||
|
- Show custom message in `PTH118` for `Path.joinpath` with starred arguments ([#7852](https://github.com/astral-sh/ruff/pull/7852))
|
||||||
|
- Fix false negative in `outdated-version-block` when using greater than comparisons ([#7920](https://github.com/astral-sh/ruff/pull/7920))
|
||||||
|
- Avoid converting f-strings within Django `gettext` calls ([#7898](https://github.com/astral-sh/ruff/pull/7898))
|
||||||
|
- Fix false positive in `PLR6301` ([#7933](https://github.com/astral-sh/ruff/pull/7933))
|
||||||
|
- Treat type aliases as typing-only expressions e.g. resolves false positive in `TCH004` ([#7968](https://github.com/astral-sh/ruff/pull/7968))
|
||||||
|
- Resolve `cache-dir` relative to project root ([#7962](https://github.com/astral-sh/ruff/pull/7962))
|
||||||
|
- Respect subscripted base classes in type-checking rules e.g. resolves false positive in `TCH003` ([#7954](https://github.com/astral-sh/ruff/pull/7954))
|
||||||
|
- Fix JSON schema limit for `line-length` ([#7883](https://github.com/astral-sh/ruff/pull/7883))
|
||||||
|
- Fix commented-out `coalesce` keyword ([#7876](https://github.com/astral-sh/ruff/pull/7876))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Document `reimplemented-starmap` performance effects ([#7846](https://github.com/astral-sh/ruff/pull/7846))
|
||||||
|
- Default to following the system dark/light mode ([#7888](https://github.com/astral-sh/ruff/pull/7888))
|
||||||
|
- Add documentation for fixes ([#7901](https://github.com/astral-sh/ruff/pull/7901))
|
||||||
|
- Fix typo in docs of `PLR6301` ([#7831](https://github.com/astral-sh/ruff/pull/7831))
|
||||||
|
- Update `UP038` docs to note that it results in slower code ([#7872](https://github.com/astral-sh/ruff/pull/7872))
|
||||||
|
- crlf -> cr-lf ([#7766](https://github.com/astral-sh/ruff/pull/7766))
|
||||||
|
- Add an example of an unsafe fix ([#7924](https://github.com/astral-sh/ruff/pull/7924))
|
||||||
|
- Fix documented examples for `unnecessary-subscript-reversal` ([#7774](https://github.com/astral-sh/ruff/pull/7774))
|
||||||
|
- Correct error in tuple example in ruff formatter docs ([#7822](https://github.com/astral-sh/ruff/pull/7822))
|
||||||
|
- Add versioning policy to documentation ([#7923](https://github.com/astral-sh/ruff/pull/7923))
|
||||||
|
- Fix invalid code in `FURB177` example ([#7832](https://github.com/astral-sh/ruff/pull/7832))
|
||||||
|
|
||||||
|
### Formatter
|
||||||
|
|
||||||
|
- Less scary `ruff format` message ([#7867](https://github.com/astral-sh/ruff/pull/7867))
|
||||||
|
- Remove spaces from import statements ([#7859](https://github.com/astral-sh/ruff/pull/7859))
|
||||||
|
- Formatter quoting for f-strings with triple quotes ([#7826](https://github.com/astral-sh/ruff/pull/7826))
|
||||||
|
- Update `ruff_python_formatter` generate.py comment ([#7850](https://github.com/astral-sh/ruff/pull/7850))
|
||||||
|
- Document one-call chaining deviation ([#7767](https://github.com/astral-sh/ruff/pull/7767))
|
||||||
|
- Allow f-string modifications in line-shrinking cases ([#7818](https://github.com/astral-sh/ruff/pull/7818))
|
||||||
|
- Add trailing comment deviation to README ([#7827](https://github.com/astral-sh/ruff/pull/7827))
|
||||||
|
- Add trailing zero between dot and exponential ([#7956](https://github.com/astral-sh/ruff/pull/7956))
|
||||||
|
- Force parentheses for power operations in unary expressions ([#7955](https://github.com/astral-sh/ruff/pull/7955))
|
||||||
|
|
||||||
|
### Playground
|
||||||
|
|
||||||
|
- Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824))
|
||||||
@@ -72,7 +72,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
charlie.r.marsh@gmail.com.
|
<charlie.r.marsh@gmail.com>.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ such that all crates are contained in a flat `crates` directory.
|
|||||||
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
|
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
|
||||||
`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
|
`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
|
||||||
|
|
||||||
At time of writing, the repository includes the following crates:
|
At the time of writing, the repository includes the following crates:
|
||||||
|
|
||||||
- `crates/ruff_linter`: library crate containing all lint rules and the core logic for running them.
|
- `crates/ruff_linter`: library crate containing all lint rules and the core logic for running them.
|
||||||
If you're working on a rule, this is the crate for you.
|
If you're working on a rule, this is the crate for you.
|
||||||
@@ -170,7 +170,8 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
|||||||
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
||||||
expressions, like function calls).
|
expressions, like function calls).
|
||||||
|
|
||||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`).
|
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
|
||||||
|
should be added in `RuleGroup::Preview`.
|
||||||
|
|
||||||
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||||
|
|
||||||
@@ -314,9 +315,18 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
|||||||
|
|
||||||
### Creating a new release
|
### Creating a new release
|
||||||
|
|
||||||
1. Update the version with `rg 0.0.269 --files-with-matches | xargs sed -i 's/0.0.269/0.0.270/g'`
|
We use an experimental in-house tool for managing releases.
|
||||||
1. Update `BREAKING_CHANGES.md`
|
|
||||||
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
|
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
|
||||||
|
1. Run `rooster release`; this command will:
|
||||||
|
- Generate a changelog entry in `CHANGELOG.md`
|
||||||
|
- Update versions in `pyproject.toml` and `Cargo.toml`
|
||||||
|
- Update references to versions in the `README.md` and documentation
|
||||||
|
1. The changelog should then be editorialized for consistency
|
||||||
|
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||||
|
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||||
|
1. Highlight any breaking changes in `BREAKING_CHANGES.md`
|
||||||
|
1. Create a pull request with the changelog and version updates
|
||||||
1. Merge the PR
|
1. Merge the PR
|
||||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||||
main has your merged PR as last commit
|
main has your merged PR as last commit
|
||||||
@@ -329,23 +339,26 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
|||||||
1. Attach artifacts to draft GitHub release
|
1. Attach artifacts to draft GitHub release
|
||||||
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
||||||
downstream jobs manually if needed.
|
downstream jobs manually if needed.
|
||||||
1. Create release notes in GitHub UI and promote from draft.
|
1. Publish the GitHub release
|
||||||
|
1. Open the draft release in the GitHub release section
|
||||||
|
1. Copy the changelog for the release into the GitHub release
|
||||||
|
- See previous releases for formatting of section headers
|
||||||
|
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
||||||
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
||||||
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
|
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
|
||||||
|
|
||||||
## Ecosystem CI
|
## Ecosystem CI
|
||||||
|
|
||||||
GitHub Actions will run your changes against a number of real-world projects from GitHub and
|
GitHub Actions will run your changes against a number of real-world projects from GitHub and
|
||||||
report on any diagnostic differences. You can also run those checks locally via:
|
report on any linter or formatter differences. You can also run those checks locally via:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
|
pip install -e ./python/ruff-ecosystem
|
||||||
|
ruff-ecosystem check ruff "./target/debug/ruff"
|
||||||
|
ruff-ecosystem format ruff "./target/debug/ruff"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
|
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
|
||||||
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
|
|
||||||
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
|
|
||||||
Note that this check will take a while to run.
|
|
||||||
|
|
||||||
## Benchmarking and Profiling
|
## Benchmarking and Profiling
|
||||||
|
|
||||||
@@ -876,5 +889,5 @@ By default, `src` is set to the project root. In the above example, we'd want to
|
|||||||
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
|
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
|
||||||
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
|
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
|
||||||
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
|
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
|
||||||
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
|
the same-package heuristic; but if your project contains multiple packages, you'll want to set `src`
|
||||||
explicitly.
|
explicitly.
|
||||||
|
|||||||
292
Cargo.lock
generated
292
Cargo.lock
generated
@@ -28,9 +28,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -74,9 +74,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.5.0"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
@@ -112,9 +112,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-wincon"
|
name = "anstyle-wincon"
|
||||||
version = "2.1.0"
|
version = "3.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
@@ -221,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata 0.3.8",
|
"regex-automata 0.3.9",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -313,9 +313,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.5"
|
version = "4.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3"
|
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -323,9 +323,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.4.5"
|
version = "4.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab"
|
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -376,21 +376,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.4.2"
|
version = "4.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.5.1"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clearscreen"
|
name = "clearscreen"
|
||||||
@@ -407,9 +407,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codspeed"
|
name = "codspeed"
|
||||||
version = "2.2.0"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b3238416c10f19985b52a937c5b3efc3ed7efe8f7ae263d2aab29a09bca9f57"
|
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -418,9 +418,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codspeed-criterion-compat"
|
name = "codspeed-criterion-compat"
|
||||||
version = "2.2.0"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fecc18f65b942d2b033545bb3bd8430a23eecbbe53fad3b1342fb0e5514bca7b"
|
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"codspeed",
|
"codspeed",
|
||||||
"colored",
|
"colored",
|
||||||
@@ -608,7 +608,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -872,6 +872,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@@ -1075,9 +1084,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.33.0"
|
version = "1.34.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
|
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"globset",
|
"globset",
|
||||||
@@ -1120,7 +1129,7 @@ dependencies = [
|
|||||||
"pmutil 0.6.1",
|
"pmutil 0.6.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1253,14 +1262,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.149"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libcst"
|
name = "libcst"
|
||||||
version = "0.1.0"
|
version = "1.1.0"
|
||||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd5c2ff400caac657bf794181d885491bb97cc37c376f8cb4fa3a0cc2176a053"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chic",
|
"chic",
|
||||||
"libcst_derive",
|
"libcst_derive",
|
||||||
@@ -1273,8 +1283,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libcst_derive"
|
name = "libcst_derive"
|
||||||
version = "0.1.0"
|
version = "1.1.0"
|
||||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d7f252282b20bfec6fae65d351ab1df7e4552a6270dd7bb779ca9d6135aabe9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
@@ -1298,9 +1309,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.5"
|
version = "0.4.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -1429,7 +1440,7 @@ version = "6.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"filetime",
|
"filetime",
|
||||||
"fsevent-sys",
|
"fsevent-sys",
|
||||||
@@ -1696,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1781,18 +1792,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.67"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyproject-toml"
|
name = "pyproject-toml"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
|
checksum = "0774c13ff0b8b7ebb4791c050c497aefcfe3f6a222c0829c7017161ed38391ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"pep440_rs",
|
"pep440_rs",
|
||||||
@@ -1901,6 +1912,15 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -1914,14 +1934,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.9.5"
|
version = "1.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata 0.3.8",
|
"regex-automata 0.4.3",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax 0.8.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1935,13 +1955,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.3.8"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax 0.8.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1956,6 +1982,12 @@ version = "0.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "result-like"
|
name = "result-like"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -2028,14 +2060,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_cli"
|
name = "ruff_cli"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argfile",
|
"argfile",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"cachedir",
|
"cachedir",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2049,7 +2081,6 @@ dependencies = [
|
|||||||
"insta-cmd",
|
"insta-cmd",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"itoa",
|
|
||||||
"log",
|
"log",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"notify",
|
"notify",
|
||||||
@@ -2132,6 +2163,7 @@ name = "ruff_diagnostics"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"is-macro",
|
||||||
"log",
|
"log",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2164,12 +2196,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
@@ -2235,7 +2267,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"ruff_python_trivia",
|
"ruff_python_trivia",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2261,7 +2293,7 @@ dependencies = [
|
|||||||
name = "ruff_python_ast"
|
name = "ruff_python_ast"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"insta",
|
"insta",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
@@ -2293,7 +2325,7 @@ name = "ruff_python_formatter"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"clap",
|
"clap",
|
||||||
"countme",
|
"countme",
|
||||||
"insta",
|
"insta",
|
||||||
@@ -2337,7 +2369,7 @@ dependencies = [
|
|||||||
name = "ruff_python_literal"
|
name = "ruff_python_literal"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"hexf-parse",
|
"hexf-parse",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
@@ -2351,12 +2383,13 @@ name = "ruff_python_parser"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"insta",
|
"insta",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
|
"memchr",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
@@ -2380,7 +2413,7 @@ dependencies = [
|
|||||||
name = "ruff_python_semantic"
|
name = "ruff_python_semantic"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
@@ -2414,7 +2447,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_shrinking"
|
name = "ruff_shrinking"
|
||||||
version = "0.1.0"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2485,6 +2518,7 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"globset",
|
"globset",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
"is-macro",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -2525,11 +2559,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.10"
|
version = "0.38.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
|
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -2633,24 +2667,24 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.188"
|
version = "1.0.190"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde-wasm-bindgen"
|
name = "serde-wasm-bindgen"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
|
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2659,13 +2693,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.188"
|
version = "1.0.190"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2681,9 +2715,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.107"
|
version = "1.0.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -2710,9 +2744,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
|
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
@@ -2720,14 +2754,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
|
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2756,9 +2790,9 @@ checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
|
checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
@@ -2814,15 +2848,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.25.2"
|
version = "0.25.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
|
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2838,9 +2872,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.37"
|
version = "2.0.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2858,13 +2892,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.8.0"
|
version = "3.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.4.1",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
@@ -2927,7 +2961,7 @@ dependencies = [
|
|||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2939,28 +2973,28 @@ dependencies = [
|
|||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
"test-case-core",
|
"test-case-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.49"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.49"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2993,6 +3027,22 @@ dependencies = [
|
|||||||
"tikv-jemalloc-sys",
|
"tikv-jemalloc-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-keccak"
|
name = "tiny-keccak"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@@ -3063,11 +3113,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.37"
|
version = "0.1.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
@@ -3076,20 +3125,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.26"
|
version = "0.1.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.31"
|
version = "0.1.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
"valuable",
|
||||||
@@ -3219,10 +3268,25 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode_names2"
|
name = "unicode_names2"
|
||||||
version = "0.6.0"
|
version = "1.2.0"
|
||||||
source = "git+https://github.com/youknowone/unicode_names2.git?rev=4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d5506ae2c3c1ccbdf468e52fc5ef536c2ccd981f01273a4cb81aa61021f3a5f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf",
|
"phf",
|
||||||
|
"unicode_names2_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode_names2_generator"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6dfc680313e95bc6637fa278cd7a22390c3c2cd7b8b2bd28755bc6c0fc811e7"
|
||||||
|
dependencies = [
|
||||||
|
"getopts",
|
||||||
|
"log",
|
||||||
|
"phf_codegen",
|
||||||
|
"rand",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3267,9 +3331,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.4.1"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -3279,13 +3343,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid-macro-internal"
|
name = "uuid-macro-internal"
|
||||||
version = "1.4.1"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3379,7 +3443,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3413,7 +3477,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|||||||
38
Cargo.toml
38
Cargo.toml
@@ -13,48 +13,48 @@ license = "MIT"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = { version = "1.0.69" }
|
anyhow = { version = "1.0.69" }
|
||||||
bitflags = { version = "2.3.1" }
|
bitflags = { version = "2.4.1" }
|
||||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||||
clap = { version = "4.4.5", features = ["derive"] }
|
clap = { version = "4.4.7", features = ["derive"] }
|
||||||
colored = { version = "2.0.0" }
|
colored = { version = "2.0.0" }
|
||||||
filetime = { version = "0.2.20" }
|
filetime = { version = "0.2.20" }
|
||||||
glob = { version = "0.3.1" }
|
glob = { version = "0.3.1" }
|
||||||
globset = { version = "0.4.10" }
|
globset = { version = "0.4.10" }
|
||||||
ignore = { version = "0.4.20" }
|
ignore = { version = "0.4.20" }
|
||||||
insta = { version = "1.33.0", feature = ["filters", "glob"] }
|
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||||
is-macro = { version = "0.3.0" }
|
is-macro = { version = "0.3.0" }
|
||||||
itertools = { version = "0.11.0" }
|
itertools = { version = "0.11.0" }
|
||||||
|
libcst = { version = "1.1.0", default-features = false }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
memchr = "2.6.4"
|
memchr = { version = "2.6.4" }
|
||||||
once_cell = { version = "1.17.1" }
|
once_cell = { version = "1.17.1" }
|
||||||
path-absolutize = { version = "3.1.1" }
|
path-absolutize = { version = "3.1.1" }
|
||||||
proc-macro2 = { version = "1.0.67" }
|
proc-macro2 = { version = "1.0.69" }
|
||||||
quote = { version = "1.0.23" }
|
quote = { version = "1.0.23" }
|
||||||
regex = { version = "1.9.5" }
|
regex = { version = "1.10.2" }
|
||||||
rustc-hash = { version = "1.1.0" }
|
rustc-hash = { version = "1.1.0" }
|
||||||
schemars = { version = "0.8.15" }
|
schemars = { version = "0.8.15" }
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.190", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.107" }
|
serde_json = { version = "1.0.108" }
|
||||||
shellexpand = { version = "3.0.0" }
|
shellexpand = { version = "3.0.0" }
|
||||||
similar = { version = "2.2.1", features = ["inline"] }
|
similar = { version = "2.3.0", features = ["inline"] }
|
||||||
smallvec = { version = "1.11.1" }
|
smallvec = { version = "1.11.1" }
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||||
strum_macros = { version = "0.25.2" }
|
strum_macros = { version = "0.25.3" }
|
||||||
syn = { version = "2.0.37" }
|
syn = { version = "2.0.39" }
|
||||||
test-case = { version = "3.2.1" }
|
test-case = { version = "3.2.1" }
|
||||||
thiserror = { version = "1.0.49" }
|
thiserror = { version = "1.0.50" }
|
||||||
toml = { version = "0.7.8" }
|
toml = { version = "0.7.8" }
|
||||||
tracing = "0.1.37"
|
tracing = { version = "0.1.40" }
|
||||||
tracing-indicatif = "0.3.4"
|
tracing-indicatif = { version = "0.3.4" }
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
unicode-ident = "1.0.12"
|
unicode-ident = { version = "1.0.12" }
|
||||||
unicode-width = "0.1.11"
|
unicode_names2 = { version = "1.2.0" }
|
||||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
unicode-width = { version = "0.1.11" }
|
||||||
|
uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||||
wsl = { version = "0.1.0" }
|
wsl = { version = "0.1.0" }
|
||||||
|
|
||||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "03179b55ebe7e916f1722e18e8f0b87c01616d1f", default-features = false }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
25
LICENSE
25
LICENSE
@@ -1269,6 +1269,31 @@ are:
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
- flake8-trio, licensed as follows:
|
||||||
|
"""
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Zac Hatfield-Dodds
|
||||||
|
|
||||||
|
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:
|
- Pyright, licensed as follows:
|
||||||
"""
|
"""
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
108
README.md
108
README.md
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||||
|
|
||||||
An extremely fast Python linter, written in Rust.
|
An extremely fast Python linter and code formatter, written in Rust.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture align="center">
|
<picture align="center">
|
||||||
@@ -24,17 +24,16 @@ An extremely fast Python linter, written in Rust.
|
|||||||
<i>Linting the CPython codebase from scratch.</i>
|
<i>Linting the CPython codebase from scratch.</i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- ⚡️ 10-100x faster than existing linters
|
- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black)
|
||||||
- 🐍 Installable via `pip`
|
- 🐍 Installable via `pip`
|
||||||
- 🛠️ `pyproject.toml` support
|
- 🛠️ `pyproject.toml` support
|
||||||
- 🤝 Python 3.12 compatibility
|
- 🤝 Python 3.12 compatibility
|
||||||
|
- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black
|
||||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||||
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/)
|
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
|
||||||
- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the
|
of popular Flake8 plugins, like flake8-bugbear
|
||||||
built-in Flake8 rule set
|
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for
|
||||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
|
||||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
|
|
||||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
|
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
|
||||||
|
|
||||||
@@ -42,10 +41,10 @@ Ruff aims to be orders of magnitude faster than alternative tools while integrat
|
|||||||
functionality behind a single, common interface.
|
functionality behind a single, common interface.
|
||||||
|
|
||||||
Ruff can be used to replace [Flake8](https://pypi.org/project/flake8/) (plus dozens of plugins),
|
Ruff can be used to replace [Flake8](https://pypi.org/project/flake8/) (plus dozens of plugins),
|
||||||
[isort](https://pypi.org/project/isort/), [pydocstyle](https://pypi.org/project/pydocstyle/),
|
[Black](https://github.com/psf/black), [isort](https://pypi.org/project/isort/),
|
||||||
[yesqa](https://github.com/asottile/yesqa), [eradicate](https://pypi.org/project/eradicate/),
|
[pydocstyle](https://pypi.org/project/pydocstyle/), [pyupgrade](https://pypi.org/project/pyupgrade/),
|
||||||
[pyupgrade](https://pypi.org/project/pyupgrade/), and [autoflake](https://pypi.org/project/autoflake/),
|
[autoflake](https://pypi.org/project/autoflake/), and more, all while executing tens or hundreds of
|
||||||
all while executing tens or hundreds of times faster than any individual tool.
|
times faster than any individual tool.
|
||||||
|
|
||||||
Ruff is extremely actively developed and used in major open-source projects like:
|
Ruff is extremely actively developed and used in major open-source projects like:
|
||||||
|
|
||||||
@@ -126,23 +125,38 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
To run Ruff, try any of the following:
|
To run Ruff as a linter, try any of the following:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check . # Lint all files in the current directory (and any subdirectories)
|
ruff check . # Lint all files in the current directory (and any subdirectories).
|
||||||
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories)
|
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
||||||
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`
|
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
||||||
ruff check path/to/code/to/file.py # Lint `file.py`
|
ruff check path/to/code/to/file.py # Lint `file.py`.
|
||||||
|
ruff check @arguments.txt # Lint using an input file, treating its contents as newline-delimited command-line arguments.
|
||||||
```
|
```
|
||||||
|
|
||||||
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
Or, to run Ruff as a formatter:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ruff format . # Format all files in the current directory (and any subdirectories).
|
||||||
|
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
||||||
|
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
||||||
|
ruff format path/to/code/to/file.py # Format `file.py`.
|
||||||
|
ruff format @arguments.txt # Format using an input file, treating its contents as newline-delimited command-line arguments.
|
||||||
|
```
|
||||||
|
|
||||||
|
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.0.292
|
rev: v0.1.5
|
||||||
hooks:
|
hooks:
|
||||||
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
args: [ --fix ]
|
||||||
|
# Run the formatter.
|
||||||
|
- id: ruff-format
|
||||||
```
|
```
|
||||||
|
|
||||||
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
|
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
|
||||||
@@ -168,18 +182,10 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml`
|
|||||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||||
for a complete list of all configuration options).
|
for a complete list of all configuration options).
|
||||||
|
|
||||||
If left unspecified, the default configuration is equivalent to:
|
If left unspecified, Ruff's default configuration is equivalent to:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
|
||||||
select = ["E", "F"]
|
|
||||||
ignore = []
|
|
||||||
|
|
||||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
||||||
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
|
||||||
unfixable = []
|
|
||||||
|
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
exclude = [
|
exclude = [
|
||||||
".bzr",
|
".bzr",
|
||||||
@@ -207,27 +213,46 @@ exclude = [
|
|||||||
|
|
||||||
# Same as Black.
|
# Same as Black.
|
||||||
line-length = 88
|
line-length = 88
|
||||||
|
indent-width = 4
|
||||||
# Allow unused variables when underscore-prefixed.
|
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
||||||
|
|
||||||
# Assume Python 3.8
|
# Assume Python 3.8
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.ruff.mccabe]
|
[tool.ruff.lint]
|
||||||
# Unlike Flake8, default to a complexity level of 10.
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
max-complexity = 10
|
select = ["E4", "E7", "E9", "F"]
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
# Like Black, use double quotes for strings.
|
||||||
|
quote-style = "double"
|
||||||
|
|
||||||
|
# Like Black, indent with spaces, rather than tabs.
|
||||||
|
indent-style = "space"
|
||||||
|
|
||||||
|
# Like Black, respect magic trailing commas.
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
# Like Black, automatically detect the appropriate line ending.
|
||||||
|
line-ending = "auto"
|
||||||
```
|
```
|
||||||
|
|
||||||
Some configuration options can be provided via the command-line, such as those related to
|
Some configuration options can be provided via the command-line, such as those related to
|
||||||
rule enablement and disablement, file discovery, logging level, and more:
|
rule enablement and disablement, file discovery, and logging level:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check path/to/code/ --select F401 --select F403 --quiet
|
ruff check path/to/code/ --select F401 --select F403 --quiet
|
||||||
```
|
```
|
||||||
|
|
||||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` for more on the
|
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
||||||
linting command.
|
for more on the linting and formatting commands, respectively.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
@@ -237,9 +262,8 @@ linting command.
|
|||||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||||
Rust as a first-party feature.
|
Rust as a first-party feature.
|
||||||
|
|
||||||
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
|
By default, Ruff enables Flake8's `F` rules, along with a subset of the `E` rules, omitting any
|
||||||
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
|
stylistic rules that overlap with the use of a formatter, like `ruff format` or
|
||||||
stylistic rules made obsolete by the use of an autoformatter, like
|
|
||||||
[Black](https://github.com/psf/black).
|
[Black](https://github.com/psf/black).
|
||||||
|
|
||||||
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
|
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
|
||||||
@@ -290,6 +314,7 @@ quality tools, including:
|
|||||||
- [flake8-super](https://pypi.org/project/flake8-super/)
|
- [flake8-super](https://pypi.org/project/flake8-super/)
|
||||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||||
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
||||||
|
- [flake8-trio](https://pypi.org/project/flake8-trio/)
|
||||||
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
||||||
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
|
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
|
||||||
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
|
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
|
||||||
@@ -331,7 +356,7 @@ In some cases, Ruff includes a "direct" Rust port of the corresponding tool.
|
|||||||
We're grateful to the maintainers of these tools for their work, and for all
|
We're grateful to the maintainers of these tools for their work, and for all
|
||||||
the value they've provided to the Python community.
|
the value they've provided to the Python community.
|
||||||
|
|
||||||
Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
|
Ruff's formatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
|
||||||
and again draws on both API and implementation details from [Rome](https://github.com/rome/tools),
|
and again draws on both API and implementation details from [Rome](https://github.com/rome/tools),
|
||||||
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).
|
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).
|
||||||
|
|
||||||
@@ -385,11 +410,13 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
- [Mypy](https://github.com/python/mypy)
|
- [Mypy](https://github.com/python/mypy)
|
||||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||||
- [Neon](https://github.com/neondatabase/neon)
|
- [Neon](https://github.com/neondatabase/neon)
|
||||||
|
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||||
- [ONNX](https://github.com/onnx/onnx)
|
- [ONNX](https://github.com/onnx/onnx)
|
||||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||||
- [PDM](https://github.com/pdm-project/pdm)
|
- [PDM](https://github.com/pdm-project/pdm)
|
||||||
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
|
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
|
||||||
- [Pandas](https://github.com/pandas-dev/pandas)
|
- [Pandas](https://github.com/pandas-dev/pandas)
|
||||||
|
- [Pillow](https://github.com/python-pillow/Pillow)
|
||||||
- [Poetry](https://github.com/python-poetry/poetry)
|
- [Poetry](https://github.com/python-poetry/poetry)
|
||||||
- [Polars](https://github.com/pola-rs/polars)
|
- [Polars](https://github.com/pola-rs/polars)
|
||||||
- [PostHog](https://github.com/PostHog/posthog)
|
- [PostHog](https://github.com/PostHog/posthog)
|
||||||
@@ -398,6 +425,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
- [PyTorch](https://github.com/pytorch/pytorch)
|
- [PyTorch](https://github.com/pytorch/pytorch)
|
||||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||||
- [Pylint](https://github.com/PyCQA/pylint)
|
- [Pylint](https://github.com/PyCQA/pylint)
|
||||||
|
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||||
- [Reflex](https://github.com/reflex-dev/reflex)
|
- [Reflex](https://github.com/reflex-dev/reflex)
|
||||||
- [Rippling](https://rippling.com)
|
- [Rippling](https://rippling.com)
|
||||||
- [Robyn](https://github.com/sansyrox/robyn)
|
- [Robyn](https://github.com/sansyrox/robyn)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[files]
|
[files]
|
||||||
extend-exclude = ["resources", "snapshots"]
|
# https://github.com/crate-ci/typos/issues/868
|
||||||
|
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
|
||||||
|
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
hel = "hel"
|
hel = "hel"
|
||||||
|
|||||||
8
assets/badge/format.json
Normal file
8
assets/badge/format.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"label": "code style",
|
||||||
|
"message": "Ruff",
|
||||||
|
"logoSvg": "<svg width=\"510\" height=\"622\" viewBox=\"0 0 510 622\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M206.701 0C200.964 0 196.314 4.64131 196.314 10.3667V41.4667C196.314 47.192 191.663 51.8333 185.927 51.8333H156.843C151.107 51.8333 146.456 56.4746 146.456 62.2V145.133C146.456 150.859 141.806 155.5 136.069 155.5H106.986C101.249 155.5 96.5988 160.141 96.5988 165.867V222.883C96.5988 228.609 91.9484 233.25 86.2118 233.25H57.1283C51.3917 233.25 46.7413 237.891 46.7413 243.617V300.633C46.7413 306.359 42.0909 311 36.3544 311H10.387C4.6504 311 0 315.641 0 321.367V352.467C0 358.192 4.6504 362.833 10.387 362.833H145.418C151.154 362.833 155.804 367.475 155.804 373.2V430.217C155.804 435.942 151.154 440.583 145.418 440.583H116.334C110.597 440.583 105.947 445.225 105.947 450.95V507.967C105.947 513.692 101.297 518.333 95.5601 518.333H66.4766C60.74 518.333 56.0896 522.975 56.0896 528.7V611.633C56.0896 617.359 60.74 622 66.4766 622H149.572C155.309 622 159.959 617.359 159.959 611.633V570.167H201.507C207.244 570.167 211.894 565.525 211.894 559.8V528.7C211.894 522.975 216.544 518.333 222.281 518.333H251.365C257.101 518.333 261.752 513.692 261.752 507.967V476.867C261.752 471.141 266.402 466.5 272.138 466.5H301.222C306.959 466.5 311.609 461.859 311.609 456.133V425.033C311.609 419.308 316.259 414.667 321.996 414.667H351.079C356.816 414.667 361.466 410.025 361.466 404.3V373.2C361.466 367.475 366.117 362.833 371.853 362.833H400.937C406.673 362.833 411.324 358.192 411.324 352.467V321.367C411.324 315.641 415.974 311 421.711 311H450.794C456.531 311 461.181 306.359 461.181 300.633V217.7C461.181 211.975 456.531 207.333 450.794 207.333H420.672C414.936 207.333 410.285 202.692 410.285 196.967V165.867C410.285 160.141 414.936 155.5 420.672 155.5H449.756C455.492 155.5 460.143 150.859 460.143 145.133V114.033C460.143 108.308 464.793 103.667 470.53 103.667H499.613C505.35 103.667 510 99.0253 510 93.3V10.3667C510 4.64132 505.35 0 499.613 0H206.701ZM168.269 440.583C162.532 440.583 157.882 445.225 157.882 450.95V507.967C157.882 513.692 153.231 518.333 147.495 518.333H118.411C112.675 518.333 108.024 522.975 108.024 528.7V559.8C108.024 565.525 112.675 570.167 118.411 570.167H159.959V528.7C159.959 522.975 164.61 518.333 170.346 518.333H199.43C205.166 518.333 209.817 513.692 209.817 507.967V476.867C209.817 471.141 214.467 466.5 220.204 466.5H249.287C255.024 466.5 259.674 461.859 259.674 456.133V425.033C259.674 419.308 264.325 414.667 270.061 414.667H299.145C304.881 414.667 309.532 410.025 309.532 404.3V373.2C309.532 367.475 314.182 362.833 319.919 362.833H349.002C354.739 362.833 359.389 358.192 359.389 352.467V321.367C359.389 315.641 364.039 311 369.776 311H398.859C404.596 311 409.246 306.359 409.246 300.633V269.533C409.246 263.808 404.596 259.167 398.859 259.167H318.88C313.143 259.167 308.493 254.525 308.493 248.8V217.7C308.493 211.975 313.143 207.333 318.88 207.333H347.963C353.7 207.333 358.35 202.692 358.35 196.967V165.867C358.35 160.141 363.001 155.5 368.737 155.5H397.821C403.557 155.5 408.208 150.859 408.208 145.133V114.033C408.208 108.308 412.858 103.667 418.595 103.667H447.678C453.415 103.667 458.065 99.0253 458.065 93.3V62.2C458.065 56.4746 453.415 51.8333 447.678 51.8333H208.778C203.041 51.8333 198.391 56.4746 198.391 62.2V145.133C198.391 150.859 193.741 155.5 188.004 155.5H158.921C153.184 155.5 148.534 160.141 148.534 165.867V222.883C148.534 228.609 143.883 233.25 138.147 233.25H109.063C103.327 233.25 98.6762 237.891 98.6762 243.617V300.633C98.6762 306.359 103.327 311 109.063 311H197.352C203.089 311 207.739 315.641 207.739 321.367V430.217C207.739 435.942 203.089 440.583 197.352 440.583H168.269Z\" fill=\"#D7FF64\"/></svg>",
|
||||||
|
"logoWidth": 10,
|
||||||
|
"labelColor": "grey",
|
||||||
|
"color": "#261230"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
description = """
|
description = """
|
||||||
Convert Flake8 configuration files to Ruff configuration files.
|
Convert Flake8 configuration files to Ruff configuration files.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
|
|||||||
use ruff_linter::rules::flake8_tidy_imports::settings::Strictness;
|
use ruff_linter::rules::flake8_tidy_imports::settings::Strictness;
|
||||||
use ruff_linter::rules::pydocstyle::settings::Convention;
|
use ruff_linter::rules::pydocstyle::settings::Convention;
|
||||||
use ruff_linter::settings::types::PythonVersion;
|
use ruff_linter::settings::types::PythonVersion;
|
||||||
|
use ruff_linter::settings::DEFAULT_SELECTORS;
|
||||||
use ruff_linter::warn_user;
|
use ruff_linter::warn_user;
|
||||||
use ruff_workspace::options::{
|
use ruff_workspace::options::{
|
||||||
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
|
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
|
||||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintOptions,
|
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintCommonOptions,
|
||||||
McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
LintOptions, McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||||
};
|
};
|
||||||
use ruff_workspace::pyproject::Pyproject;
|
use ruff_workspace::pyproject::Pyproject;
|
||||||
|
|
||||||
@@ -25,11 +26,6 @@ use super::external_config::ExternalConfig;
|
|||||||
use super::plugin::Plugin;
|
use super::plugin::Plugin;
|
||||||
use super::{parser, plugin};
|
use super::{parser, plugin};
|
||||||
|
|
||||||
const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
|
||||||
RuleSelector::Linter(Linter::Pyflakes),
|
|
||||||
RuleSelector::Linter(Linter::Pycodestyle),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub(crate) fn convert(
|
pub(crate) fn convert(
|
||||||
config: &HashMap<String, HashMap<String, Option<String>>>,
|
config: &HashMap<String, HashMap<String, Option<String>>>,
|
||||||
external_config: &ExternalConfig,
|
external_config: &ExternalConfig,
|
||||||
@@ -103,7 +99,7 @@ pub(crate) fn convert(
|
|||||||
|
|
||||||
// Parse each supported option.
|
// Parse each supported option.
|
||||||
let mut options = Options::default();
|
let mut options = Options::default();
|
||||||
let mut lint_options = LintOptions::default();
|
let mut lint_options = LintCommonOptions::default();
|
||||||
let mut flake8_annotations = Flake8AnnotationsOptions::default();
|
let mut flake8_annotations = Flake8AnnotationsOptions::default();
|
||||||
let mut flake8_bugbear = Flake8BugbearOptions::default();
|
let mut flake8_bugbear = Flake8BugbearOptions::default();
|
||||||
let mut flake8_builtins = Flake8BuiltinsOptions::default();
|
let mut flake8_builtins = Flake8BuiltinsOptions::default();
|
||||||
@@ -437,8 +433,11 @@ pub(crate) fn convert(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lint_options != LintOptions::default() {
|
if lint_options != LintCommonOptions::default() {
|
||||||
options.lint = Some(lint_options);
|
options.lint = Some(LintOptions {
|
||||||
|
common: lint_options,
|
||||||
|
..LintOptions::default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the pyproject.toml.
|
// Create the pyproject.toml.
|
||||||
@@ -469,7 +468,9 @@ mod tests {
|
|||||||
use ruff_linter::rules::flake8_quotes;
|
use ruff_linter::rules::flake8_quotes;
|
||||||
use ruff_linter::rules::pydocstyle::settings::Convention;
|
use ruff_linter::rules::pydocstyle::settings::Convention;
|
||||||
use ruff_linter::settings::types::PythonVersion;
|
use ruff_linter::settings::types::PythonVersion;
|
||||||
use ruff_workspace::options::{Flake8QuotesOptions, LintOptions, Options, PydocstyleOptions};
|
use ruff_workspace::options::{
|
||||||
|
Flake8QuotesOptions, LintCommonOptions, LintOptions, Options, PydocstyleOptions,
|
||||||
|
};
|
||||||
use ruff_workspace::pyproject::Pyproject;
|
use ruff_workspace::pyproject::Pyproject;
|
||||||
|
|
||||||
use crate::converter::DEFAULT_SELECTORS;
|
use crate::converter::DEFAULT_SELECTORS;
|
||||||
@@ -479,8 +480,8 @@ mod tests {
|
|||||||
use super::super::plugin::Plugin;
|
use super::super::plugin::Plugin;
|
||||||
use super::convert;
|
use super::convert;
|
||||||
|
|
||||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintOptions {
|
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintCommonOptions {
|
||||||
LintOptions {
|
LintCommonOptions {
|
||||||
ignore: Some(vec![]),
|
ignore: Some(vec![]),
|
||||||
select: Some(
|
select: Some(
|
||||||
DEFAULT_SELECTORS
|
DEFAULT_SELECTORS
|
||||||
@@ -490,7 +491,7 @@ mod tests {
|
|||||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
..LintOptions::default()
|
..LintCommonOptions::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +503,10 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
lint: Some(lint_default_options([])),
|
lint: Some(LintOptions {
|
||||||
|
common: lint_default_options([]),
|
||||||
|
..LintOptions::default()
|
||||||
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@@ -520,7 +524,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||||
lint: Some(lint_default_options([])),
|
lint: Some(LintOptions {
|
||||||
|
common: lint_default_options([]),
|
||||||
|
..LintOptions::default()
|
||||||
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@@ -538,7 +545,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||||
lint: Some(lint_default_options([])),
|
lint: Some(LintOptions {
|
||||||
|
common: lint_default_options([]),
|
||||||
|
..LintOptions::default()
|
||||||
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@@ -555,7 +565,10 @@ mod tests {
|
|||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
lint: Some(lint_default_options([])),
|
lint: Some(LintOptions {
|
||||||
|
common: lint_default_options([]),
|
||||||
|
..LintOptions::default()
|
||||||
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
@@ -573,13 +586,16 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
lint: Some(LintOptions {
|
lint: Some(LintOptions {
|
||||||
flake8_quotes: Some(Flake8QuotesOptions {
|
common: LintCommonOptions {
|
||||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
flake8_quotes: Some(Flake8QuotesOptions {
|
||||||
multiline_quotes: None,
|
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||||
docstring_quotes: None,
|
multiline_quotes: None,
|
||||||
avoid_escape: None,
|
docstring_quotes: None,
|
||||||
}),
|
avoid_escape: None,
|
||||||
..lint_default_options([])
|
}),
|
||||||
|
..lint_default_options([])
|
||||||
|
},
|
||||||
|
..LintOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
@@ -601,12 +617,15 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
lint: Some(LintOptions {
|
lint: Some(LintOptions {
|
||||||
pydocstyle: Some(PydocstyleOptions {
|
common: LintCommonOptions {
|
||||||
convention: Some(Convention::Numpy),
|
pydocstyle: Some(PydocstyleOptions {
|
||||||
ignore_decorators: None,
|
convention: Some(Convention::Numpy),
|
||||||
property_decorators: None,
|
ignore_decorators: None,
|
||||||
}),
|
property_decorators: None,
|
||||||
..lint_default_options([Linter::Pydocstyle.into()])
|
}),
|
||||||
|
..lint_default_options([Linter::Pydocstyle.into()])
|
||||||
|
},
|
||||||
|
..LintOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
@@ -625,13 +644,16 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
lint: Some(LintOptions {
|
lint: Some(LintOptions {
|
||||||
flake8_quotes: Some(Flake8QuotesOptions {
|
common: LintCommonOptions {
|
||||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
flake8_quotes: Some(Flake8QuotesOptions {
|
||||||
multiline_quotes: None,
|
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||||
docstring_quotes: None,
|
multiline_quotes: None,
|
||||||
avoid_escape: None,
|
docstring_quotes: None,
|
||||||
}),
|
avoid_escape: None,
|
||||||
..lint_default_options([Linter::Flake8Quotes.into()])
|
}),
|
||||||
|
..lint_default_options([Linter::Flake8Quotes.into()])
|
||||||
|
},
|
||||||
|
..LintOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
@@ -652,7 +674,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
target_version: Some(PythonVersion::Py38),
|
target_version: Some(PythonVersion::Py38),
|
||||||
lint: Some(lint_default_options([])),
|
lint: Some(LintOptions {
|
||||||
|
common: lint_default_options([]),
|
||||||
|
..LintOptions::default()
|
||||||
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub(crate) fn collect_per_file_ignores(
|
|||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
per_file_ignores
|
per_file_ignores
|
||||||
.entry(pair.pattern)
|
.entry(pair.pattern)
|
||||||
.or_insert_with(Vec::new)
|
.or_default()
|
||||||
.push(pair.prefix);
|
.push(pair.prefix);
|
||||||
}
|
}
|
||||||
per_file_ignores
|
per_file_ignores
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ serde_json.workspace = true
|
|||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
ureq = "2.8.0"
|
ureq = "2.8.0"
|
||||||
criterion = { version = "0.5.1", default-features = false }
|
criterion = { version = "0.5.1", default-features = false }
|
||||||
codspeed-criterion-compat = { version="2.2.0", default-features = false, optional = true}
|
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ruff_linter.path = "../ruff_linter"
|
ruff_linter.path = "../ruff_linter"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_cli"
|
name = "ruff_cli"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -44,7 +44,6 @@ glob = { workspace = true }
|
|||||||
ignore = { workspace = true }
|
ignore = { workspace = true }
|
||||||
is-macro = { workspace = true }
|
is-macro = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
itoa = { version = "1.0.6" }
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
notify = { version = "6.1.1" }
|
notify = { version = "6.1.1" }
|
||||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||||
@@ -66,9 +65,9 @@ wild = { version = "2" }
|
|||||||
assert_cmd = { version = "2.0.8" }
|
assert_cmd = { version = "2.0.8" }
|
||||||
# Avoid writing colored snapshots when running tests from the terminal
|
# Avoid writing colored snapshots when running tests from the terminal
|
||||||
colored = { workspace = true, features = ["no-color"]}
|
colored = { workspace = true, features = ["no-color"]}
|
||||||
insta = { workspace = true, features = ["filters"] }
|
insta = { workspace = true, features = ["filters", "json"] }
|
||||||
insta-cmd = { version = "0.4.0" }
|
insta-cmd = { version = "0.4.0" }
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.8.1"
|
||||||
test-case = { workspace = true }
|
test-case = { workspace = true }
|
||||||
ureq = { version = "2.8.0", features = [] }
|
ureq = { version = "2.8.0", features = [] }
|
||||||
|
|
||||||
|
|||||||
80
crates/ruff_cli/build.rs
Normal file
80
crates/ruff_cli/build.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use std::{fs, path::Path, process::Command};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// The workspace root directory is not available without walking up the tree
|
||||||
|
// https://github.com/rust-lang/cargo/issues/3946
|
||||||
|
let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||||
|
.join("..")
|
||||||
|
.join("..");
|
||||||
|
|
||||||
|
commit_info(&workspace_root);
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
let target = std::env::var("TARGET").unwrap();
|
||||||
|
println!("cargo:rustc-env=RUST_HOST_TARGET={target}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_info(workspace_root: &Path) {
|
||||||
|
// If not in a git repository, do not attempt to retrieve commit information
|
||||||
|
let git_dir = workspace_root.join(".git");
|
||||||
|
if !git_dir.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let git_head_path = git_dir.join("HEAD");
|
||||||
|
println!(
|
||||||
|
"cargo:rerun-if-changed={}",
|
||||||
|
git_head_path.as_path().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let git_head_contents = fs::read_to_string(git_head_path);
|
||||||
|
if let Ok(git_head_contents) = git_head_contents {
|
||||||
|
// The contents are either a commit or a reference in the following formats
|
||||||
|
// - "<commit>" when the head is detached
|
||||||
|
// - "ref <ref>" when working on a branch
|
||||||
|
// If a commit, checking if the HEAD file has changed is sufficient
|
||||||
|
// If a ref, we need to add the head file for that ref to rebuild on commit
|
||||||
|
let mut git_ref_parts = git_head_contents.split_whitespace();
|
||||||
|
git_ref_parts.next();
|
||||||
|
if let Some(git_ref) = git_ref_parts.next() {
|
||||||
|
let git_ref_path = git_dir.join(git_ref);
|
||||||
|
println!(
|
||||||
|
"cargo:rerun-if-changed={}",
|
||||||
|
git_ref_path.as_path().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = match Command::new("git")
|
||||||
|
.arg("log")
|
||||||
|
.arg("-1")
|
||||||
|
.arg("--date=short")
|
||||||
|
.arg("--abbrev=9")
|
||||||
|
.arg("--format=%H %h %cd %(describe)")
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) if output.status.success() => output,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||||
|
let mut parts = stdout.split_whitespace();
|
||||||
|
let mut next = || parts.next().unwrap();
|
||||||
|
println!("cargo:rustc-env=RUFF_COMMIT_HASH={}", next());
|
||||||
|
println!("cargo:rustc-env=RUFF_COMMIT_SHORT_HASH={}", next());
|
||||||
|
println!("cargo:rustc-env=RUFF_COMMIT_DATE={}", next());
|
||||||
|
|
||||||
|
// Describe can fail for some commits
|
||||||
|
// https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emdescribeoptionsem
|
||||||
|
if let Some(describe) = parts.next() {
|
||||||
|
let mut describe_parts = describe.split('-');
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=RUFF_LAST_TAG={}",
|
||||||
|
describe_parts.next().unwrap()
|
||||||
|
);
|
||||||
|
// If this is the tagged commit, this component will be missing
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=RUFF_LAST_TAG_DISTANCE={}",
|
||||||
|
describe_parts.next().unwrap_or("0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
crates/ruff_cli/resources/test/fixtures/formatted.py
vendored
Normal file
1
crates/ruff_cli/resources/test/fixtures/formatted.py
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("All formatted!")
|
||||||
72
crates/ruff_cli/resources/test/fixtures/unformatted.ipynb
vendored
Normal file
72
crates/ruff_cli/resources/test/fixtures/unformatted.ipynb
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "98e1dd71-14a2-454d-9be0-061dde560b07",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import numpy\n",
|
||||||
|
"maths = (numpy.arange(100)**2).sum()\n",
|
||||||
|
"stats= numpy.asarray([1,2,3,4]).median()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "83a0b1b8",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"A markdown cell"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "ae12f012",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# A cell with IPython escape command\n",
|
||||||
|
"def some_function(foo, bar):\n",
|
||||||
|
" pass\n",
|
||||||
|
"%matplotlib inline"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "10f3bbf9",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"foo = %pwd\n",
|
||||||
|
"def some_function(foo,bar,):\n",
|
||||||
|
" # Another cell with IPython escape command\n",
|
||||||
|
" foo = %pwd\n",
|
||||||
|
" print(foo)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.10.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
3
crates/ruff_cli/resources/test/fixtures/unformatted.py
vendored
Normal file
3
crates/ruff_cli/resources/test/fixtures/unformatted.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
x = 1
|
||||||
|
y=2
|
||||||
|
z = 3
|
||||||
@@ -8,10 +8,12 @@ use ruff_linter::line_width::LineLength;
|
|||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
use ruff_linter::registry::Rule;
|
use ruff_linter::registry::Rule;
|
||||||
use ruff_linter::settings::types::{
|
use ruff_linter::settings::types::{
|
||||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
|
||||||
|
SerializationFormat, UnsafeFixes,
|
||||||
};
|
};
|
||||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||||
|
use ruff_workspace::options::PycodestyleOptions;
|
||||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@@ -48,7 +50,11 @@ pub enum Command {
|
|||||||
|
|
||||||
/// Output format
|
/// Output format
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
format: HelpFormat,
|
output_format: HelpFormat,
|
||||||
|
|
||||||
|
/// Output format (Deprecated: Use `--output-format` instead).
|
||||||
|
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||||
|
format: Option<HelpFormat>,
|
||||||
},
|
},
|
||||||
/// List or describe the available configuration options.
|
/// List or describe the available configuration options.
|
||||||
Config { option: Option<String> },
|
Config { option: Option<String> },
|
||||||
@@ -56,7 +62,11 @@ pub enum Command {
|
|||||||
Linter {
|
Linter {
|
||||||
/// Output format
|
/// Output format
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
format: HelpFormat,
|
output_format: HelpFormat,
|
||||||
|
|
||||||
|
/// Output format (Deprecated: Use `--output-format` instead).
|
||||||
|
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||||
|
format: Option<HelpFormat>,
|
||||||
},
|
},
|
||||||
/// Clear any caches in the current directory and any subdirectories.
|
/// Clear any caches in the current directory and any subdirectories.
|
||||||
#[clap(alias = "--clean")]
|
#[clap(alias = "--clean")]
|
||||||
@@ -65,9 +75,12 @@ pub enum Command {
|
|||||||
#[clap(alias = "--generate-shell-completion", hide = true)]
|
#[clap(alias = "--generate-shell-completion", hide = true)]
|
||||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||||
/// Run the Ruff formatter on the given files or directories.
|
/// Run the Ruff formatter on the given files or directories.
|
||||||
#[doc(hidden)]
|
|
||||||
#[clap(hide = true)]
|
|
||||||
Format(FormatCommand),
|
Format(FormatCommand),
|
||||||
|
/// Display Ruff's version
|
||||||
|
Version {
|
||||||
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
|
output_format: HelpFormat,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `Parser` derive is for ruff_dev, for ruff_cli `Args` would be sufficient
|
// The `Parser` derive is for ruff_dev, for ruff_cli `Args` would be sufficient
|
||||||
@@ -76,12 +89,18 @@ pub enum Command {
|
|||||||
pub struct CheckCommand {
|
pub struct CheckCommand {
|
||||||
/// List of files or directories to check.
|
/// List of files or directories to check.
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
/// Attempt to automatically fix lint violations.
|
/// Apply fixes to resolve lint violations.
|
||||||
/// Use `--no-fix` to disable.
|
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||||
#[arg(long, overrides_with("no_fix"))]
|
#[arg(long, overrides_with("no_fix"))]
|
||||||
fix: bool,
|
fix: bool,
|
||||||
#[clap(long, overrides_with("fix"), hide = true)]
|
#[clap(long, overrides_with("fix"), hide = true)]
|
||||||
no_fix: bool,
|
no_fix: bool,
|
||||||
|
/// Include fixes that may not retain the original intent of the code.
|
||||||
|
/// Use `--no-unsafe-fixes` to disable.
|
||||||
|
#[arg(long, overrides_with("no_unsafe_fixes"))]
|
||||||
|
unsafe_fixes: bool,
|
||||||
|
#[arg(long, overrides_with("unsafe_fixes"), hide = true)]
|
||||||
|
no_unsafe_fixes: bool,
|
||||||
/// Show violations with source code.
|
/// Show violations with source code.
|
||||||
/// Use `--no-show-source` to disable.
|
/// Use `--no-show-source` to disable.
|
||||||
#[arg(long, overrides_with("no_show_source"))]
|
#[arg(long, overrides_with("no_show_source"))]
|
||||||
@@ -100,8 +119,8 @@ pub struct CheckCommand {
|
|||||||
/// Run in watch mode by re-running whenever files change.
|
/// Run in watch mode by re-running whenever files change.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub watch: bool,
|
pub watch: bool,
|
||||||
/// Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`.
|
/// Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`.
|
||||||
/// Use `--no-fix-only` to disable.
|
/// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||||
#[arg(long, overrides_with("no_fix_only"))]
|
#[arg(long, overrides_with("no_fix_only"))]
|
||||||
fix_only: bool,
|
fix_only: bool,
|
||||||
#[clap(long, overrides_with("fix_only"), hide = true)]
|
#[clap(long, overrides_with("fix_only"), hide = true)]
|
||||||
@@ -110,16 +129,6 @@ pub struct CheckCommand {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
ignore_noqa: bool,
|
ignore_noqa: bool,
|
||||||
|
|
||||||
/// Output serialization format for violations. (Deprecated: Use `--output-format` instead).
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
value_enum,
|
|
||||||
env = "RUFF_FORMAT",
|
|
||||||
conflicts_with = "output_format",
|
|
||||||
hide = true
|
|
||||||
)]
|
|
||||||
pub format: Option<SerializationFormat>,
|
|
||||||
|
|
||||||
/// Output serialization format for violations.
|
/// Output serialization format for violations.
|
||||||
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
|
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
|
||||||
pub output_format: Option<SerializationFormat>,
|
pub output_format: Option<SerializationFormat>,
|
||||||
@@ -269,7 +278,7 @@ pub struct CheckCommand {
|
|||||||
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
||||||
pub dummy_variable_rgx: Option<Regex>,
|
pub dummy_variable_rgx: Option<Regex>,
|
||||||
/// Disable cache reads.
|
/// Disable cache reads.
|
||||||
#[arg(short, long, help_heading = "Miscellaneous")]
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
/// Ignore all configuration files.
|
/// Ignore all configuration files.
|
||||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||||
@@ -342,6 +351,9 @@ pub struct CheckCommand {
|
|||||||
conflicts_with = "watch",
|
conflicts_with = "watch",
|
||||||
)]
|
)]
|
||||||
pub show_settings: bool,
|
pub show_settings: bool,
|
||||||
|
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]).
|
||||||
|
#[arg(long, value_delimiter = ',', hide = true)]
|
||||||
|
pub extension: Option<Vec<ExtensionPair>>,
|
||||||
/// Dev-only argument to show fixes
|
/// Dev-only argument to show fixes
|
||||||
#[arg(long, hide = true)]
|
#[arg(long, hide = true)]
|
||||||
pub ecosystem_ci: bool,
|
pub ecosystem_ci: bool,
|
||||||
@@ -356,9 +368,21 @@ pub struct FormatCommand {
|
|||||||
/// files would have been modified, and zero otherwise.
|
/// files would have been modified, and zero otherwise.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub check: bool,
|
pub check: bool,
|
||||||
|
/// Avoid writing any formatted files back; instead, exit with a non-zero status code and the
|
||||||
|
/// difference between the current file and how the formatted file would look like.
|
||||||
|
#[arg(long)]
|
||||||
|
pub diff: bool,
|
||||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
|
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
|
||||||
#[arg(long, conflicts_with = "isolated")]
|
#[arg(long, conflicts_with = "isolated")]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Disable cache reads.
|
||||||
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
|
pub no_cache: bool,
|
||||||
|
/// Path to the cache directory.
|
||||||
|
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||||
|
pub cache_dir: Option<PathBuf>,
|
||||||
|
|
||||||
/// Respect file exclusions via `.gitignore` and other standard ignore files.
|
/// Respect file exclusions via `.gitignore` and other standard ignore files.
|
||||||
/// Use `--no-respect-gitignore` to disable.
|
/// Use `--no-respect-gitignore` to disable.
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -369,6 +393,15 @@ pub struct FormatCommand {
|
|||||||
respect_gitignore: bool,
|
respect_gitignore: bool,
|
||||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||||
no_respect_gitignore: bool,
|
no_respect_gitignore: bool,
|
||||||
|
/// List of paths, used to omit files and/or directories from analysis.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_delimiter = ',',
|
||||||
|
value_name = "FILE_PATTERN",
|
||||||
|
help_heading = "File selection"
|
||||||
|
)]
|
||||||
|
pub exclude: Option<Vec<FilePattern>>,
|
||||||
|
|
||||||
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
|
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
|
||||||
/// Use `--no-force-exclude` to disable.
|
/// Use `--no-force-exclude` to disable.
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -380,7 +413,7 @@ pub struct FormatCommand {
|
|||||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||||
no_force_exclude: bool,
|
no_force_exclude: bool,
|
||||||
/// Set the line-length.
|
/// Set the line-length.
|
||||||
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
#[arg(long, help_heading = "Format configuration")]
|
||||||
pub line_length: Option<LineLength>,
|
pub line_length: Option<LineLength>,
|
||||||
/// Ignore all configuration files.
|
/// Ignore all configuration files.
|
||||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||||
@@ -388,10 +421,12 @@ pub struct FormatCommand {
|
|||||||
/// The name of the file when passing it through stdin.
|
/// The name of the file when passing it through stdin.
|
||||||
#[arg(long, help_heading = "Miscellaneous")]
|
#[arg(long, help_heading = "Miscellaneous")]
|
||||||
pub stdin_filename: Option<PathBuf>,
|
pub stdin_filename: Option<PathBuf>,
|
||||||
|
/// The minimum Python version that should be supported.
|
||||||
/// Enable preview mode; checks will include unstable rules and fixes.
|
#[arg(long, value_enum)]
|
||||||
|
pub target_version: Option<PythonVersion>,
|
||||||
|
/// Enable preview mode; enables unstable formatting.
|
||||||
/// Use `--no-preview` to disable.
|
/// Use `--no-preview` to disable.
|
||||||
#[arg(long, overrides_with("no_preview"), hide = true)]
|
#[arg(long, overrides_with("no_preview"))]
|
||||||
preview: bool,
|
preview: bool,
|
||||||
#[clap(long, overrides_with("preview"), hide = true)]
|
#[clap(long, overrides_with("preview"), hide = true)]
|
||||||
no_preview: bool,
|
no_preview: bool,
|
||||||
@@ -478,6 +513,7 @@ impl CheckCommand {
|
|||||||
extend_exclude: self.extend_exclude,
|
extend_exclude: self.extend_exclude,
|
||||||
extend_fixable: self.extend_fixable,
|
extend_fixable: self.extend_fixable,
|
||||||
extend_ignore: self.extend_ignore,
|
extend_ignore: self.extend_ignore,
|
||||||
|
extend_per_file_ignores: self.extend_per_file_ignores,
|
||||||
extend_select: self.extend_select,
|
extend_select: self.extend_select,
|
||||||
extend_unfixable: self.extend_unfixable,
|
extend_unfixable: self.extend_unfixable,
|
||||||
fixable: self.fixable,
|
fixable: self.fixable,
|
||||||
@@ -497,9 +533,12 @@ impl CheckCommand {
|
|||||||
cache_dir: self.cache_dir,
|
cache_dir: self.cache_dir,
|
||||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||||
|
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
|
||||||
|
.map(UnsafeFixes::from),
|
||||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||||
output_format: self.output_format.or(self.format),
|
output_format: self.output_format,
|
||||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||||
|
extension: self.extension,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -512,9 +551,11 @@ impl FormatCommand {
|
|||||||
(
|
(
|
||||||
FormatArguments {
|
FormatArguments {
|
||||||
check: self.check,
|
check: self.check,
|
||||||
|
diff: self.diff,
|
||||||
config: self.config,
|
config: self.config,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
isolated: self.isolated,
|
isolated: self.isolated,
|
||||||
|
no_cache: self.no_cache,
|
||||||
stdin_filename: self.stdin_filename,
|
stdin_filename: self.stdin_filename,
|
||||||
},
|
},
|
||||||
CliOverrides {
|
CliOverrides {
|
||||||
@@ -523,8 +564,12 @@ impl FormatCommand {
|
|||||||
self.respect_gitignore,
|
self.respect_gitignore,
|
||||||
self.no_respect_gitignore,
|
self.no_respect_gitignore,
|
||||||
),
|
),
|
||||||
|
exclude: self.exclude,
|
||||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||||
|
target_version: self.target_version,
|
||||||
|
cache_dir: self.cache_dir,
|
||||||
|
|
||||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||||
..CliOverrides::default()
|
..CliOverrides::default()
|
||||||
},
|
},
|
||||||
@@ -568,6 +613,8 @@ pub struct CheckArguments {
|
|||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct FormatArguments {
|
pub struct FormatArguments {
|
||||||
pub check: bool,
|
pub check: bool,
|
||||||
|
pub no_cache: bool,
|
||||||
|
pub diff: bool,
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub isolated: bool,
|
pub isolated: bool,
|
||||||
@@ -589,6 +636,7 @@ pub struct CliOverrides {
|
|||||||
pub ignore: Option<Vec<RuleSelector>>,
|
pub ignore: Option<Vec<RuleSelector>>,
|
||||||
pub line_length: Option<LineLength>,
|
pub line_length: Option<LineLength>,
|
||||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||||
|
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||||
pub preview: Option<PreviewMode>,
|
pub preview: Option<PreviewMode>,
|
||||||
pub respect_gitignore: Option<bool>,
|
pub respect_gitignore: Option<bool>,
|
||||||
pub select: Option<Vec<RuleSelector>>,
|
pub select: Option<Vec<RuleSelector>>,
|
||||||
@@ -599,9 +647,11 @@ pub struct CliOverrides {
|
|||||||
pub cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
pub fix: Option<bool>,
|
pub fix: Option<bool>,
|
||||||
pub fix_only: Option<bool>,
|
pub fix_only: Option<bool>,
|
||||||
|
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||||
pub force_exclude: Option<bool>,
|
pub force_exclude: Option<bool>,
|
||||||
pub output_format: Option<SerializationFormat>,
|
pub output_format: Option<SerializationFormat>,
|
||||||
pub show_fixes: Option<bool>,
|
pub show_fixes: Option<bool>,
|
||||||
|
pub extension: Option<Vec<ExtensionPair>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurationTransformer for CliOverrides {
|
impl ConfigurationTransformer for CliOverrides {
|
||||||
@@ -618,12 +668,21 @@ impl ConfigurationTransformer for CliOverrides {
|
|||||||
if let Some(extend_exclude) = &self.extend_exclude {
|
if let Some(extend_exclude) = &self.extend_exclude {
|
||||||
config.extend_exclude.extend(extend_exclude.clone());
|
config.extend_exclude.extend(extend_exclude.clone());
|
||||||
}
|
}
|
||||||
|
if let Some(extend_per_file_ignores) = &self.extend_per_file_ignores {
|
||||||
|
config
|
||||||
|
.lint
|
||||||
|
.extend_per_file_ignores
|
||||||
|
.extend(collect_per_file_ignores(extend_per_file_ignores.clone()));
|
||||||
|
}
|
||||||
if let Some(fix) = &self.fix {
|
if let Some(fix) = &self.fix {
|
||||||
config.fix = Some(*fix);
|
config.fix = Some(*fix);
|
||||||
}
|
}
|
||||||
if let Some(fix_only) = &self.fix_only {
|
if let Some(fix_only) = &self.fix_only {
|
||||||
config.fix_only = Some(*fix_only);
|
config.fix_only = Some(*fix_only);
|
||||||
}
|
}
|
||||||
|
if self.unsafe_fixes.is_some() {
|
||||||
|
config.unsafe_fixes = self.unsafe_fixes;
|
||||||
|
}
|
||||||
config.lint.rule_selections.push(RuleSelection {
|
config.lint.rule_selections.push(RuleSelection {
|
||||||
select: self.select.clone(),
|
select: self.select.clone(),
|
||||||
ignore: self
|
ignore: self
|
||||||
@@ -650,11 +709,17 @@ impl ConfigurationTransformer for CliOverrides {
|
|||||||
if let Some(force_exclude) = &self.force_exclude {
|
if let Some(force_exclude) = &self.force_exclude {
|
||||||
config.force_exclude = Some(*force_exclude);
|
config.force_exclude = Some(*force_exclude);
|
||||||
}
|
}
|
||||||
if let Some(line_length) = &self.line_length {
|
if let Some(line_length) = self.line_length {
|
||||||
config.line_length = Some(*line_length);
|
config.line_length = Some(line_length);
|
||||||
|
config.lint.pycodestyle = Some(PycodestyleOptions {
|
||||||
|
max_line_length: Some(line_length),
|
||||||
|
..config.lint.pycodestyle.unwrap_or_default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if let Some(preview) = &self.preview {
|
if let Some(preview) = &self.preview {
|
||||||
config.preview = Some(*preview);
|
config.preview = Some(*preview);
|
||||||
|
config.lint.preview = Some(*preview);
|
||||||
|
config.format.preview = Some(*preview);
|
||||||
}
|
}
|
||||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||||
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||||
@@ -671,6 +736,9 @@ impl ConfigurationTransformer for CliOverrides {
|
|||||||
if let Some(target_version) = &self.target_version {
|
if let Some(target_version) = &self.target_version {
|
||||||
config.target_version = Some(*target_version);
|
config.target_version = Some(*target_version);
|
||||||
}
|
}
|
||||||
|
if let Some(extension) = &self.extension {
|
||||||
|
config.lint.extension = Some(extension.clone().into_iter().collect());
|
||||||
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
@@ -682,7 +750,7 @@ pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgn
|
|||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
per_file_ignores
|
per_file_ignores
|
||||||
.entry(pair.pattern)
|
.entry(pair.pattern)
|
||||||
.or_insert_with(Vec::new)
|
.or_default()
|
||||||
.push(pair.prefix);
|
.push(pair.prefix);
|
||||||
}
|
}
|
||||||
per_file_ignores
|
per_file_ignores
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::fmt::Debug;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::io::{self, BufReader, BufWriter, Write};
|
use std::io::{self, BufReader, BufWriter, Write};
|
||||||
@@ -8,29 +8,62 @@ use std::sync::Mutex;
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use filetime::FileTime;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::{debug, error};
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||||
use ruff_linter::message::Message;
|
use ruff_linter::message::Message;
|
||||||
use ruff_linter::warn_user;
|
use ruff_linter::{warn_user, VERSION};
|
||||||
|
use ruff_macros::CacheKey;
|
||||||
use ruff_notebook::NotebookIndex;
|
use ruff_notebook::NotebookIndex;
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy, Resolver};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
|
use crate::cache;
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
|
|
||||||
/// Maximum duration for which we keep a file in cache that hasn't been seen.
|
|
||||||
const MAX_LAST_SEEN: Duration = Duration::from_secs(30 * 24 * 60 * 60); // 30 days.
|
|
||||||
|
|
||||||
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
||||||
pub(crate) type RelativePath = Path;
|
pub(crate) type RelativePath = Path;
|
||||||
/// [`PathBuf`] that is relative to the package root in [`PackageCache`].
|
/// [`PathBuf`] that is relative to the package root in [`PackageCache`].
|
||||||
pub(crate) type RelativePathBuf = PathBuf;
|
pub(crate) type RelativePathBuf = PathBuf;
|
||||||
|
|
||||||
|
#[derive(CacheKey)]
|
||||||
|
pub(crate) struct FileCacheKey {
|
||||||
|
/// Timestamp when the file was last modified before the (cached) check.
|
||||||
|
file_last_modified: FileTime,
|
||||||
|
/// Permissions of the file before the (cached) check.
|
||||||
|
file_permissions_mode: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileCacheKey {
|
||||||
|
pub(crate) fn from_path(path: &Path) -> io::Result<FileCacheKey> {
|
||||||
|
// Construct a cache key for the file
|
||||||
|
let metadata = path.metadata()?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let permissions = {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
metadata.permissions().mode()
|
||||||
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
let permissions: u32 = metadata.permissions().readonly().into();
|
||||||
|
|
||||||
|
Ok(FileCacheKey {
|
||||||
|
file_last_modified: FileTime::from_last_modification_time(&metadata),
|
||||||
|
file_permissions_mode: permissions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Cache.
|
/// Cache.
|
||||||
///
|
///
|
||||||
/// `Cache` holds everything required to display the diagnostics for a single
|
/// `Cache` holds everything required to display the diagnostics for a single
|
||||||
@@ -50,7 +83,7 @@ pub(crate) struct Cache {
|
|||||||
/// Files that are linted, but are not in `package.files` or are in
|
/// Files that are linted, but are not in `package.files` or are in
|
||||||
/// `package.files` but are outdated. This gets merged with `package.files`
|
/// `package.files` but are outdated. This gets merged with `package.files`
|
||||||
/// when the cache is written back to disk in [`Cache::store`].
|
/// when the cache is written back to disk in [`Cache::store`].
|
||||||
new_files: Mutex<HashMap<RelativePathBuf, FileCache>>,
|
changes: Mutex<Vec<Change>>,
|
||||||
/// The "current" timestamp used as cache for the updates of
|
/// The "current" timestamp used as cache for the updates of
|
||||||
/// [`FileCache::last_seen`]
|
/// [`FileCache::last_seen`]
|
||||||
last_seen_cache: u64,
|
last_seen_cache: u64,
|
||||||
@@ -65,12 +98,11 @@ impl Cache {
|
|||||||
///
|
///
|
||||||
/// Finally `settings` is used to ensure we don't open a cache for different
|
/// Finally `settings` is used to ensure we don't open a cache for different
|
||||||
/// settings. It also defines the directory where to store the cache.
|
/// settings. It also defines the directory where to store the cache.
|
||||||
pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Cache {
|
pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Self {
|
||||||
debug_assert!(package_root.is_absolute(), "package root not canonicalized");
|
debug_assert!(package_root.is_absolute(), "package root not canonicalized");
|
||||||
|
|
||||||
let mut buf = itoa::Buffer::new();
|
let key = format!("{}", cache_key(&package_root, settings));
|
||||||
let key = Path::new(buf.format(cache_key(&package_root, settings)));
|
let path = PathBuf::from_iter([&settings.cache_dir, Path::new(VERSION), Path::new(&key)]);
|
||||||
let path = PathBuf::from_iter([&settings.cache_dir, Path::new("content"), key]);
|
|
||||||
|
|
||||||
let file = match File::open(&path) {
|
let file = match File::open(&path) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
@@ -105,46 +137,34 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an empty `Cache`.
|
/// Create an empty `Cache`.
|
||||||
fn empty(path: PathBuf, package_root: PathBuf) -> Cache {
|
fn empty(path: PathBuf, package_root: PathBuf) -> Self {
|
||||||
let package = PackageCache {
|
let package = PackageCache {
|
||||||
package_root,
|
package_root,
|
||||||
files: HashMap::new(),
|
files: FxHashMap::default(),
|
||||||
};
|
};
|
||||||
Cache::new(path, package)
|
Cache::new(path, package)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn new(path: PathBuf, package: PackageCache) -> Cache {
|
fn new(path: PathBuf, package: PackageCache) -> Self {
|
||||||
Cache {
|
Cache {
|
||||||
path,
|
path,
|
||||||
package,
|
package,
|
||||||
new_files: Mutex::new(HashMap::new()),
|
changes: Mutex::new(Vec::new()),
|
||||||
// SAFETY: this will be truncated to the year ~2554 (so don't use
|
// SAFETY: this will be truncated to the year ~2554 (so don't use
|
||||||
// this code after that!).
|
// this code after that!).
|
||||||
last_seen_cache: SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64,
|
last_seen_cache: SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store the cache to disk, if it has been changed.
|
/// Applies the pending changes and persists the cache to disk, if it has been changed.
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
pub(crate) fn persist(mut self) -> Result<()> {
|
||||||
pub(crate) fn store(mut self) -> Result<()> {
|
if !self.save() {
|
||||||
let new_files = self.new_files.into_inner().unwrap();
|
|
||||||
if new_files.is_empty() {
|
|
||||||
// No changes made, no need to write the same cache file back to
|
// No changes made, no need to write the same cache file back to
|
||||||
// disk.
|
// disk.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove cached files that we haven't seen in a while.
|
|
||||||
let now = self.last_seen_cache;
|
|
||||||
self.package.files.retain(|_, file| {
|
|
||||||
// SAFETY: this will be truncated to the year ~2554.
|
|
||||||
(now - *file.last_seen.get_mut()) <= MAX_LAST_SEEN.as_millis() as u64
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply any changes made and keep track of when we last saw files.
|
|
||||||
self.package.files.extend(new_files);
|
|
||||||
|
|
||||||
let file = File::create(&self.path)
|
let file = File::create(&self.path)
|
||||||
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
|
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
|
||||||
let writer = BufWriter::new(file);
|
let writer = BufWriter::new(file);
|
||||||
@@ -156,6 +176,53 @@ impl Cache {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies the pending changes without storing the cache to disk.
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
pub(crate) fn save(&mut self) -> bool {
|
||||||
|
/// Maximum duration for which we keep a file in cache that hasn't been seen.
|
||||||
|
const MAX_LAST_SEEN: Duration = Duration::from_secs(30 * 24 * 60 * 60); // 30 days.
|
||||||
|
|
||||||
|
let changes = std::mem::take(self.changes.get_mut().unwrap());
|
||||||
|
if changes.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove cached files that we haven't seen in a while.
|
||||||
|
let now = self.last_seen_cache;
|
||||||
|
self.package.files.retain(|_, file| {
|
||||||
|
// SAFETY: this will be truncated to the year ~2554.
|
||||||
|
(now - *file.last_seen.get_mut()) <= MAX_LAST_SEEN.as_millis() as u64
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply any changes made and keep track of when we last saw files.
|
||||||
|
for change in changes {
|
||||||
|
let entry = self
|
||||||
|
.package
|
||||||
|
.files
|
||||||
|
.entry(change.path)
|
||||||
|
.and_modify(|existing| {
|
||||||
|
if existing.key != change.new_key {
|
||||||
|
// Reset the data if the key change.
|
||||||
|
existing.data = FileCacheData::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.key = change.new_key;
|
||||||
|
existing
|
||||||
|
.last_seen
|
||||||
|
.store(self.last_seen_cache, Ordering::Relaxed);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| FileCache {
|
||||||
|
key: change.new_key,
|
||||||
|
last_seen: AtomicU64::new(self.last_seen_cache),
|
||||||
|
data: FileCacheData::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
change.new_data.apply(&mut entry.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the relative path based on `path` and the package root.
|
/// Returns the relative path based on `path` and the package root.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `path` is not within the package.
|
/// Returns `None` if `path` is not within the package.
|
||||||
@@ -169,7 +236,7 @@ impl Cache {
|
|||||||
///
|
///
|
||||||
/// This returns `None` if `key` differs from the cached key or if the
|
/// This returns `None` if `key` differs from the cached key or if the
|
||||||
/// cache doesn't contain results for the file.
|
/// cache doesn't contain results for the file.
|
||||||
pub(crate) fn get<T: CacheKey>(&self, path: &RelativePath, key: &T) -> Option<&FileCache> {
|
pub(crate) fn get(&self, path: &RelativePath, key: &FileCacheKey) -> Option<&FileCache> {
|
||||||
let file = self.package.files.get(path)?;
|
let file = self.package.files.get(path)?;
|
||||||
|
|
||||||
let mut hasher = CacheKeyHasher::new();
|
let mut hasher = CacheKeyHasher::new();
|
||||||
@@ -185,50 +252,34 @@ impl Cache {
|
|||||||
Some(file)
|
Some(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_formatted(&self, path: &RelativePath, key: &FileCacheKey) -> bool {
|
||||||
|
self.get(path, key)
|
||||||
|
.is_some_and(|entry| entry.data.formatted)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add or update a file cache at `path` relative to the package root.
|
/// Add or update a file cache at `path` relative to the package root.
|
||||||
pub(crate) fn update<T: CacheKey>(
|
fn update(&self, path: RelativePathBuf, key: &FileCacheKey, data: ChangeData) {
|
||||||
&self,
|
|
||||||
path: RelativePathBuf,
|
|
||||||
key: T,
|
|
||||||
messages: &[Message],
|
|
||||||
imports: &ImportMap,
|
|
||||||
notebook_index: Option<&NotebookIndex>,
|
|
||||||
) {
|
|
||||||
let source = if let Some(msg) = messages.first() {
|
|
||||||
msg.file.source_text().to_owned()
|
|
||||||
} else {
|
|
||||||
String::new() // No messages, no need to keep the source!
|
|
||||||
};
|
|
||||||
|
|
||||||
let messages = messages
|
|
||||||
.iter()
|
|
||||||
.map(|msg| {
|
|
||||||
// Make sure that all message use the same source file.
|
|
||||||
assert!(
|
|
||||||
msg.file == messages.first().unwrap().file,
|
|
||||||
"message uses a different source file"
|
|
||||||
);
|
|
||||||
CacheMessage {
|
|
||||||
kind: msg.kind.clone(),
|
|
||||||
range: msg.range,
|
|
||||||
fix: msg.fix.clone(),
|
|
||||||
noqa_offset: msg.noqa_offset,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut hasher = CacheKeyHasher::new();
|
let mut hasher = CacheKeyHasher::new();
|
||||||
key.cache_key(&mut hasher);
|
key.cache_key(&mut hasher);
|
||||||
|
|
||||||
let file = FileCache {
|
self.changes.lock().unwrap().push(Change {
|
||||||
key: hasher.finish(),
|
path,
|
||||||
last_seen: AtomicU64::new(self.last_seen_cache),
|
new_key: hasher.finish(),
|
||||||
imports: imports.clone(),
|
new_data: data,
|
||||||
messages,
|
});
|
||||||
source,
|
}
|
||||||
notebook_index: notebook_index.cloned(),
|
|
||||||
};
|
pub(crate) fn update_lint(
|
||||||
self.new_files.lock().unwrap().insert(path, file);
|
&self,
|
||||||
|
path: RelativePathBuf,
|
||||||
|
key: &FileCacheKey,
|
||||||
|
data: LintCacheData,
|
||||||
|
) {
|
||||||
|
self.update(path, key, ChangeData::Lint(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_formatted(&self, path: RelativePathBuf, key: &FileCacheKey) {
|
||||||
|
self.update(path, key, ChangeData::Formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +292,7 @@ struct PackageCache {
|
|||||||
/// single file "packages", e.g. scripts.
|
/// single file "packages", e.g. scripts.
|
||||||
package_root: PathBuf,
|
package_root: PathBuf,
|
||||||
/// Mapping of source file path to it's cached data.
|
/// Mapping of source file path to it's cached data.
|
||||||
files: HashMap<RelativePathBuf, FileCache>,
|
files: FxHashMap<RelativePathBuf, FileCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On disk representation of the cache per source file.
|
/// On disk representation of the cache per source file.
|
||||||
@@ -254,71 +305,59 @@ pub(crate) struct FileCache {
|
|||||||
/// Represented as the number of milliseconds since Unix epoch. This will
|
/// Represented as the number of milliseconds since Unix epoch. This will
|
||||||
/// break in 1970 + ~584 years (~2554).
|
/// break in 1970 + ~584 years (~2554).
|
||||||
last_seen: AtomicU64,
|
last_seen: AtomicU64,
|
||||||
/// Imports made.
|
|
||||||
imports: ImportMap,
|
data: FileCacheData,
|
||||||
/// Diagnostic messages.
|
|
||||||
messages: Vec<CacheMessage>,
|
|
||||||
/// Source code of the file.
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
///
|
|
||||||
/// This will be empty if `messages` is empty.
|
|
||||||
source: String,
|
|
||||||
/// Notebook index if this file is a Jupyter Notebook.
|
|
||||||
notebook_index: Option<NotebookIndex>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileCache {
|
impl FileCache {
|
||||||
/// Convert the file cache into `Diagnostics`, using `path` as file name.
|
/// Convert the file cache into `Diagnostics`, using `path` as file name.
|
||||||
pub(crate) fn as_diagnostics(&self, path: &Path) -> Diagnostics {
|
pub(crate) fn to_diagnostics(&self, path: &Path) -> Option<Diagnostics> {
|
||||||
let messages = if self.messages.is_empty() {
|
self.data.lint.as_ref().map(|lint| {
|
||||||
Vec::new()
|
let messages = if lint.messages.is_empty() {
|
||||||
} else {
|
Vec::new()
|
||||||
let file = SourceFileBuilder::new(path.to_string_lossy(), &*self.source).finish();
|
} else {
|
||||||
self.messages
|
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
|
||||||
.iter()
|
lint.messages
|
||||||
.map(|msg| Message {
|
.iter()
|
||||||
kind: msg.kind.clone(),
|
.map(|msg| Message {
|
||||||
range: msg.range,
|
kind: msg.kind.clone(),
|
||||||
fix: msg.fix.clone(),
|
range: msg.range,
|
||||||
file: file.clone(),
|
fix: msg.fix.clone(),
|
||||||
noqa_offset: msg.noqa_offset,
|
file: file.clone(),
|
||||||
})
|
noqa_offset: msg.noqa_offset,
|
||||||
.collect()
|
})
|
||||||
};
|
.collect()
|
||||||
let notebook_indexes = if let Some(notebook_index) = self.notebook_index.as_ref() {
|
};
|
||||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
|
let notebook_indexes = if let Some(notebook_index) = lint.notebook_index.as_ref() {
|
||||||
} else {
|
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
|
||||||
FxHashMap::default()
|
} else {
|
||||||
};
|
FxHashMap::default()
|
||||||
Diagnostics::new(messages, self.imports.clone(), notebook_indexes)
|
};
|
||||||
|
Diagnostics::new(messages, lint.imports.clone(), notebook_indexes)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On disk representation of a diagnostic message.
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[derive(Deserialize, Debug, Serialize)]
|
struct FileCacheData {
|
||||||
struct CacheMessage {
|
lint: Option<LintCacheData>,
|
||||||
kind: DiagnosticKind,
|
formatted: bool,
|
||||||
/// Range into the message's [`FileCache::source`].
|
|
||||||
range: TextRange,
|
|
||||||
fix: Option<Fix>,
|
|
||||||
noqa_offset: TextSize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a hash key based on the `package_root`, `settings` and the crate
|
/// Returns a hash key based on the `package_root`, `settings` and the crate
|
||||||
/// version.
|
/// version.
|
||||||
fn cache_key(package_root: &Path, settings: &Settings) -> u64 {
|
fn cache_key(package_root: &Path, settings: &Settings) -> u64 {
|
||||||
let mut hasher = CacheKeyHasher::new();
|
let mut hasher = CacheKeyHasher::new();
|
||||||
env!("CARGO_PKG_VERSION").cache_key(&mut hasher);
|
|
||||||
package_root.cache_key(&mut hasher);
|
package_root.cache_key(&mut hasher);
|
||||||
settings.cache_key(&mut hasher);
|
settings.cache_key(&mut hasher);
|
||||||
|
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the cache at the specified `Path`.
|
/// Initialize the cache at the specified `Path`.
|
||||||
pub(crate) fn init(path: &Path) -> Result<()> {
|
pub(crate) fn init(path: &Path) -> Result<()> {
|
||||||
// Create the cache directories.
|
// Create the cache directories.
|
||||||
fs::create_dir_all(path.join("content"))?;
|
fs::create_dir_all(path.join(VERSION))?;
|
||||||
|
|
||||||
// Add the CACHEDIR.TAG.
|
// Add the CACHEDIR.TAG.
|
||||||
if !cachedir::is_tagged(path)? {
|
if !cachedir::is_tagged(path)? {
|
||||||
@@ -335,31 +374,208 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize, PartialEq)]
|
||||||
|
pub(crate) struct LintCacheData {
|
||||||
|
/// Imports made.
|
||||||
|
pub(super) imports: ImportMap,
|
||||||
|
/// Diagnostic messages.
|
||||||
|
pub(super) messages: Vec<CacheMessage>,
|
||||||
|
/// Source code of the file.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// This will be empty if `messages` is empty.
|
||||||
|
pub(super) source: String,
|
||||||
|
/// Notebook index if this file is a Jupyter Notebook.
|
||||||
|
pub(super) notebook_index: Option<NotebookIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LintCacheData {
|
||||||
|
pub(crate) fn from_messages(
|
||||||
|
messages: &[Message],
|
||||||
|
imports: ImportMap,
|
||||||
|
notebook_index: Option<NotebookIndex>,
|
||||||
|
) -> Self {
|
||||||
|
let source = if let Some(msg) = messages.first() {
|
||||||
|
msg.file.source_text().to_owned()
|
||||||
|
} else {
|
||||||
|
String::new() // No messages, no need to keep the source!
|
||||||
|
};
|
||||||
|
|
||||||
|
let messages = messages
|
||||||
|
.iter()
|
||||||
|
.map(|msg| {
|
||||||
|
// Make sure that all message use the same source file.
|
||||||
|
assert_eq!(
|
||||||
|
msg.file,
|
||||||
|
messages.first().unwrap().file,
|
||||||
|
"message uses a different source file"
|
||||||
|
);
|
||||||
|
CacheMessage {
|
||||||
|
kind: msg.kind.clone(),
|
||||||
|
range: msg.range,
|
||||||
|
fix: msg.fix.clone(),
|
||||||
|
noqa_offset: msg.noqa_offset,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
imports,
|
||||||
|
messages,
|
||||||
|
source,
|
||||||
|
notebook_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On disk representation of a diagnostic message.
|
||||||
|
#[derive(Deserialize, Debug, Serialize, PartialEq)]
|
||||||
|
pub(super) struct CacheMessage {
|
||||||
|
kind: DiagnosticKind,
|
||||||
|
/// Range into the message's [`FileCache::source`].
|
||||||
|
range: TextRange,
|
||||||
|
fix: Option<Fix>,
|
||||||
|
noqa_offset: TextSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait PackageCaches {
|
||||||
|
fn get(&self, package_root: &Path) -> Option<&Cache>;
|
||||||
|
|
||||||
|
fn persist(self) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PackageCaches for Option<T>
|
||||||
|
where
|
||||||
|
T: PackageCaches,
|
||||||
|
{
|
||||||
|
fn get(&self, package_root: &Path) -> Option<&Cache> {
|
||||||
|
match self {
|
||||||
|
None => None,
|
||||||
|
Some(caches) => caches.get(package_root),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn persist(self) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
None => Ok(()),
|
||||||
|
Some(caches) => caches.persist(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct PackageCacheMap<'a>(FxHashMap<&'a Path, Cache>);
|
||||||
|
|
||||||
|
impl<'a> PackageCacheMap<'a> {
|
||||||
|
pub(crate) fn init(
|
||||||
|
pyproject_config: &PyprojectConfig,
|
||||||
|
package_roots: &FxHashMap<&'a Path, Option<&'a Path>>,
|
||||||
|
resolver: &Resolver,
|
||||||
|
) -> Self {
|
||||||
|
fn init_cache(path: &Path) {
|
||||||
|
if let Err(e) = cache::init(path) {
|
||||||
|
error!("Failed to initialize cache at {}: {e:?}", path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match pyproject_config.strategy {
|
||||||
|
PyprojectDiscoveryStrategy::Fixed => {
|
||||||
|
init_cache(&pyproject_config.settings.cache_dir);
|
||||||
|
}
|
||||||
|
PyprojectDiscoveryStrategy::Hierarchical => {
|
||||||
|
for settings in
|
||||||
|
std::iter::once(&pyproject_config.settings).chain(resolver.settings())
|
||||||
|
{
|
||||||
|
init_cache(&settings.cache_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(
|
||||||
|
package_roots
|
||||||
|
.iter()
|
||||||
|
.map(|(package, package_root)| package_root.unwrap_or(package))
|
||||||
|
.unique()
|
||||||
|
.par_bridge()
|
||||||
|
.map(|cache_root| {
|
||||||
|
let settings = resolver.resolve(cache_root, pyproject_config);
|
||||||
|
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
||||||
|
(cache_root, cache)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageCaches for PackageCacheMap<'_> {
|
||||||
|
fn get(&self, package_root: &Path) -> Option<&Cache> {
|
||||||
|
let cache = self.0.get(package_root);
|
||||||
|
|
||||||
|
if cache.is_none() {
|
||||||
|
debug!("No cache found for {}", package_root.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
cache
|
||||||
|
}
|
||||||
|
|
||||||
|
fn persist(self) -> Result<()> {
|
||||||
|
self.0
|
||||||
|
.into_par_iter()
|
||||||
|
.try_for_each(|(_, cache)| cache.persist())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Change {
|
||||||
|
path: PathBuf,
|
||||||
|
new_key: u64,
|
||||||
|
new_data: ChangeData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ChangeData {
|
||||||
|
Lint(LintCacheData),
|
||||||
|
Formatted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChangeData {
|
||||||
|
fn apply(self, data: &mut FileCacheData) {
|
||||||
|
match self {
|
||||||
|
ChangeData::Lint(new_lint) => {
|
||||||
|
data.lint = Some(new_lint);
|
||||||
|
}
|
||||||
|
ChangeData::Formatted => {
|
||||||
|
data.formatted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use filetime::{set_file_mtime, FileTime};
|
|
||||||
use std::env::temp_dir;
|
use std::env::temp_dir;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use filetime::{set_file_mtime, FileTime};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
use ruff_cache::CACHE_DIR_NAME;
|
use ruff_cache::CACHE_DIR_NAME;
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags;
|
||||||
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use crate::cache::RelativePathBuf;
|
use ruff_python_ast::PySourceType;
|
||||||
use crate::cache::{self, Cache, FileCache};
|
|
||||||
use crate::diagnostics::{lint_path, Diagnostics};
|
|
||||||
|
|
||||||
use std::sync::atomic::AtomicU64;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use ruff_python_ast::imports::ImportMap;
|
|
||||||
|
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
use test_case::test_case;
|
|
||||||
|
use crate::cache::{self, FileCache, FileCacheData, FileCacheKey};
|
||||||
|
use crate::cache::{Cache, RelativePathBuf};
|
||||||
|
use crate::commands::format::{format_path, FormatCommandError, FormatMode, FormatResult};
|
||||||
|
use crate::diagnostics::{lint_path, Diagnostics};
|
||||||
|
|
||||||
#[test_case("../ruff_linter/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_linter"; "ruff_linter_fixtures")]
|
#[test_case("../ruff_linter/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_linter"; "ruff_linter_fixtures")]
|
||||||
#[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")]
|
#[test_case("../ruff_notebook/resources/test/fixtures", "ruff_tests/cache_same_results_ruff_notebook"; "ruff_notebook_fixtures")]
|
||||||
@@ -376,7 +592,7 @@ mod tests {
|
|||||||
|
|
||||||
let package_root = fs::canonicalize(package_root).unwrap();
|
let package_root = fs::canonicalize(package_root).unwrap();
|
||||||
let cache = Cache::open(package_root.clone(), &settings);
|
let cache = Cache::open(package_root.clone(), &settings);
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
let mut parse_errors = Vec::new();
|
let mut parse_errors = Vec::new();
|
||||||
@@ -410,6 +626,7 @@ mod tests {
|
|||||||
Some(&cache),
|
Some(&cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate,
|
||||||
|
UnsafeFixes::Enabled,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if diagnostics
|
if diagnostics
|
||||||
@@ -425,7 +642,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_ne!(paths, &[] as &[std::path::PathBuf], "no files checked");
|
assert_ne!(paths, &[] as &[std::path::PathBuf], "no files checked");
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
|
|
||||||
let cache = Cache::open(package_root.clone(), &settings);
|
let cache = Cache::open(package_root.clone(), &settings);
|
||||||
assert_ne!(cache.package.files.len(), 0);
|
assert_ne!(cache.package.files.len(), 0);
|
||||||
@@ -455,6 +672,7 @@ mod tests {
|
|||||||
Some(&cache),
|
Some(&cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate,
|
||||||
|
UnsafeFixes::Enabled,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -469,21 +687,21 @@ mod tests {
|
|||||||
let test_cache = TestCache::new("cache_adds_file_on_lint");
|
let test_cache = TestCache::new("cache_adds_file_on_lint");
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
test_cache.write_source_file("source.py", source);
|
test_cache.write_source_file("source.py", source);
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
|
|
||||||
test_cache
|
test_cache
|
||||||
.lint_file_with_cache("source.py", &cache)
|
.lint_file_with_cache("source.py", &cache)
|
||||||
.expect("Failed to lint test file");
|
.expect("Failed to lint test file");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.new_files.lock().unwrap().len(),
|
cache.changes.lock().unwrap().len(),
|
||||||
1,
|
1,
|
||||||
"A single new file should be added to the cache"
|
"A single new file should be added to the cache"
|
||||||
);
|
);
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -494,9 +712,9 @@ mod tests {
|
|||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
test_cache.write_source_file("source_1.py", source);
|
test_cache.write_source_file("source_1.py", source);
|
||||||
test_cache.write_source_file("source_2.py", source);
|
test_cache.write_source_file("source_2.py", source);
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
|
|
||||||
test_cache
|
test_cache
|
||||||
@@ -506,12 +724,39 @@ mod tests {
|
|||||||
.lint_file_with_cache("source_2.py", &cache)
|
.lint_file_with_cache("source_2.py", &cache)
|
||||||
.expect("Failed to lint test file");
|
.expect("Failed to lint test file");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.new_files.lock().unwrap().len(),
|
cache.changes.lock().unwrap().len(),
|
||||||
|
2,
|
||||||
|
"Both files should be added to the cache"
|
||||||
|
);
|
||||||
|
cache.persist().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cache_adds_files_on_format() {
|
||||||
|
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
|
||||||
|
|
||||||
|
let test_cache = TestCache::new("cache_adds_files_on_format");
|
||||||
|
let cache = test_cache.open();
|
||||||
|
test_cache.write_source_file("source_1.py", source);
|
||||||
|
test_cache.write_source_file("source_2.py", source);
|
||||||
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
|
cache.persist().unwrap();
|
||||||
|
let cache = test_cache.open();
|
||||||
|
|
||||||
|
test_cache
|
||||||
|
.format_file_with_cache("source_1.py", &cache)
|
||||||
|
.expect("Failed to format test file");
|
||||||
|
test_cache
|
||||||
|
.format_file_with_cache("source_2.py", &cache)
|
||||||
|
.expect("Failed to format test file");
|
||||||
|
assert_eq!(
|
||||||
|
cache.changes.lock().unwrap().len(),
|
||||||
2,
|
2,
|
||||||
"Both files should be added to the cache"
|
"Both files should be added to the cache"
|
||||||
);
|
);
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -521,13 +766,13 @@ mod tests {
|
|||||||
let test_cache = TestCache::new("cache_invalidated_on_file_modified_time");
|
let test_cache = TestCache::new("cache_invalidated_on_file_modified_time");
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
let source_path = test_cache.write_source_file("source.py", source);
|
let source_path = test_cache.write_source_file("source.py", source);
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
let expected_diagnostics = test_cache
|
let expected_diagnostics = test_cache
|
||||||
.lint_file_with_cache("source.py", &cache)
|
.lint_file_with_cache("source.py", &cache)
|
||||||
.expect("Failed to lint test file");
|
.expect("Failed to lint test file");
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
|
|
||||||
// Update the modified time of the file to a time in the future
|
// Update the modified time of the file to a time in the future
|
||||||
@@ -542,7 +787,7 @@ mod tests {
|
|||||||
.expect("Failed to lint test file");
|
.expect("Failed to lint test file");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.new_files.lock().unwrap().len(),
|
cache.changes.lock().unwrap().len(),
|
||||||
1,
|
1,
|
||||||
"Cache should not be used, the file should be treated as new and added to the cache"
|
"Cache should not be used, the file should be treated as new and added to the cache"
|
||||||
);
|
);
|
||||||
@@ -580,13 +825,13 @@ mod tests {
|
|||||||
let test_cache = TestCache::new("cache_invalidated_on_permission_change");
|
let test_cache = TestCache::new("cache_invalidated_on_permission_change");
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
let path = test_cache.write_source_file("source.py", source);
|
let path = test_cache.write_source_file("source.py", source);
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
let expected_diagnostics = test_cache
|
let expected_diagnostics = test_cache
|
||||||
.lint_file_with_cache("source.py", &cache)
|
.lint_file_with_cache("source.py", &cache)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
|
|
||||||
// Flip the permissions on the file
|
// Flip the permissions on the file
|
||||||
@@ -600,7 +845,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.new_files.lock().unwrap().len(),
|
cache.changes.lock().unwrap().len(),
|
||||||
1,
|
1,
|
||||||
"Cache should not be used, the file should be treated as new and added to the cache"
|
"Cache should not be used, the file should be treated as new and added to the cache"
|
||||||
);
|
);
|
||||||
@@ -612,8 +857,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cache_removes_stale_files_on_store() {
|
fn cache_removes_stale_files_on_persist() {
|
||||||
let test_cache = TestCache::new("cache_removes_stale_files_on_store");
|
let test_cache = TestCache::new("cache_removes_stale_files_on_persist");
|
||||||
let mut cache = test_cache.open();
|
let mut cache = test_cache.open();
|
||||||
|
|
||||||
// Add a file to the cache that hasn't been linted or seen since the '70s!
|
// Add a file to the cache that hasn't been linted or seen since the '70s!
|
||||||
@@ -623,10 +868,7 @@ mod tests {
|
|||||||
FileCache {
|
FileCache {
|
||||||
key: 123,
|
key: 123,
|
||||||
last_seen: AtomicU64::new(123),
|
last_seen: AtomicU64::new(123),
|
||||||
imports: ImportMap::new(),
|
data: FileCacheData::default(),
|
||||||
messages: Vec::new(),
|
|
||||||
source: String::new(),
|
|
||||||
notebook_index: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -634,34 +876,125 @@ mod tests {
|
|||||||
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
|
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
|
||||||
test_cache.write_source_file("new.py", source);
|
test_cache.write_source_file("new.py", source);
|
||||||
let new_path_key = RelativePathBuf::from("new.py");
|
let new_path_key = RelativePathBuf::from("new.py");
|
||||||
assert_eq!(cache.new_files.lock().unwrap().len(), 0);
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
test_cache
|
test_cache
|
||||||
.lint_file_with_cache("new.py", &cache)
|
.lint_file_with_cache("new.py", &cache)
|
||||||
.expect("Failed to lint test file");
|
.expect("Failed to lint test file");
|
||||||
|
|
||||||
// Storing the cache should remove the old (`old.py`) file.
|
// Storing the cache should remove the old (`old.py`) file.
|
||||||
cache.store().unwrap();
|
cache.persist().unwrap();
|
||||||
// So we when we open the cache again it shouldn't contain `old.py`.
|
// So we when we open the cache again it shouldn't contain `old.py`.
|
||||||
let cache = test_cache.open();
|
let cache = test_cache.open();
|
||||||
|
|
||||||
assert!(
|
assert_eq!(
|
||||||
cache.package.files.keys().collect_vec() == vec![&new_path_key],
|
cache.package.files.keys().collect_vec(),
|
||||||
|
vec![&new_path_key],
|
||||||
"Only the new file should be present"
|
"Only the new file should be present"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_updates_cache_entry() {
|
||||||
|
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
|
||||||
|
|
||||||
|
let test_cache = TestCache::new("format_updates_cache_entry");
|
||||||
|
let cache = test_cache.open();
|
||||||
|
test_cache.write_source_file("source.py", source);
|
||||||
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
|
cache.persist().unwrap();
|
||||||
|
let cache = test_cache.open();
|
||||||
|
|
||||||
|
// Cache the lint results
|
||||||
|
test_cache
|
||||||
|
.lint_file_with_cache("source.py", &cache)
|
||||||
|
.expect("Failed to lint test file");
|
||||||
|
cache.persist().unwrap();
|
||||||
|
|
||||||
|
let mut cache = test_cache.open();
|
||||||
|
|
||||||
|
// Now lint the file
|
||||||
|
test_cache
|
||||||
|
.format_file_with_cache("source.py", &cache)
|
||||||
|
.expect("Failed to format test file");
|
||||||
|
|
||||||
|
cache.save();
|
||||||
|
|
||||||
|
assert_eq!(cache.package.files.len(), 1);
|
||||||
|
|
||||||
|
let Some(file_cache) = cache.get(
|
||||||
|
Path::new("source.py"),
|
||||||
|
&FileCacheKey::from_path(&test_cache.package_root.join("source.py")).unwrap(),
|
||||||
|
) else {
|
||||||
|
panic!("Cache entry for `source.py` is missing.");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(file_cache.data.lint.is_some());
|
||||||
|
assert!(file_cache.data.formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_changes_invalidate_file_cache() {
|
||||||
|
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
|
||||||
|
|
||||||
|
let test_cache = TestCache::new("file_changes_invalidate_file_cache");
|
||||||
|
let cache = test_cache.open();
|
||||||
|
let source_path = test_cache.write_source_file("source.py", source);
|
||||||
|
assert_eq!(cache.changes.lock().unwrap().len(), 0);
|
||||||
|
|
||||||
|
cache.persist().unwrap();
|
||||||
|
let cache = test_cache.open();
|
||||||
|
|
||||||
|
// Cache the format and lint results
|
||||||
|
test_cache
|
||||||
|
.lint_file_with_cache("source.py", &cache)
|
||||||
|
.expect("Failed to lint test file");
|
||||||
|
test_cache
|
||||||
|
.format_file_with_cache("source.py", &cache)
|
||||||
|
.expect("Failed to format test file");
|
||||||
|
|
||||||
|
cache.persist().unwrap();
|
||||||
|
|
||||||
|
let mut cache = test_cache.open();
|
||||||
|
assert_eq!(cache.package.files.len(), 1);
|
||||||
|
|
||||||
|
set_file_mtime(
|
||||||
|
&source_path,
|
||||||
|
FileTime::from_system_time(SystemTime::now() + std::time::Duration::from_secs(1)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
test_cache
|
||||||
|
.format_file_with_cache("source.py", &cache)
|
||||||
|
.expect("Failed to format test file");
|
||||||
|
|
||||||
|
cache.save();
|
||||||
|
|
||||||
|
assert_eq!(cache.package.files.len(), 1);
|
||||||
|
|
||||||
|
let Some(file_cache) = cache.get(
|
||||||
|
Path::new("source.py"),
|
||||||
|
&FileCacheKey::from_path(&source_path).unwrap(),
|
||||||
|
) else {
|
||||||
|
panic!("Cache entry for `source.py` is missing.");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(file_cache.data.lint, None);
|
||||||
|
assert!(file_cache.data.formatted);
|
||||||
|
}
|
||||||
|
|
||||||
struct TestCache {
|
struct TestCache {
|
||||||
package_root: PathBuf,
|
package_root: PathBuf,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCache {
|
impl TestCache {
|
||||||
fn new(name: &str) -> Self {
|
fn new(test_case: &str) -> Self {
|
||||||
// Build a new cache directory and clear it
|
// Build a new cache directory and clear it
|
||||||
let mut test_dir = temp_dir();
|
let mut test_dir = temp_dir();
|
||||||
test_dir.push("ruff_tests/cache");
|
test_dir.push("ruff_tests/cache");
|
||||||
test_dir.push(name);
|
test_dir.push(test_case);
|
||||||
|
|
||||||
let _ = fs::remove_dir_all(&test_dir);
|
let _ = fs::remove_dir_all(&test_dir);
|
||||||
|
|
||||||
@@ -712,6 +1045,22 @@ mod tests {
|
|||||||
Some(cache),
|
Some(cache),
|
||||||
flags::Noqa::Enabled,
|
flags::Noqa::Enabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate,
|
||||||
|
UnsafeFixes::Enabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_file_with_cache(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
cache: &Cache,
|
||||||
|
) -> Result<FormatResult, FormatCommandError> {
|
||||||
|
let file_path = self.package_root.join(path);
|
||||||
|
format_path(
|
||||||
|
&file_path,
|
||||||
|
&self.settings.formatter,
|
||||||
|
PySourceType::Python,
|
||||||
|
FormatMode::Write,
|
||||||
|
Some(cache),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ruff_linter::linter::add_noqa_to_path;
|
|||||||
use ruff_linter::source_kind::SourceKind;
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ pub(crate) fn add_noqa(
|
|||||||
&paths
|
&paths
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(ignore::DirEntry::path)
|
.map(ResolvedFile::path)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
pyproject_config,
|
pyproject_config,
|
||||||
);
|
);
|
||||||
@@ -45,14 +45,15 @@ pub(crate) fn add_noqa(
|
|||||||
let modifications: usize = paths
|
let modifications: usize = paths
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter_map(|entry| {
|
.filter_map(|resolved_file| {
|
||||||
let path = entry.path();
|
|
||||||
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
|
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
|
||||||
SourceType::from(path)
|
SourceType::from(resolved_file.path())
|
||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let package = path
|
let path = resolved_file.path();
|
||||||
|
let package = resolved_file
|
||||||
|
.path()
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|parent| package_roots.get(parent))
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.and_then(|package| *package);
|
.and_then(|package| *package);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -7,7 +6,6 @@ use std::time::Instant;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use ignore::Error;
|
use ignore::Error;
|
||||||
use itertools::Itertools;
|
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
@@ -16,19 +14,23 @@ use rustc_hash::FxHashMap;
|
|||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_linter::message::Message;
|
use ruff_linter::message::Message;
|
||||||
use ruff_linter::registry::Rule;
|
use ruff_linter::registry::Rule;
|
||||||
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{flags, LinterSettings};
|
||||||
use ruff_linter::{fs, warn_user_once, IOError};
|
use ruff_linter::{fs, warn_user_once, IOError};
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy};
|
use ruff_workspace::resolver::{
|
||||||
|
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
use crate::cache::{self, Cache};
|
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
use crate::panic::catch_unwind;
|
use crate::panic::catch_unwind;
|
||||||
|
|
||||||
/// Run the linter over a collection of files.
|
/// Run the linter over a collection of files.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn check(
|
pub(crate) fn check(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
@@ -36,162 +38,154 @@ pub(crate) fn check(
|
|||||||
cache: flags::Cache,
|
cache: flags::Cache,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
|
unsafe_fixes: UnsafeFixes,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
// Collect all the Python files to check.
|
// Collect all the Python files to check.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
let duration = start.elapsed();
|
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||||
debug!("Identified files to lint in: {:?}", duration);
|
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
warn_user_once!("No Python files found under the given path(s)");
|
warn_user_once!("No Python files found under the given path(s)");
|
||||||
return Ok(Diagnostics::default());
|
return Ok(Diagnostics::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the cache.
|
|
||||||
if cache.into() {
|
|
||||||
fn init_cache(path: &Path) {
|
|
||||||
if let Err(e) = cache::init(path) {
|
|
||||||
error!("Failed to initialize cache at {}: {e:?}", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match pyproject_config.strategy {
|
|
||||||
PyprojectDiscoveryStrategy::Fixed => {
|
|
||||||
init_cache(&pyproject_config.settings.cache_dir);
|
|
||||||
}
|
|
||||||
PyprojectDiscoveryStrategy::Hierarchical => {
|
|
||||||
for settings in
|
|
||||||
std::iter::once(&pyproject_config.settings).chain(resolver.settings())
|
|
||||||
{
|
|
||||||
init_cache(&settings.cache_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Discover the package root for each Python file.
|
// Discover the package root for each Python file.
|
||||||
let package_roots = resolver.package_roots(
|
let package_roots = resolver.package_roots(
|
||||||
&paths
|
&paths
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(ignore::DirEntry::path)
|
.map(ResolvedFile::path)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
pyproject_config,
|
pyproject_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load the caches.
|
// Load the caches.
|
||||||
let caches = bool::from(cache).then(|| {
|
let caches = if bool::from(cache) {
|
||||||
package_roots
|
Some(PackageCacheMap::init(
|
||||||
.iter()
|
pyproject_config,
|
||||||
.map(|(package, package_root)| package_root.unwrap_or(package))
|
&package_roots,
|
||||||
.unique()
|
&resolver,
|
||||||
.par_bridge()
|
))
|
||||||
.map(|cache_root| {
|
} else {
|
||||||
let settings = resolver.resolve(cache_root, pyproject_config);
|
None
|
||||||
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
};
|
||||||
(cache_root, cache)
|
|
||||||
})
|
|
||||||
.collect::<HashMap<&Path, Cache>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut diagnostics: Diagnostics = paths
|
let diagnostics_per_file = paths.par_iter().filter_map(|resolved_file| {
|
||||||
.par_iter()
|
let result = match resolved_file {
|
||||||
.map(|entry| {
|
Ok(resolved_file) => {
|
||||||
match entry {
|
let path = resolved_file.path();
|
||||||
Ok(entry) => {
|
let package = path
|
||||||
let path = entry.path();
|
.parent()
|
||||||
let package = path
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.parent()
|
.and_then(|package| *package);
|
||||||
.and_then(|parent| package_roots.get(parent))
|
|
||||||
.and_then(|package| *package);
|
|
||||||
|
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path, pyproject_config);
|
||||||
|
|
||||||
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
let cache = caches.as_ref().and_then(|caches| {
|
&& match_exclusion(
|
||||||
if let Some(cache) = caches.get(&cache_root) {
|
resolved_file.path(),
|
||||||
Some(cache)
|
resolved_file.file_name(),
|
||||||
} else {
|
&settings.linter.exclude,
|
||||||
debug!("No cache found for {}", cache_root.display());
|
)
|
||||||
None
|
{
|
||||||
}
|
return None;
|
||||||
});
|
|
||||||
|
|
||||||
lint_path(path, package, &settings.linter, cache, noqa, fix_mode).map_err(|e| {
|
|
||||||
(Some(path.to_owned()), {
|
|
||||||
let mut error = e.to_string();
|
|
||||||
for cause in e.chain() {
|
|
||||||
write!(&mut error, "\n Cause: {cause}").unwrap();
|
|
||||||
}
|
|
||||||
error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Err(e) => Err((
|
|
||||||
if let Error::WithPath { path, .. } = e {
|
|
||||||
Some(path.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
e.io_error()
|
|
||||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
.unwrap_or_else(|(path, message)| {
|
|
||||||
if let Some(path) = &path {
|
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
|
||||||
if settings.linter.rules.enabled(Rule::IOError) {
|
|
||||||
let dummy =
|
|
||||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
|
||||||
|
|
||||||
Diagnostics::new(
|
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||||
vec![Message::from_diagnostic(
|
let cache = caches.get(cache_root);
|
||||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
|
||||||
dummy,
|
lint_path(
|
||||||
TextSize::default(),
|
path,
|
||||||
)],
|
package,
|
||||||
ImportMap::default(),
|
&settings.linter,
|
||||||
FxHashMap::default(),
|
cache,
|
||||||
)
|
noqa,
|
||||||
} else {
|
fix_mode,
|
||||||
warn!(
|
unsafe_fixes,
|
||||||
"{}{}{} {message}",
|
)
|
||||||
"Failed to lint ".bold(),
|
.map_err(|e| {
|
||||||
fs::relativize_path(path).bold(),
|
(Some(path.to_path_buf()), {
|
||||||
":".bold()
|
let mut error = e.to_string();
|
||||||
);
|
for cause in e.chain() {
|
||||||
Diagnostics::default()
|
write!(&mut error, "\n Cause: {cause}").unwrap();
|
||||||
}
|
}
|
||||||
|
error
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => Err((
|
||||||
|
if let Error::WithPath { path, .. } = e {
|
||||||
|
Some(path.clone())
|
||||||
} else {
|
} else {
|
||||||
warn!("{} {message}", "Encountered error:".bold());
|
None
|
||||||
|
},
|
||||||
|
e.io_error()
|
||||||
|
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(result.unwrap_or_else(|(path, message)| {
|
||||||
|
if let Some(path) = &path {
|
||||||
|
let settings = resolver.resolve(path, pyproject_config);
|
||||||
|
if settings.linter.rules.enabled(Rule::IOError) {
|
||||||
|
let dummy =
|
||||||
|
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||||
|
|
||||||
|
Diagnostics::new(
|
||||||
|
vec![Message::from_diagnostic(
|
||||||
|
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||||
|
dummy,
|
||||||
|
TextSize::default(),
|
||||||
|
)],
|
||||||
|
ImportMap::default(),
|
||||||
|
FxHashMap::default(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"{}{}{} {message}",
|
||||||
|
"Failed to lint ".bold(),
|
||||||
|
fs::relativize_path(path).bold(),
|
||||||
|
":".bold()
|
||||||
|
);
|
||||||
Diagnostics::default()
|
Diagnostics::default()
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
})
|
warn!("{} {message}", "Encountered error:".bold());
|
||||||
.reduce(Diagnostics::default, |mut acc, item| {
|
Diagnostics::default()
|
||||||
acc += item;
|
}
|
||||||
acc
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
diagnostics.messages.sort();
|
// Aggregate the diagnostics of all checked files and count the checked files.
|
||||||
|
// This can't be a regular for loop because we use `par_iter`.
|
||||||
|
let (mut all_diagnostics, checked_files) = diagnostics_per_file
|
||||||
|
.fold(
|
||||||
|
|| (Diagnostics::default(), 0u64),
|
||||||
|
|(all_diagnostics, checked_files), file_diagnostics| {
|
||||||
|
(all_diagnostics + file_diagnostics, checked_files + 1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
|| (Diagnostics::default(), 0u64),
|
||||||
|
|a, b| (a.0 + b.0, a.1 + b.1),
|
||||||
|
);
|
||||||
|
|
||||||
|
all_diagnostics.messages.sort();
|
||||||
|
|
||||||
// Store the caches.
|
// Store the caches.
|
||||||
if let Some(caches) = caches {
|
caches.persist()?;
|
||||||
caches
|
|
||||||
.into_par_iter()
|
|
||||||
.try_for_each(|(_, cache)| cache.store())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
debug!("Checked {:?} files in: {:?}", paths.len(), duration);
|
debug!("Checked {:?} files in: {:?}", checked_files, duration);
|
||||||
|
|
||||||
Ok(diagnostics)
|
Ok(all_diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||||
/// a diagnostic if the linting the file panics.
|
/// a diagnostic if the linting the file panics.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn lint_path(
|
fn lint_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
package: Option<&Path>,
|
package: Option<&Path>,
|
||||||
@@ -199,9 +193,10 @@ fn lint_path(
|
|||||||
cache: Option<&Cache>,
|
cache: Option<&Cache>,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
|
unsafe_fixes: UnsafeFixes,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
let result = catch_unwind(|| {
|
let result = catch_unwind(|| {
|
||||||
crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode)
|
crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode, unsafe_fixes)
|
||||||
});
|
});
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@@ -238,6 +233,7 @@ mod test {
|
|||||||
|
|
||||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||||
use ruff_linter::registry::Rule;
|
use ruff_linter::registry::Rule;
|
||||||
|
use ruff_linter::settings::types::UnsafeFixes;
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{flags, LinterSettings};
|
||||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
@@ -285,6 +281,7 @@ mod test {
|
|||||||
flags::Cache::Disabled,
|
flags::Cache::Disabled,
|
||||||
flags::Noqa::Disabled,
|
flags::Noqa::Disabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate,
|
||||||
|
UnsafeFixes::Enabled,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
|||||||
|
|
||||||
use ruff_linter::packaging;
|
use ruff_linter::packaging;
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags;
|
||||||
use ruff_workspace::resolver::{python_file_at_path, PyprojectConfig};
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||||
@@ -18,9 +18,19 @@ pub(crate) fn check_stdin(
|
|||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
if let Some(filename) = filename {
|
if pyproject_config.settings.file_resolver.force_exclude {
|
||||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
if let Some(filename) = filename {
|
||||||
return Ok(Diagnostics::default());
|
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||||
|
return Ok(Diagnostics::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let lint_settings = &pyproject_config.settings.linter;
|
||||||
|
if filename
|
||||||
|
.file_name()
|
||||||
|
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||||
|
{
|
||||||
|
return Ok(Diagnostics::default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||||
|
|||||||
@@ -1,29 +1,37 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{stderr, stdout, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::{error, warn};
|
||||||
use rayon::iter::Either::{Left, Right};
|
use rayon::iter::Either::{Left, Right};
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use ruff_diagnostics::SourceMap;
|
use ruff_diagnostics::SourceMap;
|
||||||
use ruff_linter::fs;
|
use ruff_linter::fs;
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
|
use ruff_linter::registry::Rule;
|
||||||
|
use ruff_linter::rules::flake8_quotes::settings::Quote;
|
||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_python_formatter::{format_module_source, FormatModuleError};
|
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::python_files_in_path;
|
use ruff_workspace::resolver::{
|
||||||
|
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
||||||
|
};
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{CliOverrides, FormatArguments};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
|
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||||
use crate::panic::{catch_unwind, PanicError};
|
use crate::panic::{catch_unwind, PanicError};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
@@ -34,6 +42,20 @@ pub(crate) enum FormatMode {
|
|||||||
Write,
|
Write,
|
||||||
/// Check if the file is formatted, but do not write the formatted contents back.
|
/// Check if the file is formatted, but do not write the formatted contents back.
|
||||||
Check,
|
Check,
|
||||||
|
/// Check if the file is formatted, show a diff if not.
|
||||||
|
Diff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatMode {
|
||||||
|
pub(crate) fn from_cli(cli: &FormatArguments) -> Self {
|
||||||
|
if cli.diff {
|
||||||
|
FormatMode::Diff
|
||||||
|
} else if cli.check {
|
||||||
|
FormatMode::Check
|
||||||
|
} else {
|
||||||
|
FormatMode::Write
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a set of files, and return the exit status.
|
/// Format a set of files, and return the exit status.
|
||||||
@@ -48,11 +70,7 @@ pub(crate) fn format(
|
|||||||
overrides,
|
overrides,
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
let mode = if cli.check {
|
let mode = FormatMode::from_cli(cli);
|
||||||
FormatMode::Check
|
|
||||||
} else {
|
|
||||||
FormatMode::Write
|
|
||||||
};
|
|
||||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
|
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
@@ -60,33 +78,81 @@ pub(crate) fn format(
|
|||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn_incompatible_formatter_settings(&pyproject_config, Some(&resolver));
|
||||||
|
|
||||||
|
// Discover the package root for each Python file.
|
||||||
|
let package_roots = resolver.package_roots(
|
||||||
|
&paths
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(ResolvedFile::path)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&pyproject_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
let caches = if cli.no_cache {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// `--no-cache` doesn't respect code changes, and so is often confusing during
|
||||||
|
// development.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
crate::warn_user!("Detected debug build without --no-cache.");
|
||||||
|
|
||||||
|
Some(PackageCacheMap::init(
|
||||||
|
&pyproject_config,
|
||||||
|
&package_roots,
|
||||||
|
&resolver,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (results, errors): (Vec<_>, Vec<_>) = paths
|
let (results, mut errors): (Vec<_>, Vec<_>) = paths
|
||||||
.into_par_iter()
|
.par_iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(entry) => {
|
Ok(resolved_file) => {
|
||||||
let path = entry.path();
|
let path = resolved_file.path();
|
||||||
|
let SourceType::Python(source_type) = SourceType::from(&path) else {
|
||||||
let SourceType::Python(source_type) = SourceType::from(path) else {
|
|
||||||
// Ignore any non-Python files.
|
// Ignore any non-Python files.
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
let settings = resolver.resolve(path, &pyproject_config);
|
||||||
|
|
||||||
|
// Ignore files that are excluded from formatting
|
||||||
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
|
&& match_exclusion(
|
||||||
|
path,
|
||||||
|
resolved_file.file_name(),
|
||||||
|
&settings.formatter.exclude,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let package = path
|
||||||
|
.parent()
|
||||||
|
.and_then(|parent| package_roots.get(parent).copied())
|
||||||
|
.flatten();
|
||||||
|
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||||
|
let cache = caches.get(cache_root);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
match catch_unwind(|| {
|
match catch_unwind(|| {
|
||||||
format_path(path, &resolved_settings.formatter, source_type, mode)
|
format_path(path, &settings.formatter, source_type, mode, cache)
|
||||||
}) {
|
}) {
|
||||||
Ok(inner) => inner,
|
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||||
Err(error) => {
|
path: resolved_file.path().to_path_buf(),
|
||||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
result,
|
||||||
}
|
}),
|
||||||
|
Err(error) => Err(FormatCommandError::Panic(
|
||||||
|
Some(resolved_file.path().to_path_buf()),
|
||||||
|
error,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
|
Err(err) => Some(Err(FormatCommandError::Ignore(err.clone()))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.partition_map(|result| match result {
|
.partition_map(|result| match result {
|
||||||
@@ -101,18 +167,33 @@ pub(crate) fn format(
|
|||||||
duration
|
duration
|
||||||
);
|
);
|
||||||
|
|
||||||
|
caches.persist()?;
|
||||||
|
|
||||||
// Report on any errors.
|
// Report on any errors.
|
||||||
|
errors.sort_unstable_by(|a, b| a.path().cmp(&b.path()));
|
||||||
|
|
||||||
for error in &errors {
|
for error in &errors {
|
||||||
error!("{error}");
|
error!("{error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = FormatResultSummary::new(results, mode);
|
let results = FormatResults::new(results.as_slice(), mode);
|
||||||
|
match mode {
|
||||||
|
FormatMode::Write => {}
|
||||||
|
FormatMode::Check => {
|
||||||
|
results.write_changed(&mut stdout().lock())?;
|
||||||
|
}
|
||||||
|
FormatMode::Diff => {
|
||||||
|
results.write_diff(&mut stdout().lock())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Report on the formatting changes.
|
// Report on the formatting changes.
|
||||||
if log_level >= LogLevel::Default {
|
if log_level >= LogLevel::Default {
|
||||||
#[allow(clippy::print_stdout)]
|
if mode.is_diff() {
|
||||||
{
|
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||||
println!("{summary}");
|
results.write_summary(&mut stderr().lock())?;
|
||||||
|
} else {
|
||||||
|
results.write_summary(&mut stdout().lock())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +205,9 @@ pub(crate) fn format(
|
|||||||
Ok(ExitStatus::Error)
|
Ok(ExitStatus::Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormatMode::Check => {
|
FormatMode::Check | FormatMode::Diff => {
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
if summary.formatted > 0 {
|
if results.any_formatted() {
|
||||||
Ok(ExitStatus::Failure)
|
Ok(ExitStatus::Failure)
|
||||||
} else {
|
} else {
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
@@ -139,61 +220,94 @@ pub(crate) fn format(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format the file at the given [`Path`].
|
/// Format the file at the given [`Path`].
|
||||||
#[tracing::instrument(skip_all, fields(path = %path.display()))]
|
#[tracing::instrument(level="debug", skip_all, fields(path = %path.display()))]
|
||||||
fn format_path(
|
pub(crate) fn format_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
mode: FormatMode,
|
mode: FormatMode,
|
||||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
cache: Option<&Cache>,
|
||||||
|
) -> Result<FormatResult, FormatCommandError> {
|
||||||
|
if let Some(cache) = cache {
|
||||||
|
let relative_path = cache
|
||||||
|
.relative_path(path)
|
||||||
|
.expect("wrong package cache for file");
|
||||||
|
|
||||||
|
if let Ok(cache_key) = FileCacheKey::from_path(path) {
|
||||||
|
if cache.is_formatted(relative_path, &cache_key) {
|
||||||
|
return Ok(FormatResult::Unchanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
let unformatted = match SourceKind::from_path(path, source_type) {
|
||||||
Ok(Some(source_kind)) => source_kind,
|
Ok(Some(source_kind)) => source_kind,
|
||||||
Ok(None) => return Ok(FormatCommandResult::Unchanged),
|
// Non Python Jupyter notebook
|
||||||
|
Ok(None) => return Ok(FormatResult::Skipped),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(FormatCommandError::Read(Some(path.to_path_buf()), err));
|
return Err(FormatCommandError::Read(Some(path.to_path_buf()), err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the source.
|
// Format the source.
|
||||||
match format_source(source_kind, source_type, Some(path), settings)? {
|
let format_result = match format_source(&unformatted, source_type, Some(path), settings)? {
|
||||||
FormattedSource::Formatted(formatted) => {
|
FormattedSource::Formatted(formatted) => match mode {
|
||||||
if mode.is_write() {
|
FormatMode::Write => {
|
||||||
let mut writer = File::create(path).map_err(|err| {
|
let mut writer = File::create(path).map_err(|err| {
|
||||||
FormatCommandError::Write(Some(path.to_path_buf()), err.into())
|
FormatCommandError::Write(Some(path.to_path_buf()), err.into())
|
||||||
})?;
|
})?;
|
||||||
formatted
|
formatted
|
||||||
.write(&mut writer)
|
.write(&mut writer)
|
||||||
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
|
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
|
||||||
|
|
||||||
|
if let Some(cache) = cache {
|
||||||
|
if let Ok(cache_key) = FileCacheKey::from_path(path) {
|
||||||
|
let relative_path = cache
|
||||||
|
.relative_path(path)
|
||||||
|
.expect("wrong package cache for file");
|
||||||
|
cache.set_formatted(relative_path.to_path_buf(), &cache_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatResult::Formatted
|
||||||
}
|
}
|
||||||
Ok(FormatCommandResult::Formatted)
|
FormatMode::Check => FormatResult::Formatted,
|
||||||
|
FormatMode::Diff => FormatResult::Diff {
|
||||||
|
unformatted,
|
||||||
|
formatted,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FormattedSource::Unchanged => {
|
||||||
|
if let Some(cache) = cache {
|
||||||
|
if let Ok(cache_key) = FileCacheKey::from_path(path) {
|
||||||
|
let relative_path = cache
|
||||||
|
.relative_path(path)
|
||||||
|
.expect("wrong package cache for file");
|
||||||
|
cache.set_formatted(relative_path.to_path_buf(), &cache_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatResult::Unchanged
|
||||||
}
|
}
|
||||||
FormattedSource::Unchanged(_) => Ok(FormatCommandResult::Unchanged),
|
};
|
||||||
}
|
|
||||||
|
Ok(format_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum FormattedSource {
|
pub(crate) enum FormattedSource {
|
||||||
/// The source was formatted, and the [`SourceKind`] contains the transformed source code.
|
/// The source was formatted, and the [`SourceKind`] contains the transformed source code.
|
||||||
Formatted(SourceKind),
|
Formatted(SourceKind),
|
||||||
/// The source was unchanged, and the [`SourceKind`] contains the original source code.
|
/// The source was unchanged.
|
||||||
Unchanged(SourceKind),
|
Unchanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FormattedSource> for FormatCommandResult {
|
impl From<FormattedSource> for FormatResult {
|
||||||
fn from(value: FormattedSource) -> Self {
|
fn from(value: FormattedSource) -> Self {
|
||||||
match value {
|
match value {
|
||||||
FormattedSource::Formatted(_) => FormatCommandResult::Formatted,
|
FormattedSource::Formatted(_) => FormatResult::Formatted,
|
||||||
FormattedSource::Unchanged(_) => FormatCommandResult::Unchanged,
|
FormattedSource::Unchanged => FormatResult::Unchanged,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FormattedSource {
|
|
||||||
pub(crate) fn source_kind(&self) -> &SourceKind {
|
|
||||||
match self {
|
|
||||||
FormattedSource::Formatted(source_kind) => source_kind,
|
|
||||||
FormattedSource::Unchanged(source_kind) => source_kind,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,30 +315,28 @@ impl FormattedSource {
|
|||||||
/// Format a [`SourceKind`], returning the transformed [`SourceKind`], or `None` if the source was
|
/// Format a [`SourceKind`], returning the transformed [`SourceKind`], or `None` if the source was
|
||||||
/// unchanged.
|
/// unchanged.
|
||||||
pub(crate) fn format_source(
|
pub(crate) fn format_source(
|
||||||
source_kind: SourceKind,
|
source_kind: &SourceKind,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
path: Option<&Path>,
|
path: Option<&Path>,
|
||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
) -> Result<FormattedSource, FormatCommandError> {
|
) -> Result<FormattedSource, FormatCommandError> {
|
||||||
match source_kind {
|
match source_kind {
|
||||||
SourceKind::Python(unformatted) => {
|
SourceKind::Python(unformatted) => {
|
||||||
let options = settings.to_format_options(source_type, &unformatted);
|
let options = settings.to_format_options(source_type, unformatted);
|
||||||
|
|
||||||
let formatted = format_module_source(&unformatted, options)
|
let formatted = format_module_source(unformatted, options)
|
||||||
.map_err(|err| FormatCommandError::Format(path.map(Path::to_path_buf), err))?;
|
.map_err(|err| FormatCommandError::Format(path.map(Path::to_path_buf), err))?;
|
||||||
|
|
||||||
let formatted = formatted.into_code();
|
let formatted = formatted.into_code();
|
||||||
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
||||||
Ok(FormattedSource::Unchanged(SourceKind::Python(unformatted)))
|
Ok(FormattedSource::Unchanged)
|
||||||
} else {
|
} else {
|
||||||
Ok(FormattedSource::Formatted(SourceKind::Python(formatted)))
|
Ok(FormattedSource::Formatted(SourceKind::Python(formatted)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SourceKind::IpyNotebook(notebook) => {
|
SourceKind::IpyNotebook(notebook) => {
|
||||||
if !notebook.is_python_notebook() {
|
if !notebook.is_python_notebook() {
|
||||||
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
|
return Ok(FormattedSource::Unchanged);
|
||||||
notebook,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = settings.to_format_options(source_type, notebook.source_code());
|
let options = settings.to_format_options(source_type, notebook.source_code());
|
||||||
@@ -272,9 +384,7 @@ pub(crate) fn format_source(
|
|||||||
|
|
||||||
// If the file was unchanged, return `None`.
|
// If the file was unchanged, return `None`.
|
||||||
let (Some(mut output), Some(last)) = (output, last) else {
|
let (Some(mut output), Some(last)) = (output, last) else {
|
||||||
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
|
return Ok(FormattedSource::Unchanged);
|
||||||
notebook,
|
|
||||||
)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the remaining content.
|
// Add the remaining content.
|
||||||
@@ -282,83 +392,155 @@ pub(crate) fn format_source(
|
|||||||
output.push_str(slice);
|
output.push_str(slice);
|
||||||
|
|
||||||
// Update the notebook.
|
// Update the notebook.
|
||||||
let mut notebook = notebook.clone();
|
let mut formatted = notebook.clone();
|
||||||
notebook.update(&source_map, output);
|
formatted.update(&source_map, output);
|
||||||
|
|
||||||
Ok(FormattedSource::Formatted(SourceKind::IpyNotebook(
|
Ok(FormattedSource::Formatted(SourceKind::IpyNotebook(
|
||||||
notebook,
|
formatted,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, is_macro::Is)]
|
/// The result of an individual formatting operation.
|
||||||
pub(crate) enum FormatCommandResult {
|
#[derive(Debug, Clone, is_macro::Is)]
|
||||||
|
pub(crate) enum FormatResult {
|
||||||
/// The file was formatted.
|
/// The file was formatted.
|
||||||
Formatted,
|
Formatted,
|
||||||
|
/// The file was formatted, [`SourceKind`] contains the formatted code
|
||||||
|
Diff {
|
||||||
|
unformatted: SourceKind,
|
||||||
|
formatted: SourceKind,
|
||||||
|
},
|
||||||
/// The file was unchanged, as the formatted contents matched the existing contents.
|
/// The file was unchanged, as the formatted contents matched the existing contents.
|
||||||
Unchanged,
|
Unchanged,
|
||||||
|
|
||||||
|
/// Skipped formatting because its an unsupported file format
|
||||||
|
Skipped,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The coupling of a [`FormatResult`] with the path of the file that was analyzed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FormatResultSummary {
|
struct FormatPathResult {
|
||||||
|
path: PathBuf,
|
||||||
|
result: FormatResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The results of formatting a set of files
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FormatResults<'a> {
|
||||||
|
/// The individual formatting results.
|
||||||
|
results: &'a [FormatPathResult],
|
||||||
/// The format mode that was used.
|
/// The format mode that was used.
|
||||||
mode: FormatMode,
|
mode: FormatMode,
|
||||||
/// The number of files that were formatted.
|
|
||||||
formatted: usize,
|
|
||||||
/// The number of files that were unchanged.
|
|
||||||
unchanged: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatResultSummary {
|
impl<'a> FormatResults<'a> {
|
||||||
fn new(diagnostics: Vec<FormatCommandResult>, mode: FormatMode) -> Self {
|
fn new(results: &'a [FormatPathResult], mode: FormatMode) -> Self {
|
||||||
let mut summary = Self {
|
Self { results, mode }
|
||||||
mode,
|
}
|
||||||
formatted: 0,
|
|
||||||
unchanged: 0,
|
/// Returns `true` if any of the files require formatting.
|
||||||
};
|
fn any_formatted(&self) -> bool {
|
||||||
for diagnostic in diagnostics {
|
self.results.iter().any(|result| match result.result {
|
||||||
match diagnostic {
|
FormatResult::Formatted | FormatResult::Diff { .. } => true,
|
||||||
FormatCommandResult::Formatted => summary.formatted += 1,
|
FormatResult::Unchanged | FormatResult::Skipped => false,
|
||||||
FormatCommandResult::Unchanged => summary.unchanged += 1,
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a diff of the formatting changes to the given writer.
|
||||||
|
fn write_diff(&self, f: &mut impl Write) -> io::Result<()> {
|
||||||
|
for (path, unformatted, formatted) in self
|
||||||
|
.results
|
||||||
|
.iter()
|
||||||
|
.filter_map(|result| {
|
||||||
|
if let FormatResult::Diff {
|
||||||
|
unformatted,
|
||||||
|
formatted,
|
||||||
|
} = &result.result
|
||||||
|
{
|
||||||
|
Some((result.path.as_path(), unformatted, formatted))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||||
|
{
|
||||||
|
unformatted.diff(formatted, Some(path), f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a list of the files that would be changed to the given writer.
|
||||||
|
fn write_changed(&self, f: &mut impl Write) -> io::Result<()> {
|
||||||
|
for path in self
|
||||||
|
.results
|
||||||
|
.iter()
|
||||||
|
.filter_map(|result| {
|
||||||
|
if result.result.is_formatted() {
|
||||||
|
Some(result.path.as_path())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sorted_unstable()
|
||||||
|
{
|
||||||
|
writeln!(f, "Would reformat: {}", fs::relativize_path(path).bold())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a summary of the formatting results to the given writer.
|
||||||
|
fn write_summary(&self, f: &mut impl Write) -> io::Result<()> {
|
||||||
|
// Compute the number of changed and unchanged files.
|
||||||
|
let mut changed = 0u32;
|
||||||
|
let mut unchanged = 0u32;
|
||||||
|
for result in self.results {
|
||||||
|
match &result.result {
|
||||||
|
FormatResult::Formatted => {
|
||||||
|
changed += 1;
|
||||||
|
}
|
||||||
|
FormatResult::Unchanged => unchanged += 1,
|
||||||
|
FormatResult::Diff { .. } => {
|
||||||
|
changed += 1;
|
||||||
|
}
|
||||||
|
FormatResult::Skipped => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FormatResultSummary {
|
// Write out a summary of the formatting results.
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
if changed > 0 && unchanged > 0 {
|
||||||
if self.formatted > 0 && self.unchanged > 0 {
|
writeln!(
|
||||||
write!(
|
|
||||||
f,
|
f,
|
||||||
"{} file{} {}, {} file{} left unchanged",
|
"{} file{} {}, {} file{} left unchanged",
|
||||||
self.formatted,
|
changed,
|
||||||
if self.formatted == 1 { "" } else { "s" },
|
if changed == 1 { "" } else { "s" },
|
||||||
match self.mode {
|
match self.mode {
|
||||||
FormatMode::Write => "reformatted",
|
FormatMode::Write => "reformatted",
|
||||||
FormatMode::Check => "would be reformatted",
|
FormatMode::Check | FormatMode::Diff => "would be reformatted",
|
||||||
},
|
},
|
||||||
self.unchanged,
|
unchanged,
|
||||||
if self.unchanged == 1 { "" } else { "s" },
|
if unchanged == 1 { "" } else { "s" },
|
||||||
)
|
)
|
||||||
} else if self.formatted > 0 {
|
} else if changed > 0 {
|
||||||
write!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"{} file{} {}",
|
"{} file{} {}",
|
||||||
self.formatted,
|
changed,
|
||||||
if self.formatted == 1 { "" } else { "s" },
|
if changed == 1 { "" } else { "s" },
|
||||||
match self.mode {
|
match self.mode {
|
||||||
FormatMode::Write => "reformatted",
|
FormatMode::Write => "reformatted",
|
||||||
FormatMode::Check => "would be reformatted",
|
FormatMode::Check | FormatMode::Diff => "would be reformatted",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else if self.unchanged > 0 {
|
} else if unchanged > 0 {
|
||||||
write!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"{} file{} left unchanged",
|
"{} file{} left unchanged",
|
||||||
self.unchanged,
|
unchanged,
|
||||||
if self.unchanged == 1 { "" } else { "s" },
|
if unchanged == 1 { "" } else { "s" },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -374,6 +556,26 @@ pub(crate) enum FormatCommandError {
|
|||||||
Read(Option<PathBuf>, SourceError),
|
Read(Option<PathBuf>, SourceError),
|
||||||
Format(Option<PathBuf>, FormatModuleError),
|
Format(Option<PathBuf>, FormatModuleError),
|
||||||
Write(Option<PathBuf>, SourceError),
|
Write(Option<PathBuf>, SourceError),
|
||||||
|
Diff(Option<PathBuf>, io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatCommandError {
|
||||||
|
fn path(&self) -> Option<&Path> {
|
||||||
|
match self {
|
||||||
|
Self::Ignore(err) => {
|
||||||
|
if let ignore::Error::WithPath { path, .. } = err {
|
||||||
|
Some(path.as_path())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Panic(path, _)
|
||||||
|
| Self::Read(path, _)
|
||||||
|
| Self::Format(path, _)
|
||||||
|
| Self::Write(path, _)
|
||||||
|
| Self::Diff(path, _) => path.as_deref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FormatCommandError {
|
impl Display for FormatCommandError {
|
||||||
@@ -439,6 +641,24 @@ impl Display for FormatCommandError {
|
|||||||
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::Diff(path, err) => {
|
||||||
|
if let Some(path) = path {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}{} {err}",
|
||||||
|
"Failed to generate diff for ".bold(),
|
||||||
|
fs::relativize_path(path).bold(),
|
||||||
|
":".bold()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{} {err}",
|
||||||
|
"Failed to generate diff".bold(),
|
||||||
|
":".bold()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Panic(path, err) => {
|
Self::Panic(path, err) => {
|
||||||
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
||||||
|
|
||||||
@@ -465,3 +685,128 @@ impl Display for FormatCommandError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn warn_incompatible_formatter_settings(
|
||||||
|
pyproject_config: &PyprojectConfig,
|
||||||
|
resolver: Option<&Resolver>,
|
||||||
|
) {
|
||||||
|
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
||||||
|
let mut incompatible_rules = FxHashSet::default();
|
||||||
|
for setting in std::iter::once(&pyproject_config.settings)
|
||||||
|
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||||
|
{
|
||||||
|
for rule in [
|
||||||
|
// The formatter might collapse implicit string concatenation on a single line.
|
||||||
|
Rule::SingleLineImplicitStringConcatenation,
|
||||||
|
// Flags missing trailing commas when all arguments are on its own line:
|
||||||
|
// ```python
|
||||||
|
// def args(
|
||||||
|
// aaaaaaaa, bbbbbbbbb, cccccccccc, ddddddddd, eeeeeeee, ffffff, gggggggggggg, hhhh
|
||||||
|
// ):
|
||||||
|
// pass
|
||||||
|
// ```
|
||||||
|
Rule::MissingTrailingComma,
|
||||||
|
] {
|
||||||
|
if setting.linter.rules.enabled(rule) {
|
||||||
|
incompatible_rules.insert(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !incompatible_rules.is_empty() {
|
||||||
|
let mut rule_names: Vec<_> = incompatible_rules
|
||||||
|
.into_iter()
|
||||||
|
.map(|rule| format!("`{}`", rule.noqa_code()))
|
||||||
|
.collect();
|
||||||
|
rule_names.sort();
|
||||||
|
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, validate settings-specific incompatibilities.
|
||||||
|
for setting in std::iter::once(&pyproject_config.settings)
|
||||||
|
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||||
|
{
|
||||||
|
// Validate all rules that rely on tab styles.
|
||||||
|
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||||
|
&& setting.formatter.indent_style.is_tab()
|
||||||
|
{
|
||||||
|
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all rules that rely on tab styles.
|
||||||
|
if setting.linter.rules.enabled(Rule::IndentWithSpaces)
|
||||||
|
&& setting.formatter.indent_style.is_tab()
|
||||||
|
{
|
||||||
|
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all rules that rely on custom indent widths.
|
||||||
|
if setting.linter.rules.any_enabled(&[
|
||||||
|
Rule::IndentationWithInvalidMultiple,
|
||||||
|
Rule::IndentationWithInvalidMultipleComment,
|
||||||
|
]) && setting.formatter.indent_width.value() != 4
|
||||||
|
{
|
||||||
|
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all rules that rely on quote styles.
|
||||||
|
if setting
|
||||||
|
.linter
|
||||||
|
.rules
|
||||||
|
.any_enabled(&[Rule::BadQuotesInlineString, Rule::AvoidableEscapedQuote])
|
||||||
|
{
|
||||||
|
match (
|
||||||
|
setting.linter.flake8_quotes.inline_quotes,
|
||||||
|
setting.formatter.quote_style,
|
||||||
|
) {
|
||||||
|
(Quote::Double, QuoteStyle::Single) => {
|
||||||
|
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||||
|
}
|
||||||
|
(Quote::Single, QuoteStyle::Double) => {
|
||||||
|
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
|
||||||
|
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
|
||||||
|
{
|
||||||
|
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
|
||||||
|
&& setting.linter.flake8_quotes.docstring_quotes == Quote::Single
|
||||||
|
{
|
||||||
|
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all isort settings.
|
||||||
|
if setting.linter.rules.enabled(Rule::UnsortedImports) {
|
||||||
|
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
|
||||||
|
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
|
||||||
|
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
|
||||||
|
warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
|
||||||
|
if setting.linter.isort.lines_between_types > 1 {
|
||||||
|
warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
|
||||||
|
// This isn't relevant when using `force-single-line`, since isort will never include a trailing comma in that case.
|
||||||
|
if setting.formatter.magic_trailing_comma.is_ignore()
|
||||||
|
&& !setting.linter.isort.force_single_line
|
||||||
|
{
|
||||||
|
if setting.linter.isort.force_wrap_aliases {
|
||||||
|
warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.linter.isort.split_on_trailing_comma {
|
||||||
|
warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ use std::io::stdout;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::warn;
|
use log::error;
|
||||||
|
|
||||||
use ruff_linter::source_kind::SourceKind;
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::python_file_at_path;
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{CliOverrides, FormatArguments};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
use crate::commands::format::{format_source, FormatCommandError, FormatCommandResult, FormatMode};
|
use crate::commands::format::{
|
||||||
|
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
||||||
|
FormatResult, FormattedSource,
|
||||||
|
};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
use crate::stdin::read_from_stdin;
|
use crate::stdin::read_from_stdin;
|
||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
@@ -23,15 +26,24 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
|||||||
overrides,
|
overrides,
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
let mode = if cli.check {
|
|
||||||
FormatMode::Check
|
|
||||||
} else {
|
|
||||||
FormatMode::Write
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
warn_incompatible_formatter_settings(&pyproject_config, None);
|
||||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
|
||||||
return Ok(ExitStatus::Success);
|
let mode = FormatMode::from_cli(cli);
|
||||||
|
|
||||||
|
if pyproject_config.settings.file_resolver.force_exclude {
|
||||||
|
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||||
|
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||||
|
return Ok(ExitStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
let format_settings = &pyproject_config.settings.formatter;
|
||||||
|
if filename
|
||||||
|
.file_name()
|
||||||
|
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||||
|
{
|
||||||
|
return Ok(ExitStatus::Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +62,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
|||||||
) {
|
) {
|
||||||
Ok(result) => match mode {
|
Ok(result) => match mode {
|
||||||
FormatMode::Write => Ok(ExitStatus::Success),
|
FormatMode::Write => Ok(ExitStatus::Success),
|
||||||
FormatMode::Check => {
|
FormatMode::Check | FormatMode::Diff => {
|
||||||
if result.is_formatted() {
|
if result.is_formatted() {
|
||||||
Ok(ExitStatus::Failure)
|
Ok(ExitStatus::Failure)
|
||||||
} else {
|
} else {
|
||||||
@@ -59,7 +71,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("{err}");
|
error!("{err}");
|
||||||
Ok(ExitStatus::Error)
|
Ok(ExitStatus::Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,30 +83,47 @@ fn format_source_code(
|
|||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
mode: FormatMode,
|
mode: FormatMode,
|
||||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
) -> Result<FormatResult, FormatCommandError> {
|
||||||
// Read the source from stdin.
|
// Read the source from stdin.
|
||||||
let source_code = read_from_stdin()
|
let source_code = read_from_stdin()
|
||||||
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err.into()))?;
|
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err.into()))?;
|
||||||
|
|
||||||
let source_kind = match SourceKind::from_source_code(source_code, source_type) {
|
let source_kind = match SourceKind::from_source_code(source_code, source_type) {
|
||||||
Ok(Some(source_kind)) => source_kind,
|
Ok(Some(source_kind)) => source_kind,
|
||||||
Ok(None) => return Ok(FormatCommandResult::Unchanged),
|
Ok(None) => return Ok(FormatResult::Unchanged),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(FormatCommandError::Read(path.map(Path::to_path_buf), err));
|
return Err(FormatCommandError::Read(path.map(Path::to_path_buf), err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the source.
|
// Format the source.
|
||||||
let formatted = format_source(source_kind, source_type, path, settings)?;
|
let formatted = format_source(&source_kind, source_type, path, settings)?;
|
||||||
|
|
||||||
// Write to stdout regardless of whether the source was formatted.
|
match &formatted {
|
||||||
if mode.is_write() {
|
FormattedSource::Formatted(formatted) => match mode {
|
||||||
let mut writer = stdout().lock();
|
FormatMode::Write => {
|
||||||
formatted
|
let mut writer = stdout().lock();
|
||||||
.source_kind()
|
formatted
|
||||||
.write(&mut writer)
|
.write(&mut writer)
|
||||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||||
|
}
|
||||||
|
FormatMode::Check => {}
|
||||||
|
FormatMode::Diff => {
|
||||||
|
source_kind
|
||||||
|
.diff(formatted, path, &mut stdout().lock())
|
||||||
|
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FormattedSource::Unchanged => {
|
||||||
|
// Write to stdout regardless of whether the source was formatted
|
||||||
|
if mode.is_write() {
|
||||||
|
let mut writer = stdout().lock();
|
||||||
|
source_kind
|
||||||
|
.write(&mut writer)
|
||||||
|
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FormatCommandResult::from(formatted))
|
Ok(FormatResult::from(formatted))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ pub(crate) mod linter;
|
|||||||
pub(crate) mod rule;
|
pub(crate) mod rule;
|
||||||
pub(crate) mod show_files;
|
pub(crate) mod show_files;
|
||||||
pub(crate) mod show_settings;
|
pub(crate) mod show_settings;
|
||||||
|
pub(crate) mod version;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl<'a> Explanation<'a> {
|
|||||||
message_formats: rule.message_formats(),
|
message_formats: rule.message_formats(),
|
||||||
fix,
|
fix,
|
||||||
explanation: rule.explanation(),
|
explanation: rule.explanation(),
|
||||||
preview: rule.is_preview(),
|
preview: rule.is_preview() || rule.is_nursery(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ fn format_rule_text(rule: Rule) -> String {
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule.is_preview() {
|
if rule.is_preview() || rule.is_nursery() {
|
||||||
output.push_str(
|
output.push_str(
|
||||||
r#"This rule is in preview and is not stable. The `--preview` flag is required for use."#,
|
r#"This rule is in preview and is not stable. The `--preview` flag is required for use."#,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
@@ -25,12 +25,13 @@ pub(crate) fn show_files(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the list of files.
|
// Print the list of files.
|
||||||
for entry in paths
|
for path in paths
|
||||||
.iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
.map(ResolvedFile::into_path)
|
||||||
|
.sorted_unstable()
|
||||||
{
|
{
|
||||||
writeln!(writer, "{}", entry.path().to_string_lossy())?;
|
writeln!(writer, "{}", path.to_string_lossy())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
@@ -19,16 +19,17 @@ pub(crate) fn show_settings(
|
|||||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
|
|
||||||
// Print the list of files.
|
// Print the list of files.
|
||||||
let Some(entry) = paths
|
let Some(path) = paths
|
||||||
.iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
.map(ResolvedFile::into_path)
|
||||||
|
.sorted_unstable()
|
||||||
.next()
|
.next()
|
||||||
else {
|
else {
|
||||||
bail!("No files found under the given path");
|
bail!("No files found under the given path");
|
||||||
};
|
};
|
||||||
let path = entry.path();
|
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(&path, pyproject_config);
|
||||||
|
|
||||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||||
|
|||||||
21
crates/ruff_cli/src/commands/version.rs
Normal file
21
crates/ruff_cli/src/commands/version.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::io::{self, BufWriter, Write};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::args::HelpFormat;
|
||||||
|
|
||||||
|
/// Display version information
|
||||||
|
pub(crate) fn version(output_format: HelpFormat) -> Result<()> {
|
||||||
|
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||||
|
let version_info = crate::version::version();
|
||||||
|
|
||||||
|
match output_format {
|
||||||
|
HelpFormat::Text => {
|
||||||
|
writeln!(stdout, "ruff {}", &version_info)?;
|
||||||
|
}
|
||||||
|
HelpFormat::Json => {
|
||||||
|
serde_json::to_writer_pretty(stdout, &version_info)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,68 +1,37 @@
|
|||||||
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||||
|
|
||||||
use std::fs::{write, File};
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufWriter, Write};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::ops::AddAssign;
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use filetime::FileTime;
|
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use similar::TextDiff;
|
|
||||||
|
|
||||||
|
use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
||||||
use ruff_linter::logging::DisplayParseError;
|
use ruff_linter::logging::DisplayParseError;
|
||||||
use ruff_linter::message::Message;
|
use ruff_linter::message::Message;
|
||||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||||
use ruff_linter::registry::AsRule;
|
use ruff_linter::registry::AsRule;
|
||||||
|
use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes};
|
||||||
use ruff_linter::settings::{flags, LinterSettings};
|
use ruff_linter::settings::{flags, LinterSettings};
|
||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::{fs, IOError, SyntaxError};
|
use ruff_linter::{fs, IOError, SyntaxError};
|
||||||
use ruff_macros::CacheKey;
|
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||||
use ruff_notebook::{Cell, Notebook, NotebookError, NotebookIndex};
|
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_python_ast::{SourceType, TomlSourceType};
|
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||||
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::cache::Cache;
|
|
||||||
|
|
||||||
#[derive(CacheKey)]
|
|
||||||
pub(crate) struct FileCacheKey {
|
|
||||||
/// Timestamp when the file was last modified before the (cached) check.
|
|
||||||
file_last_modified: FileTime,
|
|
||||||
/// Permissions of the file before the (cached) check.
|
|
||||||
file_permissions_mode: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileCacheKey {
|
|
||||||
fn from_path(path: &Path) -> io::Result<FileCacheKey> {
|
|
||||||
// Construct a cache key for the file
|
|
||||||
let metadata = path.metadata()?;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
let permissions = metadata.permissions().mode();
|
|
||||||
#[cfg(windows)]
|
|
||||||
let permissions: u32 = metadata.permissions().readonly().into();
|
|
||||||
|
|
||||||
Ok(FileCacheKey {
|
|
||||||
file_last_modified: FileTime::from_last_modification_time(&metadata),
|
|
||||||
file_permissions_mode: permissions,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub(crate) struct Diagnostics {
|
pub(crate) struct Diagnostics {
|
||||||
pub(crate) messages: Vec<Message>,
|
pub(crate) messages: Vec<Message>,
|
||||||
pub(crate) fixed: FxHashMap<String, FixTable>,
|
pub(crate) fixed: FixMap,
|
||||||
pub(crate) imports: ImportMap,
|
pub(crate) imports: ImportMap,
|
||||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||||
}
|
}
|
||||||
@@ -75,7 +44,7 @@ impl Diagnostics {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
messages,
|
messages,
|
||||||
fixed: FxHashMap::default(),
|
fixed: FixMap::default(),
|
||||||
imports,
|
imports,
|
||||||
notebook_indexes,
|
notebook_indexes,
|
||||||
}
|
}
|
||||||
@@ -143,25 +112,76 @@ impl Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Add for Diagnostics {
|
||||||
|
type Output = Diagnostics;
|
||||||
|
|
||||||
|
fn add(mut self, other: Self) -> Self::Output {
|
||||||
|
self += other;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AddAssign for Diagnostics {
|
impl AddAssign for Diagnostics {
|
||||||
fn add_assign(&mut self, other: Self) {
|
fn add_assign(&mut self, other: Self) {
|
||||||
self.messages.extend(other.messages);
|
self.messages.extend(other.messages);
|
||||||
self.imports.extend(other.imports);
|
self.imports.extend(other.imports);
|
||||||
for (filename, fixed) in other.fixed {
|
self.fixed += other.fixed;
|
||||||
|
self.notebook_indexes.extend(other.notebook_indexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of fixes indexed by file path.
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
pub(crate) struct FixMap(FxHashMap<String, FixTable>);
|
||||||
|
|
||||||
|
impl FixMap {
|
||||||
|
/// Returns `true` if there are no fixes in the map.
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the fixes in the map, along with the file path.
|
||||||
|
pub(crate) fn iter(&self) -> impl Iterator<Item = (&String, &FixTable)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the fixes in the map.
|
||||||
|
pub(crate) fn values(&self) -> impl Iterator<Item = &FixTable> {
|
||||||
|
self.0.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(String, FixTable)> for FixMap {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (String, FixTable)>>(iter: T) -> Self {
|
||||||
|
Self(
|
||||||
|
iter.into_iter()
|
||||||
|
.filter(|(_, fixes)| !fixes.is_empty())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for FixMap {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
for (filename, fixed) in rhs.0 {
|
||||||
if fixed.is_empty() {
|
if fixed.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let fixed_in_file = self.fixed.entry(filename).or_default();
|
let fixed_in_file = self.0.entry(filename).or_default();
|
||||||
for (rule, count) in fixed {
|
for (rule, count) in fixed {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
*fixed_in_file.entry(rule).or_default() += count;
|
*fixed_in_file.entry(rule).or_default() += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.notebook_indexes.extend(other.notebook_indexes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option<PySourceType> {
|
||||||
|
let ext = path?.extension()?.to_str()?;
|
||||||
|
extension.get(ext).map(PySourceType::from)
|
||||||
|
}
|
||||||
|
|
||||||
/// Lint the source code at the given `Path`.
|
/// Lint the source code at the given `Path`.
|
||||||
pub(crate) fn lint_path(
|
pub(crate) fn lint_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
@@ -170,23 +190,31 @@ pub(crate) fn lint_path(
|
|||||||
cache: Option<&Cache>,
|
cache: Option<&Cache>,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
|
unsafe_fixes: UnsafeFixes,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
// Check the cache.
|
// Check the cache.
|
||||||
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
|
|
||||||
// side-effects that aren't captured in the cache. (In practice, it's fine
|
|
||||||
// to cache `fixer::Mode::Apply`, since a file either has no fixes, or we'll
|
|
||||||
// write the fixes to disk, thus invalidating the cache. But it's a bit hard
|
|
||||||
// to reason about. We need to come up with a better solution here.)
|
|
||||||
let caching = match cache {
|
let caching = match cache {
|
||||||
Some(cache) if noqa.into() && fix_mode.is_generate() => {
|
Some(cache) if noqa.into() => {
|
||||||
let relative_path = cache
|
let relative_path = cache
|
||||||
.relative_path(path)
|
.relative_path(path)
|
||||||
.expect("wrong package cache for file");
|
.expect("wrong package cache for file");
|
||||||
|
|
||||||
let cache_key = FileCacheKey::from_path(path).context("Failed to create cache key")?;
|
let cache_key = FileCacheKey::from_path(path).context("Failed to create cache key")?;
|
||||||
|
let cached_diagnostics = cache
|
||||||
if let Some(cache) = cache.get(relative_path, &cache_key) {
|
.get(relative_path, &cache_key)
|
||||||
return Ok(cache.as_diagnostics(path));
|
.and_then(|entry| entry.to_diagnostics(path));
|
||||||
|
if let Some(diagnostics) = cached_diagnostics {
|
||||||
|
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||||
|
// and writing the diff to stdout, respectively). If a file has diagnostics, we
|
||||||
|
// need to avoid reading from and writing to the cache in these modes.
|
||||||
|
if match fix_mode {
|
||||||
|
flags::FixMode::Generate => true,
|
||||||
|
flags::FixMode::Apply | flags::FixMode::Diff => {
|
||||||
|
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
return Ok(diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash the file metadata for later so when we update the cache it reflects the prerun
|
// Stash the file metadata for later so when we update the cache it reflects the prerun
|
||||||
@@ -198,31 +226,35 @@ pub(crate) fn lint_path(
|
|||||||
|
|
||||||
debug!("Checking: {}", path.display());
|
debug!("Checking: {}", path.display());
|
||||||
|
|
||||||
let source_type = match SourceType::from(path) {
|
let source_type = match override_source_type(Some(path), &settings.extension) {
|
||||||
SourceType::Toml(TomlSourceType::Pyproject) => {
|
Some(source_type) => source_type,
|
||||||
let messages = if settings
|
None => match SourceType::from(path) {
|
||||||
.rules
|
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||||
.iter_enabled()
|
let messages = if settings
|
||||||
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
|
.rules
|
||||||
{
|
.iter_enabled()
|
||||||
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
|
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
|
||||||
Ok(contents) => contents,
|
{
|
||||||
Err(err) => {
|
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
|
||||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
Ok(contents) => contents,
|
||||||
}
|
Err(err) => {
|
||||||
|
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let source_file =
|
||||||
|
SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||||
|
lint_pyproject_toml(source_file, settings)
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
};
|
};
|
||||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
return Ok(Diagnostics {
|
||||||
lint_pyproject_toml(source_file, settings)
|
messages,
|
||||||
} else {
|
..Diagnostics::default()
|
||||||
vec![]
|
});
|
||||||
};
|
}
|
||||||
return Ok(Diagnostics {
|
SourceType::Toml(_) => return Ok(Diagnostics::default()),
|
||||||
messages,
|
SourceType::Python(source_type) => source_type,
|
||||||
..Diagnostics::default()
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
SourceType::Toml(_) => return Ok(Diagnostics::default()),
|
|
||||||
SourceType::Python(source_type) => source_type,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
@@ -246,76 +278,24 @@ pub(crate) fn lint_path(
|
|||||||
result,
|
result,
|
||||||
transformed,
|
transformed,
|
||||||
fixed,
|
fixed,
|
||||||
}) = lint_fix(path, package, noqa, settings, &source_kind, source_type)
|
}) = lint_fix(
|
||||||
{
|
path,
|
||||||
|
package,
|
||||||
|
noqa,
|
||||||
|
unsafe_fixes,
|
||||||
|
settings,
|
||||||
|
&source_kind,
|
||||||
|
source_type,
|
||||||
|
) {
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => match transformed.as_ref() {
|
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||||
SourceKind::Python(transformed) => {
|
|
||||||
write(path, transformed.as_bytes())?;
|
|
||||||
}
|
|
||||||
SourceKind::IpyNotebook(notebook) => {
|
|
||||||
let mut writer = BufWriter::new(File::create(path)?);
|
|
||||||
notebook.write(&mut writer)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
match transformed.as_ref() {
|
source_kind.diff(
|
||||||
SourceKind::Python(transformed) => {
|
transformed.as_ref(),
|
||||||
let mut stdout = io::stdout().lock();
|
Some(path),
|
||||||
TextDiff::from_lines(source_kind.source_code(), transformed)
|
&mut io::stdout().lock(),
|
||||||
.unified_diff()
|
)?;
|
||||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
|
||||||
.to_writer(&mut stdout)?;
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
SourceKind::IpyNotebook(dest_notebook) => {
|
|
||||||
// We need to load the notebook again, since we might've
|
|
||||||
// mutated it.
|
|
||||||
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
// Cell indices are 1-based.
|
|
||||||
for ((idx, src_cell), dest_cell) in (1u32..)
|
|
||||||
.zip(src_notebook.cells().iter())
|
|
||||||
.zip(dest_notebook.cells().iter())
|
|
||||||
{
|
|
||||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
|
||||||
(src_cell, dest_cell)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
TextDiff::from_lines(
|
|
||||||
&src_code_cell.source.to_string(),
|
|
||||||
&dest_code_cell.source.to_string(),
|
|
||||||
)
|
|
||||||
.unified_diff()
|
|
||||||
// Jupyter notebook cells don't necessarily have a newline
|
|
||||||
// at the end. For example,
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// For a cell containing the above code, there'll only be one line,
|
|
||||||
// and it won't have a newline at the end. If it did, there'd be
|
|
||||||
// two lines, and the second line would be empty:
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// print("hello")
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
.missing_newline_hint(false)
|
|
||||||
.header(
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
|
||||||
)
|
|
||||||
.to_writer(&mut stdout)?;
|
|
||||||
}
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
}
|
}
|
||||||
@@ -338,13 +318,25 @@ pub(crate) fn lint_path(
|
|||||||
if let Some((cache, relative_path, key)) = caching {
|
if let Some((cache, relative_path, key)) = caching {
|
||||||
// We don't cache parsing errors.
|
// We don't cache parsing errors.
|
||||||
if parse_error.is_none() {
|
if parse_error.is_none() {
|
||||||
cache.update(
|
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||||
relative_path.to_owned(),
|
// and writing the diff to stdout, respectively). If a file has diagnostics, we
|
||||||
key,
|
// need to avoid reading from and writing to the cache in these modes.
|
||||||
&messages,
|
if match fix_mode {
|
||||||
&imports,
|
flags::FixMode::Generate => true,
|
||||||
source_kind.as_ipy_notebook().map(Notebook::index),
|
flags::FixMode::Apply | flags::FixMode::Diff => {
|
||||||
);
|
messages.is_empty() && fixed.is_empty()
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
cache.update_lint(
|
||||||
|
relative_path.to_owned(),
|
||||||
|
&key,
|
||||||
|
LintCacheData::from_messages(
|
||||||
|
&messages,
|
||||||
|
imports.clone(),
|
||||||
|
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +362,7 @@ pub(crate) fn lint_path(
|
|||||||
|
|
||||||
Ok(Diagnostics {
|
Ok(Diagnostics {
|
||||||
messages,
|
messages,
|
||||||
fixed: FxHashMap::from_iter([(fs::relativize_path(path), fixed)]),
|
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||||
imports,
|
imports,
|
||||||
notebook_indexes,
|
notebook_indexes,
|
||||||
})
|
})
|
||||||
@@ -387,8 +379,15 @@ pub(crate) fn lint_stdin(
|
|||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
// TODO(charlie): Support `pyproject.toml`.
|
// TODO(charlie): Support `pyproject.toml`.
|
||||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
let source_type = if let Some(source_type) =
|
||||||
return Ok(Diagnostics::default());
|
override_source_type(path, &settings.linter.extension)
|
||||||
|
{
|
||||||
|
source_type
|
||||||
|
} else {
|
||||||
|
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||||
|
return Ok(Diagnostics::default());
|
||||||
|
};
|
||||||
|
source_type
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract the sources from the file.
|
// Extract the sources from the file.
|
||||||
@@ -416,6 +415,7 @@ pub(crate) fn lint_stdin(
|
|||||||
path.unwrap_or_else(|| Path::new("-")),
|
path.unwrap_or_else(|| Path::new("-")),
|
||||||
package,
|
package,
|
||||||
noqa,
|
noqa,
|
||||||
|
settings.unsafe_fixes,
|
||||||
&settings.linter,
|
&settings.linter,
|
||||||
&source_kind,
|
&source_kind,
|
||||||
source_type,
|
source_type,
|
||||||
@@ -428,20 +428,7 @@ pub(crate) fn lint_stdin(
|
|||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
// But only write a diff if it's non-empty.
|
// But only write a diff if it's non-empty.
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
let text_diff = TextDiff::from_lines(
|
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
|
||||||
source_kind.source_code(),
|
|
||||||
transformed.source_code(),
|
|
||||||
);
|
|
||||||
let mut unified_diff = text_diff.unified_diff();
|
|
||||||
if let Some(path) = path {
|
|
||||||
unified_diff
|
|
||||||
.header(&fs::relativize_path(path), &fs::relativize_path(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
unified_diff.to_writer(&mut stdout)?;
|
|
||||||
stdout.write_all(b"\n")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
@@ -500,7 +487,7 @@ pub(crate) fn lint_stdin(
|
|||||||
|
|
||||||
Ok(Diagnostics {
|
Ok(Diagnostics {
|
||||||
messages,
|
messages,
|
||||||
fixed: FxHashMap::from_iter([(
|
fixed: FixMap::from_iter([(
|
||||||
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
|
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
|
||||||
fixed,
|
fixed,
|
||||||
)]),
|
)]),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(clippy::print_stdout)]
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, stdout, BufWriter, Write};
|
use std::io::{self, stdout, BufWriter, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -6,16 +8,17 @@ use std::sync::mpsc::channel;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
use colored::Colorize;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||||
|
|
||||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags::FixMode;
|
||||||
use ruff_linter::settings::types::SerializationFormat;
|
use ruff_linter::settings::types::SerializationFormat;
|
||||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::args::{Args, CheckCommand, Command, FormatCommand};
|
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
|
||||||
use crate::printer::{Flags as PrinterFlags, Printer};
|
use crate::printer::{Flags as PrinterFlags, Printer};
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
@@ -26,6 +29,7 @@ mod panic;
|
|||||||
mod printer;
|
mod printer;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
mod stdin;
|
mod stdin;
|
||||||
|
mod version;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum ExitStatus {
|
pub enum ExitStatus {
|
||||||
@@ -97,6 +101,15 @@ fn is_stdin(files: &[PathBuf], stdin_filename: Option<&Path>) -> bool {
|
|||||||
file == Path::new("-")
|
file == Path::new("-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the actual value of the `format` desired from either `output_format`
|
||||||
|
/// or `format`, and warn the user if they're using the deprecated form.
|
||||||
|
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
|
||||||
|
if format.is_some() {
|
||||||
|
warn_user!("The `--format` argument is deprecated. Use `--output-format` instead.");
|
||||||
|
}
|
||||||
|
format.unwrap_or(output_format)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
Args {
|
Args {
|
||||||
command,
|
command,
|
||||||
@@ -104,8 +117,6 @@ pub fn run(
|
|||||||
}: Args,
|
}: Args,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
{
|
{
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
let default_panic_hook = std::panic::take_hook();
|
let default_panic_hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
#[allow(clippy::print_stderr)]
|
#[allow(clippy::print_stderr)]
|
||||||
@@ -135,12 +146,22 @@ pub fn run(
|
|||||||
set_up_logging(&log_level)?;
|
set_up_logging(&log_level)?;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Command::Rule { rule, all, format } => {
|
Command::Version { output_format } => {
|
||||||
|
commands::version::version(output_format)?;
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
||||||
|
Command::Rule {
|
||||||
|
rule,
|
||||||
|
all,
|
||||||
|
format,
|
||||||
|
mut output_format,
|
||||||
|
} => {
|
||||||
|
output_format = resolve_help_output_format(output_format, format);
|
||||||
if all {
|
if all {
|
||||||
commands::rule::rules(format)?;
|
commands::rule::rules(output_format)?;
|
||||||
}
|
}
|
||||||
if let Some(rule) = rule {
|
if let Some(rule) = rule {
|
||||||
commands::rule::rule(rule, format)?;
|
commands::rule::rule(rule, output_format)?;
|
||||||
}
|
}
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
@@ -148,8 +169,12 @@ pub fn run(
|
|||||||
commands::config::config(option.as_deref())?;
|
commands::config::config(option.as_deref())?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Linter { format } => {
|
Command::Linter {
|
||||||
commands::linter::linter(format)?;
|
format,
|
||||||
|
mut output_format,
|
||||||
|
} => {
|
||||||
|
output_format = resolve_help_output_format(output_format, format);
|
||||||
|
commands::linter::linter(output_format)?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Clean => {
|
Command::Clean => {
|
||||||
@@ -166,11 +191,6 @@ pub fn run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
warn_user_once!(
|
|
||||||
"`ruff format` is a work-in-progress, subject to change at any time, and intended only for \
|
|
||||||
experimentation."
|
|
||||||
);
|
|
||||||
|
|
||||||
let (cli, overrides) = args.partition();
|
let (cli, overrides) = args.partition();
|
||||||
|
|
||||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||||
@@ -181,14 +201,6 @@ fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
if args.format.is_some() {
|
|
||||||
if std::env::var("RUFF_FORMAT").is_ok() {
|
|
||||||
warn_user!("The environment variable `RUFF_FORMAT` is deprecated. Use `RUFF_OUTPUT_FORMAT` instead.");
|
|
||||||
} else {
|
|
||||||
warn_user!("The argument `--format=<FORMAT>` is deprecated. Use `--output-format=<FORMAT>` instead.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (cli, overrides) = args.partition();
|
let (cli, overrides) = args.partition();
|
||||||
|
|
||||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||||
@@ -208,6 +220,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
}
|
}
|
||||||
_ => Box::new(BufWriter::new(io::stdout())),
|
_ => Box::new(BufWriter::new(io::stdout())),
|
||||||
};
|
};
|
||||||
|
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
|
||||||
|
|
||||||
if cli.show_settings {
|
if cli.show_settings {
|
||||||
commands::show_settings::show_settings(
|
commands::show_settings::show_settings(
|
||||||
@@ -228,6 +241,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
let Settings {
|
let Settings {
|
||||||
fix,
|
fix,
|
||||||
fix_only,
|
fix_only,
|
||||||
|
unsafe_fixes,
|
||||||
output_format,
|
output_format,
|
||||||
show_fixes,
|
show_fixes,
|
||||||
show_source,
|
show_source,
|
||||||
@@ -236,17 +250,20 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
|
|
||||||
// Fix rules are as follows:
|
// Fix rules are as follows:
|
||||||
// - By default, generate all fixes, but don't apply them to the filesystem.
|
// - By default, generate all fixes, but don't apply them to the filesystem.
|
||||||
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or
|
// - If `--fix` or `--fix-only` is set, apply applicable fixes to the filesystem (or
|
||||||
// print them to stdout, if we're reading from stdin).
|
// print them to stdout, if we're reading from stdin).
|
||||||
// - If `--diff` or `--fix-only` are set, don't print any violations (only
|
// - If `--diff` or `--fix-only` are set, don't print any violations (only applicable fixes)
|
||||||
// fixes).
|
// - By default, applicable fixes only include [`Applicablility::Automatic`], but if
|
||||||
|
// `--unsafe-fixes` is set, then [`Applicablility::Suggested`] fixes are included.
|
||||||
|
|
||||||
let fix_mode = if cli.diff {
|
let fix_mode = if cli.diff {
|
||||||
flags::FixMode::Diff
|
FixMode::Diff
|
||||||
} else if fix || fix_only {
|
} else if fix || fix_only {
|
||||||
flags::FixMode::Apply
|
FixMode::Apply
|
||||||
} else {
|
} else {
|
||||||
flags::FixMode::Generate
|
FixMode::Generate
|
||||||
};
|
};
|
||||||
|
|
||||||
let cache = !cli.no_cache;
|
let cache = !cli.no_cache;
|
||||||
let noqa = !cli.ignore_noqa;
|
let noqa = !cli.ignore_noqa;
|
||||||
let mut printer_flags = PrinterFlags::empty();
|
let mut printer_flags = PrinterFlags::empty();
|
||||||
@@ -290,11 +307,17 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
let printer = Printer::new(output_format, log_level, fix_mode, printer_flags);
|
let printer = Printer::new(
|
||||||
|
output_format,
|
||||||
|
log_level,
|
||||||
|
fix_mode,
|
||||||
|
unsafe_fixes,
|
||||||
|
printer_flags,
|
||||||
|
);
|
||||||
|
|
||||||
if cli.watch {
|
if cli.watch {
|
||||||
if output_format != SerializationFormat::Text {
|
if output_format != SerializationFormat::Text {
|
||||||
warn_user!("--format 'text' is used in watch mode.");
|
warn_user!("`--output-format text` is always used in watch mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the file watcher.
|
// Configure the file watcher.
|
||||||
@@ -318,6 +341,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
|
unsafe_fixes,
|
||||||
)?;
|
)?;
|
||||||
printer.write_continuously(&mut writer, &messages)?;
|
printer.write_continuously(&mut writer, &messages)?;
|
||||||
|
|
||||||
@@ -350,6 +374,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
|
unsafe_fixes,
|
||||||
)?;
|
)?;
|
||||||
printer.write_continuously(&mut writer, &messages)?;
|
printer.write_continuously(&mut writer, &messages)?;
|
||||||
}
|
}
|
||||||
@@ -376,18 +401,22 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
|||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
|
unsafe_fixes,
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always try to print violations (the printer itself may suppress output),
|
// Always try to print violations (though the printer itself may suppress output)
|
||||||
// unless we're writing fixes via stdin (in which case, the transformed
|
// If we're writing fixes via stdin, the transformed source code goes to the writer
|
||||||
// source code goes to stdout).
|
// so send the summary to stderr instead
|
||||||
if !(is_stdin && matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff)) {
|
let mut summary_writer = if is_stdin && matches!(fix_mode, FixMode::Apply | FixMode::Diff) {
|
||||||
if cli.statistics {
|
stderr_writer
|
||||||
printer.write_statistics(&diagnostics, &mut writer)?;
|
} else {
|
||||||
} else {
|
writer
|
||||||
printer.write_once(&diagnostics, &mut writer)?;
|
};
|
||||||
}
|
if cli.statistics {
|
||||||
|
printer.write_statistics(&diagnostics, &mut summary_writer)?;
|
||||||
|
} else {
|
||||||
|
printer.write_once(&diagnostics, &mut summary_writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cli.exit_zero {
|
if !cli.exit_zero {
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ use anyhow::Result;
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::{iterate, Itertools};
|
use itertools::{iterate, Itertools};
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use ruff_linter::fs::relativize_path;
|
use ruff_linter::fs::relativize_path;
|
||||||
use ruff_linter::linter::FixTable;
|
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
use ruff_linter::message::{
|
use ruff_linter::message::{
|
||||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||||
@@ -19,10 +17,10 @@ use ruff_linter::message::{
|
|||||||
};
|
};
|
||||||
use ruff_linter::notify_user;
|
use ruff_linter::notify_user;
|
||||||
use ruff_linter::registry::{AsRule, Rule};
|
use ruff_linter::registry::{AsRule, Rule};
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags::{self};
|
||||||
use ruff_linter::settings::types::SerializationFormat;
|
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
|
||||||
|
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::{Diagnostics, FixMap};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default, Debug, Copy, Clone)]
|
#[derive(Default, Debug, Copy, Clone)]
|
||||||
@@ -73,6 +71,7 @@ pub(crate) struct Printer {
|
|||||||
format: SerializationFormat,
|
format: SerializationFormat,
|
||||||
log_level: LogLevel,
|
log_level: LogLevel,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
|
unsafe_fixes: UnsafeFixes,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +80,14 @@ impl Printer {
|
|||||||
format: SerializationFormat,
|
format: SerializationFormat,
|
||||||
log_level: LogLevel,
|
log_level: LogLevel,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
|
unsafe_fixes: UnsafeFixes,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
format,
|
format,
|
||||||
log_level,
|
log_level,
|
||||||
fix_mode,
|
fix_mode,
|
||||||
|
unsafe_fixes,
|
||||||
flags,
|
flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,12 +100,15 @@ impl Printer {
|
|||||||
|
|
||||||
fn write_summary_text(&self, writer: &mut dyn Write, diagnostics: &Diagnostics) -> Result<()> {
|
fn write_summary_text(&self, writer: &mut dyn Write, diagnostics: &Diagnostics) -> Result<()> {
|
||||||
if self.log_level >= LogLevel::Default {
|
if self.log_level >= LogLevel::Default {
|
||||||
|
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||||
|
|
||||||
|
let fixed = diagnostics
|
||||||
|
.fixed
|
||||||
|
.values()
|
||||||
|
.flat_map(std::collections::HashMap::values)
|
||||||
|
.sum::<usize>();
|
||||||
|
|
||||||
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||||
let fixed = diagnostics
|
|
||||||
.fixed
|
|
||||||
.values()
|
|
||||||
.flat_map(std::collections::HashMap::values)
|
|
||||||
.sum::<usize>();
|
|
||||||
let remaining = diagnostics.messages.len();
|
let remaining = diagnostics.messages.len();
|
||||||
let total = fixed + remaining;
|
let total = fixed + remaining;
|
||||||
if fixed > 0 {
|
if fixed > 0 {
|
||||||
@@ -118,32 +122,83 @@ impl Printer {
|
|||||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_fix_status(self.fix_mode) {
|
if let Some(fixables) = fixables {
|
||||||
let num_fixable = diagnostics
|
let fix_prefix = format!("[{}]", "*".cyan());
|
||||||
.messages
|
|
||||||
.iter()
|
if self.unsafe_fixes.is_enabled() {
|
||||||
.filter(|message| message.fix.is_some())
|
if fixables.applicable > 0 {
|
||||||
.count();
|
writeln!(
|
||||||
if num_fixable > 0 {
|
writer,
|
||||||
writeln!(
|
"{fix_prefix} {} fixable with the --fix option.",
|
||||||
writer,
|
fixables.applicable
|
||||||
"[{}] {num_fixable} potentially fixable with the --fix option.",
|
)?;
|
||||||
"*".cyan(),
|
}
|
||||||
)?;
|
} else {
|
||||||
|
if fixables.applicable > 0 && fixables.unapplicable_unsafe > 0 {
|
||||||
|
let es = if fixables.unapplicable_unsafe == 1 {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"es"
|
||||||
|
};
|
||||||
|
writeln!(writer,
|
||||||
|
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||||
|
fixables.applicable, fixables.unapplicable_unsafe
|
||||||
|
)?;
|
||||||
|
} else if fixables.applicable > 0 {
|
||||||
|
// Only applicable fixes
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{fix_prefix} {} fixable with the `--fix` option.",
|
||||||
|
fixables.applicable,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// Only unapplicable fixes
|
||||||
|
let es = if fixables.unapplicable_unsafe == 1 {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"es"
|
||||||
|
};
|
||||||
|
writeln!(writer,
|
||||||
|
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||||
|
fixables.unapplicable_unsafe
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let fixed = diagnostics
|
// Check if there are unapplied fixes
|
||||||
.fixed
|
let unapplied = {
|
||||||
.values()
|
if let Some(fixables) = fixables {
|
||||||
.flat_map(std::collections::HashMap::values)
|
fixables.unapplicable_unsafe
|
||||||
.sum::<usize>();
|
|
||||||
if fixed > 0 {
|
|
||||||
let s = if fixed == 1 { "" } else { "s" };
|
|
||||||
if self.fix_mode.is_apply() {
|
|
||||||
writeln!(writer, "Fixed {fixed} error{s}.")?;
|
|
||||||
} else {
|
} else {
|
||||||
writeln!(writer, "Would fix {fixed} error{s}.")?;
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if unapplied > 0 {
|
||||||
|
let es = if unapplied == 1 { "" } else { "es" };
|
||||||
|
if fixed > 0 {
|
||||||
|
let s = if fixed == 1 { "" } else { "s" };
|
||||||
|
if self.fix_mode.is_apply() {
|
||||||
|
writeln!(writer, "Fixed {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "Would fix {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.fix_mode.is_apply() {
|
||||||
|
writeln!(writer, "No errors fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "No errors would be fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if fixed > 0 {
|
||||||
|
let s = if fixed == 1 { "" } else { "s" };
|
||||||
|
if self.fix_mode.is_apply() {
|
||||||
|
writeln!(writer, "Fixed {fixed} error{s}.")?;
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "Would fix {fixed} error{s}.")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +233,7 @@ impl Printer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||||
|
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||||
|
|
||||||
match self.format {
|
match self.format {
|
||||||
SerializationFormat::Json => {
|
SerializationFormat::Json => {
|
||||||
@@ -191,9 +247,10 @@ impl Printer {
|
|||||||
}
|
}
|
||||||
SerializationFormat::Text => {
|
SerializationFormat::Text => {
|
||||||
TextEmitter::default()
|
TextEmitter::default()
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||||
|
.with_unsafe_fixes(self.unsafe_fixes)
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
|
|
||||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||||
@@ -209,7 +266,8 @@ impl Printer {
|
|||||||
SerializationFormat::Grouped => {
|
SerializationFormat::Grouped => {
|
||||||
GroupedEmitter::default()
|
GroupedEmitter::default()
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||||
|
.with_unsafe_fixes(self.unsafe_fixes)
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
|
|
||||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||||
@@ -359,6 +417,8 @@ impl Printer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||||
|
|
||||||
if !diagnostics.messages.is_empty() {
|
if !diagnostics.messages.is_empty() {
|
||||||
if self.log_level >= LogLevel::Default {
|
if self.log_level >= LogLevel::Default {
|
||||||
writeln!(writer)?;
|
writeln!(writer)?;
|
||||||
@@ -366,8 +426,9 @@ impl Printer {
|
|||||||
|
|
||||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||||
TextEmitter::default()
|
TextEmitter::default()
|
||||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||||
|
.with_unsafe_fixes(self.unsafe_fixes)
|
||||||
.emit(writer, &diagnostics.messages, &context)?;
|
.emit(writer, &diagnostics.messages, &context)?;
|
||||||
}
|
}
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
@@ -390,16 +451,16 @@ fn num_digits(n: usize) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the [`Printer`] should indicate that a rule is fixable.
|
/// Return `true` if the [`Printer`] should indicate that a rule is fixable.
|
||||||
const fn show_fix_status(fix_mode: flags::FixMode) -> bool {
|
fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics>) -> bool {
|
||||||
// If we're in application mode, avoid indicating that a rule is fixable.
|
// If we're in application mode, avoid indicating that a rule is fixable.
|
||||||
// If the specific violation were truly fixable, it would've been fixed in
|
// If the specific violation were truly fixable, it would've been fixed in
|
||||||
// this pass! (We're occasionally unable to determine whether a specific
|
// this pass! (We're occasionally unable to determine whether a specific
|
||||||
// violation is fixable without trying to fix it, so if fix is not
|
// violation is fixable without trying to fix it, so if fix is not
|
||||||
// enabled, we may inadvertently indicate that a rule is fixable.)
|
// enabled, we may inadvertently indicate that a rule is fixable.)
|
||||||
!fix_mode.is_apply()
|
(!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
|
||||||
let total = fixed
|
let total = fixed
|
||||||
.values()
|
.values()
|
||||||
.map(|table| table.values().sum::<usize>())
|
.map(|table| table.values().sum::<usize>())
|
||||||
@@ -439,3 +500,43 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Statistics for [applicable][ruff_diagnostics::Applicability] fixes.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FixableStatistics {
|
||||||
|
applicable: u32,
|
||||||
|
unapplicable_unsafe: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixableStatistics {
|
||||||
|
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
|
||||||
|
let mut applicable = 0;
|
||||||
|
let mut unapplicable_unsafe = 0;
|
||||||
|
|
||||||
|
for message in &diagnostics.messages {
|
||||||
|
if let Some(fix) = &message.fix {
|
||||||
|
if fix.applies(unsafe_fixes.required_applicability()) {
|
||||||
|
applicable += 1;
|
||||||
|
} else {
|
||||||
|
// Do not include unapplicable fixes at other levels that do not provide an opt-in
|
||||||
|
if fix.applicability().is_unsafe() {
|
||||||
|
unapplicable_unsafe += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if applicable == 0 && unapplicable_unsafe == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self {
|
||||||
|
applicable,
|
||||||
|
unapplicable_unsafe,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_applicable_fixes(&self) -> bool {
|
||||||
|
self.applicable > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: crates/ruff_cli/src/version.rs
|
||||||
|
expression: version
|
||||||
|
---
|
||||||
|
0.0.0
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: crates/ruff_cli/src/version.rs
|
||||||
|
expression: version
|
||||||
|
---
|
||||||
|
0.0.0 (53b0f5d92 2023-10-19)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: crates/ruff_cli/src/version.rs
|
||||||
|
expression: version
|
||||||
|
---
|
||||||
|
0.0.0+24 (53b0f5d92 2023-10-19)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: crates/ruff_cli/src/version.rs
|
||||||
|
expression: version
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"version": "0.0.0",
|
||||||
|
"commit_info": {
|
||||||
|
"short_commit_hash": "53b0f5d92",
|
||||||
|
"commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
|
||||||
|
"commit_date": "2023-10-19",
|
||||||
|
"last_tag": "v0.0.1",
|
||||||
|
"commits_since_last_tag": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
130
crates/ruff_cli/src/version.rs
Normal file
130
crates/ruff_cli/src/version.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
//! Code for representing Ruff's release version number.
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Information about the git repository where Ruff was built from.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct CommitInfo {
|
||||||
|
short_commit_hash: String,
|
||||||
|
commit_hash: String,
|
||||||
|
commit_date: String,
|
||||||
|
last_tag: Option<String>,
|
||||||
|
commits_since_last_tag: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ruff's version.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct VersionInfo {
|
||||||
|
/// Ruff's version, such as "0.5.1"
|
||||||
|
version: String,
|
||||||
|
/// Information about the git commit we may have been built from.
|
||||||
|
///
|
||||||
|
/// `None` if not built from a git repo or if retrieval failed.
|
||||||
|
commit_info: Option<CommitInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VersionInfo {
|
||||||
|
/// Formatted version information: "<version>[+<commits>] (<commit> <date>)"
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.version)?;
|
||||||
|
|
||||||
|
if let Some(ref ci) = self.commit_info {
|
||||||
|
if ci.commits_since_last_tag > 0 {
|
||||||
|
write!(f, "+{}", ci.commits_since_last_tag)?;
|
||||||
|
}
|
||||||
|
write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns information about Ruff's version.
|
||||||
|
pub(crate) fn version() -> VersionInfo {
|
||||||
|
// Environment variables are only read at compile-time
|
||||||
|
macro_rules! option_env_str {
|
||||||
|
($name:expr) => {
|
||||||
|
option_env!($name).map(|s| s.to_string())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This version is pulled from Cargo.toml and set by Cargo
|
||||||
|
let version = option_env_str!("CARGO_PKG_VERSION").unwrap();
|
||||||
|
|
||||||
|
// Commit info is pulled from git and set by `build.rs`
|
||||||
|
let commit_info = option_env_str!("RUFF_COMMIT_HASH").map(|commit_hash| CommitInfo {
|
||||||
|
short_commit_hash: option_env_str!("RUFF_COMMIT_SHORT_HASH").unwrap(),
|
||||||
|
commit_hash,
|
||||||
|
commit_date: option_env_str!("RUFF_COMMIT_DATE").unwrap(),
|
||||||
|
last_tag: option_env_str!("RUFF_LAST_TAG"),
|
||||||
|
commits_since_last_tag: option_env_str!("RUFF_LAST_TAG_DISTANCE")
|
||||||
|
.as_deref()
|
||||||
|
.map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
|
||||||
|
});
|
||||||
|
|
||||||
|
VersionInfo {
|
||||||
|
version,
|
||||||
|
commit_info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use insta::{assert_display_snapshot, assert_json_snapshot};
|
||||||
|
|
||||||
|
use super::{CommitInfo, VersionInfo};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_formatting() {
|
||||||
|
let version = VersionInfo {
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
|
commit_info: None,
|
||||||
|
};
|
||||||
|
assert_display_snapshot!(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_formatting_with_commit_info() {
|
||||||
|
let version = VersionInfo {
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
|
commit_info: Some(CommitInfo {
|
||||||
|
short_commit_hash: "53b0f5d92".to_string(),
|
||||||
|
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||||
|
last_tag: Some("v0.0.1".to_string()),
|
||||||
|
commit_date: "2023-10-19".to_string(),
|
||||||
|
commits_since_last_tag: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
assert_display_snapshot!(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_formatting_with_commits_since_last_tag() {
|
||||||
|
let version = VersionInfo {
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
|
commit_info: Some(CommitInfo {
|
||||||
|
short_commit_hash: "53b0f5d92".to_string(),
|
||||||
|
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||||
|
last_tag: Some("v0.0.1".to_string()),
|
||||||
|
commit_date: "2023-10-19".to_string(),
|
||||||
|
commits_since_last_tag: 24,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
assert_display_snapshot!(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_serializable() {
|
||||||
|
let version = VersionInfo {
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
|
commit_info: Some(CommitInfo {
|
||||||
|
short_commit_hash: "53b0f5d92".to_string(),
|
||||||
|
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||||
|
last_tag: Some("v0.0.1".to_string()),
|
||||||
|
commit_date: "2023-10-19".to_string(),
|
||||||
|
commits_since_last_tag: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
assert_json_snapshot!(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#![cfg(not(target_family = "wasm"))]
|
#![cfg(not(target_family = "wasm"))]
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ const BIN_NAME: &str = "ruff";
|
|||||||
#[test]
|
#[test]
|
||||||
fn default_options() {
|
fn default_options() {
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(["format", "--isolated"])
|
.args(["format", "--isolated", "--stdin-filename", "test.py"])
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin(r#"
|
.pass_stdin(r#"
|
||||||
def foo(arg1, arg2,):
|
def foo(arg1, arg2,):
|
||||||
@@ -39,7 +40,6 @@ if condition:
|
|||||||
print('Hy "Micha"') # Should not change quotes
|
print('Hy "Micha"') # Should not change quotes
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +50,9 @@ fn format_options() -> Result<()> {
|
|||||||
fs::write(
|
fs::write(
|
||||||
&ruff_toml,
|
&ruff_toml,
|
||||||
r#"
|
r#"
|
||||||
|
indent-width = 8
|
||||||
|
line-length = 84
|
||||||
|
|
||||||
[format]
|
[format]
|
||||||
indent-style = "tab"
|
indent-style = "tab"
|
||||||
quote-style = "single"
|
quote-style = "single"
|
||||||
@@ -64,7 +67,7 @@ line-ending = "cr-lf"
|
|||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin(r#"
|
.pass_stdin(r#"
|
||||||
def foo(arg1, arg2,):
|
def foo(arg1, arg2,):
|
||||||
print("Shouldn't change quotes")
|
print("Shouldn't change quotes. It exceeds the line width with the tab size 8")
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
@@ -76,14 +79,248 @@ if condition:
|
|||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
def foo(arg1, arg2):
|
def foo(arg1, arg2):
|
||||||
print("Shouldn't change quotes")
|
print(
|
||||||
|
"Shouldn't change quotes. It exceeds the line width with the tab size 8"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
print('Should change quotes')
|
print('Should change quotes')
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mixed_line_endings() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("main.py"),
|
||||||
|
"from test import say_hy\n\nif __name__ == \"__main__\":\n say_hy(\"dear Ruff contributor\")\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("test.py"),
|
||||||
|
"def say_hy(name: str):\r\n print(f\"Hy {name}\")\r\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--no-cache", "--diff", "--isolated"])
|
||||||
|
.arg("."), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
2 files left unchanged
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exclude() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-exclude = ["out"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["test.py", "generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("main.py"),
|
||||||
|
r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Excluded file but passed to the CLI directly, should be formatted
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("generated.py"),
|
||||||
|
r#"NUMBERS = [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||||
|
]
|
||||||
|
OTHER = "OTHER"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let out_dir = tempdir.path().join("out");
|
||||||
|
fs::create_dir(&out_dir)?;
|
||||||
|
|
||||||
|
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--no-cache", "--check", "--config"])
|
||||||
|
.arg(ruff_toml.file_name().unwrap())
|
||||||
|
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
|
||||||
|
.arg(test_path.file_name().unwrap())
|
||||||
|
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||||
|
.arg("."), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
Would reformat: main.py
|
||||||
|
Would reformat: test.py
|
||||||
|
2 files would be reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn force_exclude() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-exclude = ["out"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["test.py", "generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("main.py"),
|
||||||
|
r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Excluded file but passed to the CLI directly, should be formatted
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("generated.py"),
|
||||||
|
r#"NUMBERS = [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||||
|
]
|
||||||
|
OTHER = "OTHER"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let out_dir = tempdir.path().join("out");
|
||||||
|
fs::create_dir(&out_dir)?;
|
||||||
|
|
||||||
|
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--no-cache", "--force-exclude", "--check", "--config"])
|
||||||
|
.arg(ruff_toml.file_name().unwrap())
|
||||||
|
// Explicitly pass test.py, should be respect the `format.exclude` when `--force-exclude` is present
|
||||||
|
.arg(test_path.file_name().unwrap())
|
||||||
|
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||||
|
.arg("."), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
Would reformat: main.py
|
||||||
|
1 file would be reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exclude_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
ignore = ["Q000", "Q001", "Q002", "Q003"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "-"])
|
||||||
|
.pass_stdin(r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn force_exclude_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
ignore = ["Q000", "Q001", "Q002", "Q003"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "--force-exclude", "-"])
|
||||||
|
.pass_stdin(r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -98,6 +335,9 @@ fn format_option_inheritance() -> Result<()> {
|
|||||||
r#"
|
r#"
|
||||||
extend = "base.toml"
|
extend = "base.toml"
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
extend-select = ["COM812"]
|
||||||
|
|
||||||
[format]
|
[format]
|
||||||
quote-style = "single"
|
quote-style = "single"
|
||||||
"#,
|
"#,
|
||||||
@@ -139,12 +379,47 @@ if condition:
|
|||||||
print('Should change quotes')
|
print('Should change quotes')
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that the legacy `format` option continues to work but emits a warning.
|
#[test]
|
||||||
|
fn deprecated_options() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
tab-size = 2
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({filters => vec![
|
||||||
|
(&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"),
|
||||||
|
]}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["format", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The `tab-size` option has been renamed to `indent-width` to emphasize that it configures the indentation used by the formatter as well as the tab width. Please update your configuration to use `indent-width = <value>` instead.
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Since 0.1.0 the legacy format option is no longer supported
|
||||||
#[test]
|
#[test]
|
||||||
fn legacy_format_option() -> Result<()> {
|
fn legacy_format_option() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
@@ -156,52 +431,385 @@ format = "json"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
insta::with_settings!({filters => vec![
|
||||||
|
(&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"),
|
||||||
|
]}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["check", "--select", "F401", "--no-cache", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ruff failed
|
||||||
|
Cause: Failed to parse `[RUFF-TOML-PATH]`: TOML parse error at line 2, column 10
|
||||||
|
|
|
||||||
|
2 | format = "json"
|
||||||
|
| ^^^^^^
|
||||||
|
invalid type: string "json", expected struct FormatOptions
|
||||||
|
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conflicting_options() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
indent-width = 2
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
select = ["ALL"]
|
||||||
|
ignore = ["D203", "D212"]
|
||||||
|
|
||||||
|
[lint.isort]
|
||||||
|
lines-after-imports = 3
|
||||||
|
lines-between-types = 2
|
||||||
|
force-wrap-aliases = true
|
||||||
|
combine-as-imports = true
|
||||||
|
split-on-trailing-comma = true
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
docstring-quotes = "single"
|
||||||
|
multiline-quotes = "single"
|
||||||
|
|
||||||
|
[format]
|
||||||
|
skip-magic-trailing-comma = true
|
||||||
|
indent-style = "tab"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(["check", "--select", "F401", "--no-cache", "--config"])
|
.args(["format", "--no-cache", "--config"])
|
||||||
.arg(&ruff_toml)
|
.arg(&ruff_toml)
|
||||||
.arg("-")
|
.arg(test_path), @r###"
|
||||||
.pass_stdin(r#"
|
success: true
|
||||||
import os
|
exit_code: 0
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
[
|
1 file reformatted
|
||||||
{
|
|
||||||
"code": "F401",
|
|
||||||
"end_location": {
|
|
||||||
"column": 10,
|
|
||||||
"row": 2
|
|
||||||
},
|
|
||||||
"filename": "-",
|
|
||||||
"fix": {
|
|
||||||
"applicability": "Automatic",
|
|
||||||
"edits": [
|
|
||||||
{
|
|
||||||
"content": "",
|
|
||||||
"end_location": {
|
|
||||||
"column": 1,
|
|
||||||
"row": 3
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"column": 1,
|
|
||||||
"row": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"message": "Remove unused import: `os`"
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"column": 8,
|
|
||||||
"row": 2
|
|
||||||
},
|
|
||||||
"message": "`os` imported but unused",
|
|
||||||
"noqa_row": 2,
|
|
||||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
|
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||||
|
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
|
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
|
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||||
|
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||||
|
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||||
|
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||||
|
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||||
|
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||||
|
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conflicting_options_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
indent-width = 2
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
select = ["ALL"]
|
||||||
|
ignore = ["D203", "D212"]
|
||||||
|
|
||||||
|
[lint.isort]
|
||||||
|
lines-after-imports = 3
|
||||||
|
lines-between-types = 2
|
||||||
|
force-wrap-aliases = true
|
||||||
|
combine-as-imports = true
|
||||||
|
split-on-trailing-comma = true
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
docstring-quotes = "single"
|
||||||
|
multiline-quotes = "single"
|
||||||
|
|
||||||
|
[format]
|
||||||
|
skip-magic-trailing-comma = true
|
||||||
|
indent-style = "tab"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["format", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||||
|
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
|
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||||
|
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||||
|
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||||
|
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||||
|
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||||
|
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||||
|
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||||
|
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_linter_options() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["ALL"]
|
||||||
|
ignore = ["D203", "D212", "COM812", "ISC001"]
|
||||||
|
|
||||||
|
[lint.isort]
|
||||||
|
lines-after-imports = 2
|
||||||
|
lines-between-types = 1
|
||||||
|
force-wrap-aliases = true
|
||||||
|
combine-as-imports = true
|
||||||
|
split-on-trailing-comma = true
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
docstring-quotes = "double"
|
||||||
|
multiline-quotes = "double"
|
||||||
|
|
||||||
|
[format]
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
quote-style = "single"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["format", "--no-cache", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg(test_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_rules_default_options() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
select = ["ALL"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["format", "--no-cache", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg(test_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
1 file reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||||
|
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||||
|
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diff() {
|
||||||
|
let args = ["format", "--no-cache", "--isolated", "--diff"];
|
||||||
|
let fixtures = Path::new("resources").join("test").join("fixtures");
|
||||||
|
let paths = [
|
||||||
|
fixtures.join("unformatted.py"),
|
||||||
|
fixtures.join("formatted.py"),
|
||||||
|
fixtures.join("unformatted.ipynb"),
|
||||||
|
];
|
||||||
|
insta::with_settings!({filters => vec![
|
||||||
|
// Replace windows paths
|
||||||
|
(r"\\", "/"),
|
||||||
|
]}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- resources/test/fixtures/unformatted.ipynb:cell 1
|
||||||
|
+++ resources/test/fixtures/unformatted.ipynb:cell 1
|
||||||
|
@@ -1,3 +1,4 @@
|
||||||
|
import numpy
|
||||||
|
-maths = (numpy.arange(100)**2).sum()
|
||||||
|
-stats= numpy.asarray([1,2,3,4]).median()
|
||||||
|
+
|
||||||
|
+maths = (numpy.arange(100) ** 2).sum()
|
||||||
|
+stats = numpy.asarray([1, 2, 3, 4]).median()
|
||||||
|
--- resources/test/fixtures/unformatted.ipynb:cell 3
|
||||||
|
+++ resources/test/fixtures/unformatted.ipynb:cell 3
|
||||||
|
@@ -1,4 +1,6 @@
|
||||||
|
# A cell with IPython escape command
|
||||||
|
def some_function(foo, bar):
|
||||||
|
pass
|
||||||
|
+
|
||||||
|
+
|
||||||
|
%matplotlib inline
|
||||||
|
--- resources/test/fixtures/unformatted.ipynb:cell 4
|
||||||
|
+++ resources/test/fixtures/unformatted.ipynb:cell 4
|
||||||
|
@@ -1,5 +1,10 @@
|
||||||
|
foo = %pwd
|
||||||
|
-def some_function(foo,bar,):
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def some_function(
|
||||||
|
+ foo,
|
||||||
|
+ bar,
|
||||||
|
+):
|
||||||
|
# Another cell with IPython escape command
|
||||||
|
foo = %pwd
|
||||||
|
print(foo)
|
||||||
|
|
||||||
|
--- resources/test/fixtures/unformatted.py
|
||||||
|
+++ resources/test/fixtures/unformatted.py
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
x = 1
|
||||||
|
-y=2
|
||||||
|
+y = 2
|
||||||
|
z = 3
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
2 files would be reformatted, 1 file left unchanged
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diff_no_change() {
|
||||||
|
let args = ["format", "--no-cache", "--isolated", "--diff"];
|
||||||
|
let fixtures = Path::new("resources").join("test").join("fixtures");
|
||||||
|
let paths = [fixtures.join("unformatted.py")];
|
||||||
|
insta::with_settings!({filters => vec![
|
||||||
|
// Replace windows paths
|
||||||
|
(r"\\", "/"),
|
||||||
|
]}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- resources/test/fixtures/unformatted.py
|
||||||
|
+++ resources/test/fixtures/unformatted.py
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
x = 1
|
||||||
|
-y=2
|
||||||
|
+y = 2
|
||||||
|
z = 3
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
1 file would be reformatted
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diff_stdin_unformatted() {
|
||||||
|
let args = [
|
||||||
|
"format",
|
||||||
|
"--isolated",
|
||||||
|
"--diff",
|
||||||
|
"-",
|
||||||
|
"--stdin-filename",
|
||||||
|
"unformatted.py",
|
||||||
|
];
|
||||||
|
let fixtures = Path::new("resources").join("test").join("fixtures");
|
||||||
|
let unformatted = fs::read(fixtures.join("unformatted.py")).unwrap();
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- unformatted.py
|
||||||
|
+++ unformatted.py
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
x = 1
|
||||||
|
-y=2
|
||||||
|
+y = 2
|
||||||
|
z = 3
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diff_stdin_formatted() {
|
||||||
|
let args = ["format", "--isolated", "--diff", "-"];
|
||||||
|
let fixtures = Path::new("resources").join("test").join("fixtures");
|
||||||
|
let unformatted = fs::read(fixtures.join("formatted.py")).unwrap();
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#![cfg(not(target_family = "wasm"))]
|
#![cfg(not(target_family = "wasm"))]
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::fs::Permissions;
|
use std::fs::Permissions;
|
||||||
@@ -12,13 +11,13 @@ use std::process::Command;
|
|||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
#[cfg(unix)]
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -46,16 +45,16 @@ fn stdin_success() {
|
|||||||
fn stdin_error() {
|
fn stdin_error() {
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.pass_stdin("import os\n"), @r#"
|
.pass_stdin("import os\n"), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:8: F401 [*] `os` imported but unused
|
-:1:8: F401 [*] `os` imported but unused
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 potentially fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"#);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -69,7 +68,7 @@ fn stdin_filename() {
|
|||||||
----- stdout -----
|
----- stdout -----
|
||||||
F401.py:1:8: F401 [*] `os` imported but unused
|
F401.py:1:8: F401 [*] `os` imported but unused
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 potentially fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -87,7 +86,7 @@ fn stdin_source_type_py() {
|
|||||||
----- stdout -----
|
----- stdout -----
|
||||||
TCH.py:1:8: F401 [*] `os` imported but unused
|
TCH.py:1:8: F401 [*] `os` imported but unused
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 potentially fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -150,6 +149,7 @@ fn stdin_fix_py() {
|
|||||||
print(sys.version)
|
print(sys.version)
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
Found 1 error (1 fixed, 0 remaining).
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +316,120 @@ fn stdin_fix_jupyter() {
|
|||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
"nbformat_minor": 5
|
"nbformat_minor": 5
|
||||||
}
|
}
|
||||||
|
----- stderr -----
|
||||||
|
Found 2 errors (2 fixed, 0 remaining).
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn stdin_override_parser_ipynb() {
|
||||||
|
let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"];
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(args)
|
||||||
|
.pass_stdin(r#"{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import os"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import sys"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"1\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print(1)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused
|
||||||
|
Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stdin_override_parser_py() {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"])
|
||||||
|
.pass_stdin("import os\n"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
F401.ipynb:1:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
@@ -336,6 +450,8 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
|||||||
print(sys.version)
|
print(sys.version)
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
-:3:4: F634 If test is a tuple, which is always `True`
|
||||||
|
Found 2 errors (1 fixed, 1 remaining).
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +598,6 @@ fn stdin_format_jupyter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,8 +747,9 @@ fn nursery_group_selector_preview_enabled() {
|
|||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:1: CPY001 Missing copyright notice at top of file
|
-:1:1: CPY001 Missing copyright notice at top of file
|
||||||
-:1:2: E225 Missing whitespace around operator
|
-:1:2: E225 [*] Missing whitespace around operator
|
||||||
Found 2 errors.
|
Found 2 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The `NURSERY` selector has been deprecated.
|
warning: The `NURSERY` selector has been deprecated.
|
||||||
@@ -652,8 +768,9 @@ fn preview_enabled_prefix() {
|
|||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:1: E741 Ambiguous variable name: `I`
|
-:1:1: E741 Ambiguous variable name: `I`
|
||||||
-:1:2: E225 Missing whitespace around operator
|
-:1:2: E225 [*] Missing whitespace around operator
|
||||||
Found 2 errors.
|
Found 2 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -672,8 +789,9 @@ fn preview_enabled_all() {
|
|||||||
-:1:1: E741 Ambiguous variable name: `I`
|
-:1:1: E741 Ambiguous variable name: `I`
|
||||||
-:1:1: D100 Missing docstring in public module
|
-:1:1: D100 Missing docstring in public module
|
||||||
-:1:1: CPY001 Missing copyright notice at top of file
|
-:1:1: CPY001 Missing copyright notice at top of file
|
||||||
-:1:2: E225 Missing whitespace around operator
|
-:1:2: E225 [*] Missing whitespace around operator
|
||||||
Found 4 errors.
|
Found 4 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||||
@@ -692,8 +810,9 @@ fn preview_enabled_direct() {
|
|||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:2: E225 Missing whitespace around operator
|
-:1:2: E225 [*] Missing whitespace around operator
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -733,6 +852,42 @@ fn preview_disabled_prefix_empty() {
|
|||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
|
||||||
|
// Does not warn that the selection is empty since the user is not trying to enable the rule
|
||||||
|
let args = ["--ignore", "CPY"];
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(args)
|
||||||
|
.pass_stdin("I=42\n"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:1: E741 Ambiguous variable name: `I`
|
||||||
|
Found 1 error.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
|
||||||
|
// Does not warn that the selection is empty since the user is not trying to enable the rule
|
||||||
|
let args = ["--fixable", "CPY"];
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(args)
|
||||||
|
.pass_stdin("I=42\n"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:1: E741 Ambiguous variable name: `I`
|
||||||
|
Found 1 error.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preview_group_selector() {
|
fn preview_group_selector() {
|
||||||
// `--select PREVIEW` should error (selector was removed)
|
// `--select PREVIEW` should error (selector was removed)
|
||||||
@@ -861,7 +1016,7 @@ fn check_input_from_argfile() -> Result<()> {
|
|||||||
----- stdout -----
|
----- stdout -----
|
||||||
/path/to/a.py:1:8: F401 [*] `os` imported but unused
|
/path/to/a.py:1:8: F401 [*] `os` imported but unused
|
||||||
Found 1 error.
|
Found 1 error.
|
||||||
[*] 1 potentially fixable with the --fix option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -869,3 +1024,543 @@ fn check_input_from_argfile() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_hints_hidden_unsafe_fixes() {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format=text",
|
||||||
|
"--isolated",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--no-cache",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["-", "--output-format", "text", "--no-cache", "--isolated", "--select", "F601"])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
Found 1 error.
|
||||||
|
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_shows_unsafe_fixes_with_opt_in() {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format=text",
|
||||||
|
"--isolated",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--no-cache",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the --fix option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_applies_safe_fixes_by_default() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1, 'a': 1}
|
||||||
|
print('foo')
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
Found 2 errors (1 fixed, 1 remaining).
|
||||||
|
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_applies_unsafe_fixes_with_opt_in() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1}
|
||||||
|
print('foo')
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Found 2 errors (2 fixed, 0 remaining).
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_does_not_apply_display_only_fixes() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"B006",
|
||||||
|
"--fix",
|
||||||
|
])
|
||||||
|
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
def add_to_list(item, some_list=[]): ...
|
||||||
|
----- stderr -----
|
||||||
|
-:1:33: B006 Do not use mutable data structures for argument defaults
|
||||||
|
Found 1 error.
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"B006",
|
||||||
|
"--fix",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
def add_to_list(item, some_list=[]): ...
|
||||||
|
----- stderr -----
|
||||||
|
-:1:33: B006 Do not use mutable data structures for argument defaults
|
||||||
|
Found 1 error.
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_only_unsafe_fixes_available() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601",
|
||||||
|
"--fix",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1, 'a': 1}
|
||||||
|
print(('foo'))
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
Found 1 error.
|
||||||
|
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_only_flag_applies_safe_fixes_by_default() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix-only",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1, 'a': 1}
|
||||||
|
print('foo')
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Fixed 1 error (1 additional fix available with `--unsafe-fixes`).
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--fix-only",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
x = {'a': 1}
|
||||||
|
print('foo')
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Fixed 2 errors.
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_shows_safe_fixes_by_default() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--diff",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
@@ -1,2 +1,2 @@
|
||||||
|
x = {'a': 1, 'a': 1}
|
||||||
|
-print(('foo'))
|
||||||
|
+print('foo')
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Would fix 1 error (1 additional fix available with `--unsafe-fixes`).
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_shows_unsafe_fixes_with_opt_in() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
"--diff",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
@@ -1,2 +1,2 @@
|
||||||
|
-x = {'a': 1, 'a': 1}
|
||||||
|
-print(('foo'))
|
||||||
|
+x = {'a': 1}
|
||||||
|
+print('foo')
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Would fix 2 errors.
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"B006",
|
||||||
|
"--diff",
|
||||||
|
"--unsafe-fixes",
|
||||||
|
])
|
||||||
|
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diff_only_unsafe_fixes_available() {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args([
|
||||||
|
"-",
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--isolated",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601",
|
||||||
|
"--diff",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
No errors would be fixed (1 fix available with `--unsafe-fixes`).
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_extend_unsafe_fixes() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
extend-unsafe-fixes = ["UP034"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["check", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.args([
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 Avoid extraneous parentheses
|
||||||
|
Found 2 errors.
|
||||||
|
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_extend_safe_fixes() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
extend-safe-fixes = ["F601"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["check", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.args([
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes() -> Result<()> {
|
||||||
|
// Adding a rule to both options should result in it being treated as unsafe
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
[lint]
|
||||||
|
extend-unsafe-fixes = ["UP034"]
|
||||||
|
extend-safe-fixes = ["UP034"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["check", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.args([
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP034",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 Avoid extraneous parentheses
|
||||||
|
Found 2 errors.
|
||||||
|
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes_by_specificity() -> Result<()> {
|
||||||
|
// Adding a rule to one option with a more specific selector should override the other option
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
target-version = "py310"
|
||||||
|
[lint]
|
||||||
|
extend-unsafe-fixes = ["UP", "UP034"]
|
||||||
|
extend-safe-fixes = ["UP03"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(["check", "--config"])
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.arg("-")
|
||||||
|
.args([
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--no-cache",
|
||||||
|
"--select",
|
||||||
|
"F601,UP018,UP034,UP038",
|
||||||
|
])
|
||||||
|
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\nprint(str('foo'))\nisinstance(x, (int, str))\n"),
|
||||||
|
@r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||||
|
-:2:7: UP034 Avoid extraneous parentheses
|
||||||
|
-:3:7: UP018 Unnecessary `str` call (rewrite as a literal)
|
||||||
|
-:4:1: UP038 [*] Use `X | Y` in `isinstance` call instead of `(X, Y)`
|
||||||
|
Found 4 errors.
|
||||||
|
[*] 1 fixable with the `--fix` option (3 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,16 +31,17 @@ inline-quotes = "single"
|
|||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
.arg(&ruff_toml)
|
.arg(&ruff_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||||
Found 3 errors.
|
Found 3 errors.
|
||||||
[*] 2 potentially fixable with the --fix option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -75,7 +76,7 @@ inline-quotes = "single"
|
|||||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||||
Found 3 errors.
|
Found 3 errors.
|
||||||
[*] 2 potentially fixable with the --fix option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -110,7 +111,7 @@ inline-quotes = "single"
|
|||||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||||
Found 3 errors.
|
Found 3 errors.
|
||||||
[*] 2 potentially fixable with the --fix option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
@@ -149,7 +150,247 @@ inline-quotes = "single"
|
|||||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||||
Found 3 errors.
|
Found 3 errors.
|
||||||
[*] 2 potentially fixable with the --fix option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exclude() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
extend-exclude = ["out"]
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
exclude = ["test.py", "generated.py"]
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("main.py"),
|
||||||
|
r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Excluded file but passed to the CLI directly, should be linted
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("generated.py"),
|
||||||
|
r#"NUMBERS = [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||||
|
]
|
||||||
|
OTHER = "OTHER"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let out_dir = tempdir.path().join("out");
|
||||||
|
fs::create_dir(&out_dir)?;
|
||||||
|
|
||||||
|
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
|
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
||||||
|
.arg(test_path.file_name().unwrap())
|
||||||
|
// Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options
|
||||||
|
.arg("."), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
main.py:4:16: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
main.py:5:12: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
test.py:3:15: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
Found 3 errors.
|
||||||
|
[*] 3 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exclude_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
exclude = ["generated.py"]
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
|
.args(["--stdin-filename", "generated.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
generated.py:4:16: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
generated.py:5:12: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_too_long_width_override() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
line-length = 80
|
||||||
|
select = ["E501"]
|
||||||
|
|
||||||
|
[pycodestyle]
|
||||||
|
max-line-length = 100
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.arg("--config")
|
||||||
|
.arg(&ruff_toml)
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
# longer than 80, but less than 100
|
||||||
|
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜"
|
||||||
|
# longer than 100
|
||||||
|
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜亜亜亜亜亜亜亜"
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
test.py:5:91: E501 Line too long (109 > 100)
|
||||||
|
Found 1 error.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn per_file_ignores_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
|
.args(["--stdin-filename", "generated.py"])
|
||||||
|
.args(["--per-file-ignores", "generated.py:Q"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
generated.py:2:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extend_per_file_ignores_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
|
.args(["--stdin-filename", "generated.py"])
|
||||||
|
.args(["--extend-per-file-ignores", "generated.py:Q"])
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(r#"
|
||||||
|
import os
|
||||||
|
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
generated.py:2:8: F401 [*] `os` imported but unused
|
||||||
|
Found 1 error.
|
||||||
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ exit_code: 1
|
|||||||
----- stdout -----
|
----- stdout -----
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"cell": null,
|
||||||
"code": "F401",
|
"code": "F401",
|
||||||
"end_location": {
|
"end_location": {
|
||||||
"column": 10,
|
"column": 10,
|
||||||
@@ -24,7 +25,7 @@ exit_code: 1
|
|||||||
},
|
},
|
||||||
"filename": "/path/to/F401.py",
|
"filename": "/path/to/F401.py",
|
||||||
"fix": {
|
"fix": {
|
||||||
"applicability": "Automatic",
|
"applicability": "safe",
|
||||||
"edits": [
|
"edits": [
|
||||||
{
|
{
|
||||||
"content": "",
|
"content": "",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ serde_json = { workspace = true }
|
|||||||
similar = { workspace = true }
|
similar = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.8.1"
|
||||||
toml = { workspace = true, features = ["parse"] }
|
toml = { workspace = true, features = ["parse"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-indicatif = { workspace = true }
|
tracing-indicatif = { workspace = true }
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use std::{fmt, fs, io, iter};
|
|||||||
|
|
||||||
use anyhow::{bail, format_err, Context, Error};
|
use anyhow::{bail, format_err, Context, Error};
|
||||||
use clap::{CommandFactory, FromArgMatches};
|
use clap::{CommandFactory, FromArgMatches};
|
||||||
use ignore::DirEntry;
|
|
||||||
use imara_diff::intern::InternedInput;
|
use imara_diff::intern::InternedInput;
|
||||||
use imara_diff::sink::Counter;
|
use imara_diff::sink::Counter;
|
||||||
use imara_diff::{diff, Algorithm};
|
use imara_diff::{diff, Algorithm};
|
||||||
@@ -34,16 +33,16 @@ use ruff_formatter::{FormatError, LineWidth, PrintError};
|
|||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||||
use ruff_python_formatter::{
|
use ruff_python_formatter::{
|
||||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
format_module_source, FormatModuleError, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
||||||
};
|
};
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, Resolver};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||||
|
|
||||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn ruff_check_paths(
|
fn ruff_check_paths(
|
||||||
dirs: &[PathBuf],
|
dirs: &[PathBuf],
|
||||||
) -> anyhow::Result<(
|
) -> anyhow::Result<(
|
||||||
Vec<Result<DirEntry, ignore::Error>>,
|
Vec<Result<ResolvedFile, ignore::Error>>,
|
||||||
Resolver,
|
Resolver,
|
||||||
PyprojectConfig,
|
PyprojectConfig,
|
||||||
)> {
|
)> {
|
||||||
@@ -467,9 +466,9 @@ fn format_dev_project(
|
|||||||
let iter = { paths.into_par_iter() };
|
let iter = { paths.into_par_iter() };
|
||||||
#[cfg(feature = "singlethreaded")]
|
#[cfg(feature = "singlethreaded")]
|
||||||
let iter = { paths.into_iter() };
|
let iter = { paths.into_iter() };
|
||||||
iter.map(|dir_entry| {
|
iter.map(|path| {
|
||||||
let result = format_dir_entry(
|
let result = format_dir_entry(
|
||||||
dir_entry,
|
path,
|
||||||
stability_check,
|
stability_check,
|
||||||
write,
|
write,
|
||||||
&black_options,
|
&black_options,
|
||||||
@@ -527,24 +526,20 @@ fn format_dev_project(
|
|||||||
|
|
||||||
/// Error handling in between walkdir and `format_dev_file`
|
/// Error handling in between walkdir and `format_dev_file`
|
||||||
fn format_dir_entry(
|
fn format_dir_entry(
|
||||||
dir_entry: Result<DirEntry, ignore::Error>,
|
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||||
stability_check: bool,
|
stability_check: bool,
|
||||||
write: bool,
|
write: bool,
|
||||||
options: &BlackOptions,
|
options: &BlackOptions,
|
||||||
resolver: &Resolver,
|
resolver: &Resolver,
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||||
let dir_entry = match dir_entry.context("Iterating the files in the repository failed") {
|
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||||
Ok(dir_entry) => dir_entry,
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
let file = dir_entry.path().to_path_buf();
|
|
||||||
// For some reason it does not filter in the beginning
|
// For some reason it does not filter in the beginning
|
||||||
if dir_entry.file_name() == "pyproject.toml" {
|
if resolved_file.file_name() == "pyproject.toml" {
|
||||||
return Ok((Ok(Statistics::default()), file));
|
return Ok((Ok(Statistics::default()), resolved_file.into_path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = dir_entry.path().to_path_buf();
|
let path = resolved_file.into_path();
|
||||||
let mut options = options.to_py_format_options(&path);
|
let mut options = options.to_py_format_options(&path);
|
||||||
|
|
||||||
let settings = resolver.resolve(&path, pyproject_config);
|
let settings = resolver.resolve(&path, pyproject_config);
|
||||||
@@ -876,9 +871,7 @@ struct BlackOptions {
|
|||||||
line_length: NonZeroU16,
|
line_length: NonZeroU16,
|
||||||
#[serde(alias = "skip-magic-trailing-comma")]
|
#[serde(alias = "skip-magic-trailing-comma")]
|
||||||
skip_magic_trailing_comma: bool,
|
skip_magic_trailing_comma: bool,
|
||||||
#[allow(unused)]
|
preview: bool,
|
||||||
#[serde(alias = "force-exclude")]
|
|
||||||
force_exclude: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlackOptions {
|
impl Default for BlackOptions {
|
||||||
@@ -886,7 +879,7 @@ impl Default for BlackOptions {
|
|||||||
Self {
|
Self {
|
||||||
line_length: NonZeroU16::new(88).unwrap(),
|
line_length: NonZeroU16::new(88).unwrap(),
|
||||||
skip_magic_trailing_comma: false,
|
skip_magic_trailing_comma: false,
|
||||||
force_exclude: None,
|
preview: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -934,6 +927,11 @@ impl BlackOptions {
|
|||||||
} else {
|
} else {
|
||||||
MagicTrailingComma::Respect
|
MagicTrailingComma::Respect
|
||||||
})
|
})
|
||||||
|
.with_preview(if self.preview {
|
||||||
|
PreviewMode::Enabled
|
||||||
|
} else {
|
||||||
|
PreviewMode::Disabled
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ use crate::ROOT_DIR;
|
|||||||
const COMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated command help. -->\n";
|
const COMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated command help. -->\n";
|
||||||
const COMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated command help. -->";
|
const COMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated command help. -->";
|
||||||
|
|
||||||
const SUBCOMMAND_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated subcommand help. -->\n";
|
const CHECK_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated check help. -->\n";
|
||||||
const SUBCOMMAND_HELP_END_PRAGMA: &str = "<!-- End auto-generated subcommand help. -->";
|
const CHECK_HELP_END_PRAGMA: &str = "<!-- End auto-generated check help. -->";
|
||||||
|
|
||||||
|
const FORMAT_HELP_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated format help. -->\n";
|
||||||
|
const FORMAT_HELP_END_PRAGMA: &str = "<!-- End auto-generated format help. -->";
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
pub(crate) struct Args {
|
pub(crate) struct Args {
|
||||||
@@ -56,11 +59,15 @@ pub(super) fn main(args: &Args) -> Result<()> {
|
|||||||
let command_help = trim_lines(&help_text());
|
let command_help = trim_lines(&help_text());
|
||||||
|
|
||||||
// Generate `ruff help check`.
|
// Generate `ruff help check`.
|
||||||
let subcommand_help = trim_lines(&check_help_text());
|
let check_help = trim_lines(&subcommand_help_text("check")?);
|
||||||
|
|
||||||
|
// Generate `ruff help format`.
|
||||||
|
let format_help = trim_lines(&subcommand_help_text("format")?);
|
||||||
|
|
||||||
if args.mode.is_dry_run() {
|
if args.mode.is_dry_run() {
|
||||||
print!("{command_help}");
|
print!("{command_help}");
|
||||||
print!("{subcommand_help}");
|
print!("{check_help}");
|
||||||
|
print!("{format_help}");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +84,15 @@ pub(super) fn main(args: &Args) -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
let new = replace_docs_section(
|
let new = replace_docs_section(
|
||||||
&new,
|
&new,
|
||||||
&format!("```text\n{subcommand_help}\n```\n\n"),
|
&format!("```text\n{check_help}\n```\n\n"),
|
||||||
SUBCOMMAND_HELP_BEGIN_PRAGMA,
|
CHECK_HELP_BEGIN_PRAGMA,
|
||||||
SUBCOMMAND_HELP_END_PRAGMA,
|
CHECK_HELP_END_PRAGMA,
|
||||||
|
)?;
|
||||||
|
let new = replace_docs_section(
|
||||||
|
&new,
|
||||||
|
&format!("```text\n{format_help}\n```\n\n"),
|
||||||
|
FORMAT_HELP_BEGIN_PRAGMA,
|
||||||
|
FORMAT_HELP_END_PRAGMA,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match args.mode {
|
match args.mode {
|
||||||
@@ -104,18 +117,19 @@ fn help_text() -> String {
|
|||||||
args::Args::command().render_help().to_string()
|
args::Args::command().render_help().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the output of `ruff help check`.
|
/// Returns the output of a given subcommand (e.g., `ruff help check`).
|
||||||
fn check_help_text() -> String {
|
fn subcommand_help_text(subcommand: &str) -> Result<String> {
|
||||||
let mut cmd = args::Args::command();
|
let mut cmd = args::Args::command();
|
||||||
|
|
||||||
// The build call is necessary for the help output to contain `Usage: ruff
|
// The build call is necessary for the help output to contain `Usage: ruff
|
||||||
// check` instead of `Usage: check` see https://github.com/clap-rs/clap/issues/4685
|
// check` instead of `Usage: check` see https://github.com/clap-rs/clap/issues/4685
|
||||||
cmd.build();
|
cmd.build();
|
||||||
|
|
||||||
cmd.find_subcommand_mut("check")
|
Ok(cmd
|
||||||
.expect("`check` subcommand not found")
|
.find_subcommand_mut(subcommand)
|
||||||
|
.with_context(|| format!("Unable to find subcommand `{subcommand}`"))?
|
||||||
.render_help()
|
.render_help()
|
||||||
.to_string()
|
.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use strum::IntoEnumIterator;
|
|||||||
use ruff_diagnostics::FixAvailability;
|
use ruff_diagnostics::FixAvailability;
|
||||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
use ruff_workspace::options_base::OptionsMetadata;
|
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
|
||||||
|
|
||||||
use crate::ROOT_DIR;
|
use crate::ROOT_DIR;
|
||||||
|
|
||||||
@@ -55,7 +55,11 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
process_documentation(explanation.trim(), &mut output);
|
process_documentation(
|
||||||
|
explanation.trim(),
|
||||||
|
&mut output,
|
||||||
|
&rule.noqa_code().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
let filename = PathBuf::from(ROOT_DIR)
|
let filename = PathBuf::from(ROOT_DIR)
|
||||||
.join("docs")
|
.join("docs")
|
||||||
@@ -74,7 +78,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_documentation(documentation: &str, out: &mut String) {
|
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) {
|
||||||
let mut in_options = false;
|
let mut in_options = false;
|
||||||
let mut after = String::new();
|
let mut after = String::new();
|
||||||
|
|
||||||
@@ -100,7 +104,17 @@ fn process_documentation(documentation: &str, out: &mut String) {
|
|||||||
if let Some(rest) = line.strip_prefix("- `") {
|
if let Some(rest) = line.strip_prefix("- `") {
|
||||||
let option = rest.trim_end().trim_end_matches('`');
|
let option = rest.trim_end().trim_end_matches('`');
|
||||||
|
|
||||||
assert!(Options::metadata().has(option), "unknown option {option}");
|
match Options::metadata().find(option) {
|
||||||
|
Some(OptionEntry::Field(field)) => {
|
||||||
|
if field.deprecated.is_some() {
|
||||||
|
eprintln!("Rule {rule_name} references deprecated option {option}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
None => {
|
||||||
|
panic!("Unknown option {option} referenced by rule {rule_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let anchor = option.replace('.', "-");
|
let anchor = option.replace('.', "-");
|
||||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||||
@@ -138,6 +152,7 @@ Something [`else`][other].
|
|||||||
|
|
||||||
[other]: http://example.com.",
|
[other]: http://example.com.",
|
||||||
&mut output,
|
&mut output,
|
||||||
|
"example",
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use ruff_python_trivia::textwrap;
|
||||||
use ruff_workspace::options::Options;
|
use ruff_workspace::options::Options;
|
||||||
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||||
|
|
||||||
@@ -101,24 +102,81 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
|||||||
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
|
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
|
||||||
}
|
}
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
|
|
||||||
|
if let Some(deprecated) = &field.deprecated {
|
||||||
|
output.push_str("!!! warning \"Deprecated\"\n");
|
||||||
|
output.push_str(" This option has been deprecated");
|
||||||
|
|
||||||
|
if let Some(since) = deprecated.since {
|
||||||
|
write!(output, " in {since}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push('.');
|
||||||
|
|
||||||
|
if let Some(message) = deprecated.message {
|
||||||
|
writeln!(output, " {message}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
output.push_str(field.doc);
|
output.push_str(field.doc);
|
||||||
output.push_str("\n\n");
|
output.push_str("\n\n");
|
||||||
output.push_str(&format!("**Default value**: `{}`\n", field.default));
|
output.push_str(&format!("**Default value**: `{}`\n", field.default));
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output.push_str(&format!(
|
output.push_str("**Example usage**:\n\n");
|
||||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
output.push_str(&format_tab(
|
||||||
if let Some(set_name) = parent_set.name() {
|
"pyproject.toml",
|
||||||
format!(".{set_name}")
|
&format_header(parent_set, ConfigurationFile::PyprojectToml),
|
||||||
} else {
|
field.example,
|
||||||
String::new()
|
));
|
||||||
},
|
output.push_str(&format_tab(
|
||||||
field.example
|
"ruff.toml",
|
||||||
|
&format_header(parent_set, ConfigurationFile::RuffToml),
|
||||||
|
field.example,
|
||||||
));
|
));
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
|
||||||
|
tab_name,
|
||||||
|
header,
|
||||||
|
textwrap::indent(content, " ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
|
||||||
|
let fmt = if let Some(set_name) = parent_set.name() {
|
||||||
|
if set_name == "format" {
|
||||||
|
String::from(".format")
|
||||||
|
} else {
|
||||||
|
format!(".lint.{set_name}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
match configuration {
|
||||||
|
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
|
||||||
|
ConfigurationFile::RuffToml => {
|
||||||
|
if fmt.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("[{}]", fmt.strip_prefix('.').unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum ConfigurationFile {
|
||||||
|
PyprojectToml,
|
||||||
|
RuffToml,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CollectOptionsVisitor {
|
struct CollectOptionsVisitor {
|
||||||
groups: Vec<(String, OptionSet)>,
|
groups: Vec<(String, OptionSet)>,
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
|||||||
for rule in rules {
|
for rule in rules {
|
||||||
let fix_token = match rule.fixable() {
|
let fix_token = match rule.fixable() {
|
||||||
FixAvailability::Always | FixAvailability::Sometimes => {
|
FixAvailability::Always | FixAvailability::Sometimes => {
|
||||||
format!("<span style='opacity: 1'>{FIX_SYMBOL}</span>")
|
format!("<span title='Automatic fix available'>{FIX_SYMBOL}</span>")
|
||||||
|
}
|
||||||
|
FixAvailability::None => {
|
||||||
|
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
|
||||||
}
|
}
|
||||||
FixAvailability::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
|
|
||||||
};
|
};
|
||||||
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
||||||
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
|
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||||
} else {
|
} else {
|
||||||
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")
|
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
|
||||||
};
|
};
|
||||||
let status_token = format!("{fix_token} {preview_token}");
|
let status_token = format!("{fix_token} {preview_token}");
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ pub(crate) fn generate() -> String {
|
|||||||
table_out.push('\n');
|
table_out.push('\n');
|
||||||
|
|
||||||
table_out.push_str(&format!(
|
table_out.push_str(&format!(
|
||||||
"The {PREVIEW_SYMBOL} emoji indicates that a rule in [\"preview\"](faq.md#what-is-preview)."
|
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
|
||||||
));
|
));
|
||||||
table_out.push('\n');
|
table_out.push('\n');
|
||||||
table_out.push('\n');
|
table_out.push('\n');
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ ruff_text_size = { path = "../ruff_text_size" }
|
|||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
is-macro = { workspace = true }
|
||||||
serde = { workspace = true, optional = true, features = [] }
|
serde = { workspace = true, optional = true, features = [] }
|
||||||
|
|||||||
@@ -5,27 +5,22 @@ use ruff_text_size::{Ranged, TextSize};
|
|||||||
|
|
||||||
use crate::edit::Edit;
|
use crate::edit::Edit;
|
||||||
|
|
||||||
/// Indicates confidence in the correctness of a suggested fix.
|
/// Indicates if a fix can be applied.
|
||||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||||
pub enum Applicability {
|
pub enum Applicability {
|
||||||
/// The fix is definitely what the user intended, or maintains the exact meaning of the code.
|
/// The fix is unsafe and should only be displayed for manual application by the user.
|
||||||
/// This fix should be automatically applied.
|
/// The fix is likely to be incorrect or the resulting code may have invalid syntax.
|
||||||
Automatic,
|
DisplayOnly,
|
||||||
|
|
||||||
/// The fix may be what the user intended, but it is uncertain.
|
/// The fix is unsafe and should only be applied with user opt-in.
|
||||||
/// The fix should result in valid code if it is applied.
|
/// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax.
|
||||||
/// The fix can be applied with user opt-in.
|
Unsafe,
|
||||||
Suggested,
|
|
||||||
|
|
||||||
/// The fix has a good chance of being incorrect or the code be incomplete.
|
/// The fix is safe and can always be applied.
|
||||||
/// The fix may result in invalid code if it is applied.
|
/// The fix is definitely what the user intended, or it maintains the exact meaning of the code.
|
||||||
/// The fix should only be manually applied by the user.
|
Safe,
|
||||||
Manual,
|
|
||||||
|
|
||||||
/// The applicability of the fix is unknown.
|
|
||||||
#[default]
|
|
||||||
Unspecified,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the level of isolation required to apply a fix.
|
/// Indicates the level of isolation required to apply a fix.
|
||||||
@@ -52,86 +47,86 @@ pub struct Fix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Fix {
|
impl Fix {
|
||||||
/// Create a new [`Fix`] with an unspecified applicability from an [`Edit`] element.
|
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from an [`Edit`] element.
|
||||||
#[deprecated(
|
pub fn safe_edit(edit: Edit) -> Self {
|
||||||
note = "Use `Fix::automatic`, `Fix::suggested`, or `Fix::manual` instead to specify an applicability."
|
|
||||||
)]
|
|
||||||
pub fn unspecified(edit: Edit) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
edits: vec![edit],
|
edits: vec![edit],
|
||||||
applicability: Applicability::Unspecified,
|
applicability: Applicability::Safe,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Fix`] with an unspecified applicability from multiple [`Edit`] elements.
|
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from multiple [`Edit`] elements.
|
||||||
#[deprecated(
|
pub fn safe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||||
note = "Use `Fix::automatic_edits`, `Fix::suggested_edits`, or `Fix::manual_edits` instead to specify an applicability."
|
|
||||||
)]
|
|
||||||
pub fn unspecified_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
|
||||||
Self {
|
|
||||||
edits: std::iter::once(edit).chain(rest).collect(),
|
|
||||||
applicability: Applicability::Unspecified,
|
|
||||||
isolation_level: IsolationLevel::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from an [`Edit`] element.
|
|
||||||
pub fn automatic(edit: Edit) -> Self {
|
|
||||||
Self {
|
|
||||||
edits: vec![edit],
|
|
||||||
applicability: Applicability::Automatic,
|
|
||||||
isolation_level: IsolationLevel::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from multiple [`Edit`] elements.
|
|
||||||
pub fn automatic_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
|
||||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||||
Self {
|
Self {
|
||||||
edits,
|
edits,
|
||||||
applicability: Applicability::Automatic,
|
applicability: Applicability::Safe,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from an [`Edit`] element.
|
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from an [`Edit`] element.
|
||||||
pub fn suggested(edit: Edit) -> Self {
|
pub fn unsafe_edit(edit: Edit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edits: vec![edit],
|
edits: vec![edit],
|
||||||
applicability: Applicability::Suggested,
|
applicability: Applicability::Unsafe,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from multiple [`Edit`] elements.
|
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from multiple [`Edit`] elements.
|
||||||
pub fn suggested_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
pub fn unsafe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||||
Self {
|
Self {
|
||||||
edits,
|
edits,
|
||||||
applicability: Applicability::Suggested,
|
applicability: Applicability::Unsafe,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from an [`Edit`] element.
|
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from an [`Edit`] element .
|
||||||
pub fn manual(edit: Edit) -> Self {
|
pub fn display_only_edit(edit: Edit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edits: vec![edit],
|
edits: vec![edit],
|
||||||
applicability: Applicability::Manual,
|
applicability: Applicability::DisplayOnly,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from multiple [`Edit`] elements.
|
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from multiple [`Edit`] elements.
|
||||||
pub fn manual_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
pub fn display_only_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||||
Self {
|
Self {
|
||||||
edits,
|
edits,
|
||||||
applicability: Applicability::Manual,
|
applicability: Applicability::DisplayOnly,
|
||||||
|
isolation_level: IsolationLevel::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
|
||||||
|
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
|
||||||
|
Self {
|
||||||
|
edits: vec![edit],
|
||||||
|
applicability,
|
||||||
|
isolation_level: IsolationLevel::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
|
||||||
|
pub fn applicable_edits(
|
||||||
|
edit: Edit,
|
||||||
|
rest: impl IntoIterator<Item = Edit>,
|
||||||
|
applicability: Applicability,
|
||||||
|
) -> Self {
|
||||||
|
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||||
|
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||||
|
Self {
|
||||||
|
edits,
|
||||||
|
applicability,
|
||||||
isolation_level: IsolationLevel::default(),
|
isolation_level: IsolationLevel::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,4 +157,16 @@ impl Fix {
|
|||||||
self.isolation_level = isolation;
|
self.isolation_level = isolation;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return [`true`] if this [`Fix`] should be applied with at a given [`Applicability`].
|
||||||
|
pub fn applies(&self, applicability: Applicability) -> bool {
|
||||||
|
self.applicability >= applicability
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Fix`] with the given [`Applicability`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_applicability(mut self, applicability: Applicability) -> Self {
|
||||||
|
self.applicability = applicability;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ rustc-hash = { workspace = true }
|
|||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
tracing = { version = "0.1.37", default-features = false, features = ["std"] }
|
tracing = { workspace = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -2448,7 +2448,7 @@ where
|
|||||||
|
|
||||||
/// Adds a new entry to the join output.
|
/// Adds a new entry to the join output.
|
||||||
pub fn entry(&mut self, entry: &dyn Format<Context>) -> &mut Self {
|
pub fn entry(&mut self, entry: &dyn Format<Context>) -> &mut Self {
|
||||||
self.result = self.result.and_then(|_| {
|
self.result = self.result.and_then(|()| {
|
||||||
if let Some(with) = &self.with {
|
if let Some(with) = &self.with {
|
||||||
if self.has_elements {
|
if self.has_elements {
|
||||||
with.fmt(self.fmt)?;
|
with.fmt(self.fmt)?;
|
||||||
@@ -2519,7 +2519,7 @@ impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> {
|
|||||||
separator: &dyn Format<Context>,
|
separator: &dyn Format<Context>,
|
||||||
entry: &dyn Format<Context>,
|
entry: &dyn Format<Context>,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.result = self.result.and_then(|_| {
|
self.result = self.result.and_then(|()| {
|
||||||
if self.empty {
|
if self.empty {
|
||||||
self.empty = false;
|
self.empty = false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ impl std::fmt::Display for IndentStyle {
|
|||||||
///
|
///
|
||||||
/// Determines the visual width of a tab character (`\t`) and the number of
|
/// Determines the visual width of a tab character (`\t`) and the number of
|
||||||
/// spaces per indent when using [`IndentStyle::Space`].
|
/// spaces per indent when using [`IndentStyle::Space`].
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct IndentWidth(NonZeroU8);
|
pub struct IndentWidth(NonZeroU8);
|
||||||
@@ -575,6 +575,10 @@ where
|
|||||||
context: PhantomData,
|
context: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rule(&self) -> &R {
|
||||||
|
&self.rule
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, R, O, C> FormatRefWithRule<'_, T, R, C>
|
impl<T, R, O, C> FormatRefWithRule<'_, T, R, C>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl<'a> Printer<'a> {
|
|||||||
|
|
||||||
/// Prints the passed in element as well as all its content,
|
/// Prints the passed in element as well as all its content,
|
||||||
/// starting at the specified indentation level
|
/// starting at the specified indentation level
|
||||||
#[tracing::instrument(name = "Printer::print", skip_all)]
|
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
|
||||||
pub fn print_with_indent(
|
pub fn print_with_indent(
|
||||||
mut self,
|
mut self,
|
||||||
document: &'a Document,
|
document: &'a Document,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.0.292"
|
version = "0.1.5"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -29,7 +29,7 @@ ruff_python_parser = { path = "../ruff_python_parser" }
|
|||||||
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
||||||
ruff_text_size = { path = "../ruff_text_size" }
|
ruff_text_size = { path = "../ruff_text_size" }
|
||||||
|
|
||||||
aho-corasick = { version = "1.1.1" }
|
aho-corasick = { version = "1.1.2" }
|
||||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
@@ -53,13 +53,13 @@ path-absolutize = { workspace = true, features = [
|
|||||||
] }
|
] }
|
||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||||
pyproject-toml = { version = "0.7.0" }
|
pyproject-toml = { version = "0.8.0" }
|
||||||
quick-junit = { version = "0.3.2" }
|
quick-junit = { version = "0.3.2" }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
result-like = { version = "0.4.6" }
|
result-like = { version = "0.4.6" }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
semver = { version = "1.0.19" }
|
semver = { version = "1.0.20" }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
similar = { workspace = true }
|
similar = { workspace = true }
|
||||||
@@ -70,7 +70,7 @@ thiserror = { workspace = true }
|
|||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
typed-arena = { version = "2.0.2" }
|
typed-arena = { version = "2.0.2" }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
unicode_names2 = { workspace = true }
|
||||||
wsl = { version = "0.1.0" }
|
wsl = { version = "0.1.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@@ -79,7 +79,7 @@ pretty_assertions = "1.3.0"
|
|||||||
test-case = { workspace = true }
|
test-case = { workspace = true }
|
||||||
# Disable colored output in tests
|
# Disable colored output in tests
|
||||||
colored = { workspace = true, features = ["no-color"] }
|
colored = { workspace = true, features = ["no-color"] }
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.8.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
19
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S310.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S310.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import urllib.request
|
||||||
|
|
||||||
|
urllib.request.urlopen(url='http://www.google.com')
|
||||||
|
urllib.request.urlopen(url='http://www.google.com', **kwargs)
|
||||||
|
urllib.request.urlopen('http://www.google.com')
|
||||||
|
urllib.request.urlopen('file:///foo/bar/baz')
|
||||||
|
urllib.request.urlopen(url)
|
||||||
|
|
||||||
|
urllib.request.Request(url='http://www.google.com', **kwargs)
|
||||||
|
urllib.request.Request(url='http://www.google.com')
|
||||||
|
urllib.request.Request('http://www.google.com')
|
||||||
|
urllib.request.Request('file:///foo/bar/baz')
|
||||||
|
urllib.request.Request(url)
|
||||||
|
|
||||||
|
urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
||||||
|
urllib.request.URLopener().open(fullurl='http://www.google.com')
|
||||||
|
urllib.request.URLopener().open('http://www.google.com')
|
||||||
|
urllib.request.URLopener().open('file:///foo/bar/baz')
|
||||||
|
urllib.request.URLopener().open(url)
|
||||||
9
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S702.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S702.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from mako.template import Template
|
||||||
|
from mako import template
|
||||||
|
import mako
|
||||||
|
|
||||||
|
|
||||||
|
Template("hello")
|
||||||
|
|
||||||
|
mako.template.Template("hern")
|
||||||
|
template.Template("hern")
|
||||||
@@ -70,6 +70,8 @@ settings.set_enable_developer_extras(True)
|
|||||||
foo.is_(True)
|
foo.is_(True)
|
||||||
bar.is_not(False)
|
bar.is_not(False)
|
||||||
next(iter([]), False)
|
next(iter([]), False)
|
||||||
|
sa.func.coalesce(tbl.c.valid, False)
|
||||||
|
|
||||||
|
|
||||||
class Registry:
|
class Registry:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|||||||
@@ -32,3 +32,16 @@ sorted(sorted(x, key=lambda y: y))
|
|||||||
sorted(sorted(x, key=lambda y: y), key=lambda x: x)
|
sorted(sorted(x, key=lambda y: y), key=lambda x: x)
|
||||||
sorted(sorted(x), reverse=True)
|
sorted(sorted(x), reverse=True)
|
||||||
sorted(sorted(x, reverse=False), reverse=True)
|
sorted(sorted(x, reverse=False), reverse=True)
|
||||||
|
|
||||||
|
# Preserve trailing comments.
|
||||||
|
xxxxxxxxxxx_xxxxx_xxxxx = sorted(
|
||||||
|
list(x_xxxx_xxxxxxxxxxx_xxxxx.xxxx()),
|
||||||
|
# xxxxxxxxxxx xxxxx xxxx xxx xx Nxxx, xxx xxxxxx3 xxxxxxxxx xx
|
||||||
|
# xx xxxx xxxxxxx xxxx xxx xxxxxxxx Nxxx
|
||||||
|
key=lambda xxxxx: xxxxx or "",
|
||||||
|
)
|
||||||
|
|
||||||
|
xxxxxxxxxxx_xxxxx_xxxxx = sorted(
|
||||||
|
list(x_xxxx_xxxxxxxxxxx_xxxxx.xxxx()), # xxxxxxxxxxx xxxxx xxxx xxx xx Nxxx
|
||||||
|
key=lambda xxxxx: xxxxx or "",
|
||||||
|
)
|
||||||
|
|||||||
@@ -58,3 +58,33 @@ def f_fix_indentation_check(foo):
|
|||||||
# Report these, but don't fix them
|
# Report these, but don't fix them
|
||||||
if foo: raise RuntimeError("This is an example exception")
|
if foo: raise RuntimeError("This is an example exception")
|
||||||
if foo: x = 1; raise RuntimeError("This is an example exception")
|
if foo: x = 1; raise RuntimeError("This is an example exception")
|
||||||
|
|
||||||
|
|
||||||
|
def f_triple_quoted_string():
|
||||||
|
raise RuntimeError(f"""This is an {"example"} exception""")
|
||||||
|
|
||||||
|
|
||||||
|
def f_multi_line_string():
|
||||||
|
raise RuntimeError(
|
||||||
|
"first"
|
||||||
|
"second"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def f_multi_line_string2():
|
||||||
|
raise RuntimeError(
|
||||||
|
"This is an {example} exception".format(
|
||||||
|
example="example"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def f_multi_line_string2():
|
||||||
|
raise RuntimeError(
|
||||||
|
(
|
||||||
|
"This is an "
|
||||||
|
"{example} exception"
|
||||||
|
).format(
|
||||||
|
example="example"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ Foo.objects.create(**{"_id": some_id}) # PIE804
|
|||||||
|
|
||||||
Foo.objects.create(**{**bar}) # PIE804
|
Foo.objects.create(**{**bar}) # PIE804
|
||||||
|
|
||||||
|
foo(**{})
|
||||||
|
|
||||||
|
|
||||||
foo(**{**data, "foo": "buzz"})
|
foo(**{**data, "foo": "buzz"})
|
||||||
foo(**buzz)
|
foo(**buzz)
|
||||||
|
|||||||
@@ -28,4 +28,12 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
|||||||
|
|
||||||
def func():
|
def func():
|
||||||
# PYI055
|
# PYI055
|
||||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
|
||||||
|
y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
from typing import Union as U
|
||||||
|
|
||||||
|
# PYI055
|
||||||
|
x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
|||||||
|
|
||||||
def func():
|
def func():
|
||||||
# PYI055
|
# PYI055
|
||||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
|
||||||
|
item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||||
|
|||||||
@@ -79,3 +79,6 @@ from ZeroDivisionError
|
|||||||
raise IndexError() from ZeroDivisionError
|
raise IndexError() from ZeroDivisionError
|
||||||
|
|
||||||
raise IndexError();
|
raise IndexError();
|
||||||
|
|
||||||
|
# RSE102
|
||||||
|
raise Foo()
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ if isinstance(a, bool) or isinstance(b, str):
|
|||||||
if isinstance(a, int) or isinstance(a.b, float):
|
if isinstance(a, int) or isinstance(a.b, float):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# OK
|
||||||
|
if isinstance(a, int) or unrelated_condition or isinstance(a, float):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if x or isinstance(a, int) or isinstance(a, float):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if x or y or isinstance(a, int) or isinstance(a, float) or z:
|
||||||
|
pass
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
# OK
|
# OK
|
||||||
|
|||||||
@@ -126,3 +126,12 @@ def f():
|
|||||||
x = yield 3
|
x = yield 3
|
||||||
else:
|
else:
|
||||||
x = yield 5
|
x = yield 5
|
||||||
|
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
# OK
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
x = 3
|
||||||
|
else:
|
||||||
|
x = 5
|
||||||
|
|||||||
@@ -185,3 +185,24 @@ async def f():
|
|||||||
if check(x):
|
if check(x):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def f():
|
||||||
|
# SIM110
|
||||||
|
for x in await iterable:
|
||||||
|
if check(x):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# OK (can't turn this into any() because the yield would end up inside a genexp)
|
||||||
|
for x in iterable:
|
||||||
|
if (yield check(x)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# OK (same)
|
||||||
|
for x in iterable:
|
||||||
|
if (yield from check(x)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|||||||
@@ -38,3 +38,7 @@ env = os.environ['FOO']
|
|||||||
|
|
||||||
if env := os.environ['FOO']:
|
if env := os.environ['FOO']:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
os.environ['https_proxy']
|
||||||
|
os.environ.get['http_proxy']
|
||||||
|
os.getenv('no_proxy')
|
||||||
|
|||||||
@@ -42,3 +42,7 @@ with contextlib.ExitStack():
|
|||||||
with contextlib.ExitStack() as exit_stack:
|
with contextlib.ExitStack() as exit_stack:
|
||||||
exit_stack_ = exit_stack
|
exit_stack_ = exit_stack
|
||||||
f = exit_stack_.enter_context(open("filename"))
|
f = exit_stack_.enter_context(open("filename"))
|
||||||
|
|
||||||
|
# OK (quick one-liner to clear file contents)
|
||||||
|
open("filename", "w").close()
|
||||||
|
pathlib.Path("filename").open("w").close()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
obj = {}
|
||||||
|
|
||||||
key in obj.keys() # SIM118
|
key in obj.keys() # SIM118
|
||||||
|
|
||||||
key not in obj.keys() # SIM118
|
key not in obj.keys() # SIM118
|
||||||
|
|||||||
@@ -114,3 +114,16 @@ elif key in a_dict:
|
|||||||
vars[idx] = a_dict[key]
|
vars[idx] = a_dict[key]
|
||||||
else:
|
else:
|
||||||
vars[idx] = "default"
|
vars[idx] = "default"
|
||||||
|
|
||||||
|
###
|
||||||
|
# Positive cases (preview)
|
||||||
|
###
|
||||||
|
|
||||||
|
# SIM401
|
||||||
|
var = a_dict[key] if key in a_dict else "default3"
|
||||||
|
|
||||||
|
# SIM401
|
||||||
|
var = "default-1" if key not in a_dict else a_dict[key]
|
||||||
|
|
||||||
|
# OK (default contains effect)
|
||||||
|
var = a_dict[key] if key in a_dict else val1 + val2
|
||||||
|
|||||||
18
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py
vendored
Normal file
18
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
async def foo():
|
||||||
|
with trio.fail_after():
|
||||||
|
...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
|
with trio.fail_at():
|
||||||
|
await ...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
|
with trio.move_on_after():
|
||||||
|
...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
|
with trio.move_at():
|
||||||
|
await ...
|
||||||
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
async def func() -> None:
|
||||||
|
trio.run(foo) # OK, not async
|
||||||
|
|
||||||
|
# OK
|
||||||
|
await trio.aclose_forcefully(foo)
|
||||||
|
await trio.open_file(foo)
|
||||||
|
await trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
await trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
await trio.open_tcp_listeners(foo)
|
||||||
|
await trio.open_tcp_stream(foo, foo)
|
||||||
|
await trio.open_unix_socket(foo)
|
||||||
|
await trio.run_process(foo)
|
||||||
|
await trio.sleep(5)
|
||||||
|
await trio.sleep_until(5)
|
||||||
|
await trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
await trio.lowlevel.checkpoint()
|
||||||
|
await trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
await trio.lowlevel.open_process(foo)
|
||||||
|
await trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
await trio.lowlevel.wait_readable(foo)
|
||||||
|
await trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
await trio.lowlevel.wait_writable(foo)
|
||||||
|
|
||||||
|
# TRIO105
|
||||||
|
trio.aclose_forcefully(foo)
|
||||||
|
trio.open_file(foo)
|
||||||
|
trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
trio.open_tcp_listeners(foo)
|
||||||
|
trio.open_tcp_stream(foo, foo)
|
||||||
|
trio.open_unix_socket(foo)
|
||||||
|
trio.run_process(foo)
|
||||||
|
trio.serve_listeners(foo, foo)
|
||||||
|
trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
trio.serve_tcp(foo, foo)
|
||||||
|
trio.sleep(foo)
|
||||||
|
trio.sleep_forever()
|
||||||
|
trio.sleep_until(foo)
|
||||||
|
trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
trio.lowlevel.checkpoint()
|
||||||
|
trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
trio.lowlevel.open_process()
|
||||||
|
trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
trio.lowlevel.wait_readable(foo)
|
||||||
|
trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
trio.lowlevel.wait_writable(foo)
|
||||||
|
|
||||||
|
async with await trio.open_file(foo): # Ok
|
||||||
|
pass
|
||||||
|
|
||||||
|
async with trio.open_file(foo): # TRIO105
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def func() -> None:
|
||||||
|
# TRIO105 (without fix)
|
||||||
|
trio.open_file(foo)
|
||||||
13
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def func(timeout):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def func(timeout=10):
|
||||||
|
...
|
||||||
16
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await trio.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await trio.sleep_until(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
trio.sleep(10)
|
||||||
28
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py
vendored
Normal file
28
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import trio
|
||||||
|
from trio import sleep
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
await trio.sleep(0) # TRIO115
|
||||||
|
await trio.sleep(1) # OK
|
||||||
|
await trio.sleep(0, 1) # OK
|
||||||
|
await trio.sleep(...) # OK
|
||||||
|
await trio.sleep() # OK
|
||||||
|
|
||||||
|
trio.sleep(0) # TRIO115
|
||||||
|
foo = 0
|
||||||
|
trio.sleep(foo) # TRIO115
|
||||||
|
trio.sleep(1) # OK
|
||||||
|
time.sleep(0) # OK
|
||||||
|
|
||||||
|
sleep(0) # TRIO115
|
||||||
|
|
||||||
|
bar = "bar"
|
||||||
|
trio.sleep(bar)
|
||||||
|
|
||||||
|
|
||||||
|
trio.sleep(0) # TRIO115
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
trio.run(trio.sleep(0)) # TRIO115
|
||||||
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .foo import Record
|
||||||
|
|
||||||
|
type RecordOrThings = Record | int | str
|
||||||
|
type RecordCallback[R: Record] = Callable[[R], None]
|
||||||
|
|
||||||
|
|
||||||
|
def process_record[R: Record](record: R) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class RecordContainer[R: Record]:
|
||||||
|
def add_record(self, record: R) -> None:
|
||||||
|
...
|
||||||
0
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
|
||||||
|
class MyBaseClass:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(MyBaseClass):
|
||||||
|
foo: Sequence
|
||||||
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from module.direct import MyBaseClass
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(MyBaseClass):
|
||||||
|
foo: Sequence
|
||||||
7
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(MyBaseClass):
|
||||||
|
foo: Sequence
|
||||||
@@ -22,3 +22,10 @@ class C:
|
|||||||
|
|
||||||
class D(C):
|
class D(C):
|
||||||
x: UUID
|
x: UUID
|
||||||
|
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
class E(BaseModel[int]):
|
||||||
|
x: collections.Awaitable
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence # TCH003
|
||||||
|
|
||||||
|
|
||||||
|
class MyBaseClass:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(MyBaseClass):
|
||||||
|
foo: Sequence
|
||||||
@@ -33,6 +33,8 @@ with open(p) as fp:
|
|||||||
fp.read()
|
fp.read()
|
||||||
open(p).close()
|
open(p).close()
|
||||||
os.getcwdb(p)
|
os.getcwdb(p)
|
||||||
|
os.path.join(p, *q)
|
||||||
|
os.sep.join(p, *q)
|
||||||
|
|
||||||
# https://github.com/astral-sh/ruff/issues/7620
|
# https://github.com/astral-sh/ruff/issues/7620
|
||||||
def opener(path, flags):
|
def opener(path, flags):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
|
|
||||||
[tool.ruff.isort]
|
[tool.ruff.lint.isort]
|
||||||
lines-after-imports = 3
|
lines-after-imports = 3
|
||||||
lines-between-types = 2
|
lines-between-types = 2
|
||||||
known-local-folder = ["ruff"]
|
known-local-folder = ["ruff"]
|
||||||
|
|||||||
106
crates/ruff_linter/resources/test/fixtures/numpy/NPY201.py
vendored
Normal file
106
crates/ruff_linter/resources/test/fixtures/numpy/NPY201.py
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
def func():
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
np.add_docstring
|
||||||
|
|
||||||
|
np.add_newdoc
|
||||||
|
|
||||||
|
np.add_newdoc_ufunc
|
||||||
|
|
||||||
|
np.asfarray([1,2,3])
|
||||||
|
|
||||||
|
np.byte_bounds(np.array([1,2,3]))
|
||||||
|
|
||||||
|
np.cast
|
||||||
|
|
||||||
|
np.cfloat(12+34j)
|
||||||
|
|
||||||
|
np.clongfloat(12+34j)
|
||||||
|
|
||||||
|
np.compat
|
||||||
|
|
||||||
|
np.complex_(12+34j)
|
||||||
|
|
||||||
|
np.DataSource
|
||||||
|
|
||||||
|
np.deprecate
|
||||||
|
|
||||||
|
np.deprecate_with_doc
|
||||||
|
|
||||||
|
np.disp(10)
|
||||||
|
|
||||||
|
np.fastCopyAndTranspose
|
||||||
|
|
||||||
|
np.find_common_type
|
||||||
|
|
||||||
|
np.get_array_wrap
|
||||||
|
|
||||||
|
np.float_
|
||||||
|
|
||||||
|
np.geterrobj
|
||||||
|
|
||||||
|
np.Inf
|
||||||
|
|
||||||
|
np.Infinity
|
||||||
|
|
||||||
|
np.infty
|
||||||
|
|
||||||
|
np.issctype
|
||||||
|
|
||||||
|
np.issubclass_(np.int32, np.integer)
|
||||||
|
|
||||||
|
np.issubsctype
|
||||||
|
|
||||||
|
np.mat
|
||||||
|
|
||||||
|
np.maximum_sctype
|
||||||
|
|
||||||
|
np.NaN
|
||||||
|
|
||||||
|
np.nbytes[np.int64]
|
||||||
|
|
||||||
|
np.NINF
|
||||||
|
|
||||||
|
np.NZERO
|
||||||
|
|
||||||
|
np.longcomplex(12+34j)
|
||||||
|
|
||||||
|
np.longfloat(12+34j)
|
||||||
|
|
||||||
|
np.lookfor
|
||||||
|
|
||||||
|
np.obj2sctype(int)
|
||||||
|
|
||||||
|
np.PINF
|
||||||
|
|
||||||
|
np.PZERO
|
||||||
|
|
||||||
|
np.recfromcsv
|
||||||
|
|
||||||
|
np.recfromtxt
|
||||||
|
|
||||||
|
np.round_(12.34)
|
||||||
|
|
||||||
|
np.safe_eval
|
||||||
|
|
||||||
|
np.sctype2char
|
||||||
|
|
||||||
|
np.sctypes
|
||||||
|
|
||||||
|
np.seterrobj
|
||||||
|
|
||||||
|
np.set_numeric_ops
|
||||||
|
|
||||||
|
np.set_string_function
|
||||||
|
|
||||||
|
np.singlecomplex(12+1j)
|
||||||
|
|
||||||
|
np.string_("asdf")
|
||||||
|
|
||||||
|
np.source
|
||||||
|
|
||||||
|
np.tracemalloc_domain
|
||||||
|
|
||||||
|
np.unicode_("asf")
|
||||||
|
|
||||||
|
np.who()
|
||||||
@@ -12,3 +12,19 @@ dict['key'] = list[index]
|
|||||||
# This is not prohibited by PEP8, but avoid it.
|
# This is not prohibited by PEP8, but avoid it.
|
||||||
class Foo (Bar, Baz):
|
class Foo (Bar, Baz):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_name () -> Union[str, None]:
|
||||||
|
"""Fetch name from --person-name in sys.argv.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
name of the person if available, otherwise None
|
||||||
|
"""
|
||||||
|
test = len(5)
|
||||||
|
Logger.info(test)
|
||||||
|
# test commented code
|
||||||
|
# Logger.info("test code")
|
||||||
|
for i in range (0, len (sys.argv)) :
|
||||||
|
if sys.argv[i] == "--name" :
|
||||||
|
return sys.argv[i + 1]
|
||||||
|
return None
|
||||||
|
|||||||
@@ -40,5 +40,11 @@ f"{(a:=1)}"
|
|||||||
f"{(lambda x:x)}"
|
f"{(lambda x:x)}"
|
||||||
f"normal{f"{a:.3f}"}normal"
|
f"normal{f"{a:.3f}"}normal"
|
||||||
|
|
||||||
|
#: Okay
|
||||||
|
snapshot.file_uri[len(f's3://{self.s3_bucket_name}/'):]
|
||||||
|
|
||||||
|
#: E231
|
||||||
|
{len(f's3://{self.s3_bucket_name}/'):1}
|
||||||
|
|
||||||
#: Okay
|
#: Okay
|
||||||
a = (1,
|
a = (1,
|
||||||
|
|||||||
@@ -54,3 +54,8 @@ f"{a=}"
|
|||||||
f"{a:=1}"
|
f"{a:=1}"
|
||||||
f"{foo(a=1)}"
|
f"{foo(a=1)}"
|
||||||
f"normal {f"{a=}"} normal"
|
f"normal {f"{a=}"} normal"
|
||||||
|
|
||||||
|
# Okay as the `=` is used inside a f-string...
|
||||||
|
print(f"{foo = }")
|
||||||
|
# ...but then it creates false negatives for now
|
||||||
|
print(f"{foo(a = 1)}")
|
||||||
|
|||||||
@@ -69,3 +69,5 @@ while 1:
|
|||||||
#: E701:2:3
|
#: E701:2:3
|
||||||
a = \
|
a = \
|
||||||
5;
|
5;
|
||||||
|
#:
|
||||||
|
with x(y) as z: ...
|
||||||
|
|||||||
@@ -5,17 +5,20 @@ if type(res) == type(42):
|
|||||||
if type(res) != type(""):
|
if type(res) != type(""):
|
||||||
pass
|
pass
|
||||||
#: E721
|
#: E721
|
||||||
|
if type(res) == memoryview:
|
||||||
|
pass
|
||||||
|
#: Okay
|
||||||
import types
|
import types
|
||||||
|
|
||||||
if res == types.IntType:
|
if res == types.IntType:
|
||||||
pass
|
pass
|
||||||
#: E721
|
#: Okay
|
||||||
import types
|
import types
|
||||||
|
|
||||||
if type(res) is not types.ListType:
|
if type(res) is not types.ListType:
|
||||||
pass
|
pass
|
||||||
#: E721
|
#: E721
|
||||||
assert type(res) == type(False)
|
assert type(res) == type(False) or type(res) == type(None)
|
||||||
#: E721
|
#: E721
|
||||||
assert type(res) == type([])
|
assert type(res) == type([])
|
||||||
#: E721
|
#: E721
|
||||||
@@ -25,21 +28,18 @@ assert type(res) == type((0,))
|
|||||||
#: E721
|
#: E721
|
||||||
assert type(res) == type((0))
|
assert type(res) == type((0))
|
||||||
#: E721
|
#: E721
|
||||||
assert type(res) != type((1,))
|
assert type(res) != type((1, ))
|
||||||
#: E721
|
#: Okay
|
||||||
assert type(res) is type((1,))
|
assert type(res) is type((1, ))
|
||||||
#: E721
|
#: Okay
|
||||||
assert type(res) is not type((1,))
|
assert type(res) is not type((1, ))
|
||||||
#: E211 E721
|
#: E211 E721
|
||||||
assert type(res) == type(
|
assert type(res) == type ([2, ])
|
||||||
[
|
|
||||||
2,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
#: E201 E201 E202 E721
|
#: E201 E201 E202 E721
|
||||||
assert type(res) == type(())
|
assert type(res) == type( ( ) )
|
||||||
#: E201 E202 E721
|
#: E201 E202 E721
|
||||||
assert type(res) == type((0,))
|
assert type(res) == type( (0, ) )
|
||||||
|
#:
|
||||||
|
|
||||||
#: Okay
|
#: Okay
|
||||||
import types
|
import types
|
||||||
@@ -50,17 +50,55 @@ if isinstance(res, str):
|
|||||||
pass
|
pass
|
||||||
if isinstance(res, types.MethodType):
|
if isinstance(res, types.MethodType):
|
||||||
pass
|
pass
|
||||||
if type(a) != type(b) or type(a) == type(ccc):
|
if isinstance(res, memoryview):
|
||||||
pass
|
pass
|
||||||
|
#: Okay
|
||||||
assert type(res) == type(None)
|
if type(res) is type:
|
||||||
|
|
||||||
types = StrEnum
|
|
||||||
if x == types.X:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
#: E721
|
#: E721
|
||||||
assert type(res) is int
|
if type(res) == type:
|
||||||
|
pass
|
||||||
|
#: Okay
|
||||||
|
def func_histype(a, b, c):
|
||||||
|
pass
|
||||||
|
#: E722
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#: E722
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#: E722 E203 E271
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except :
|
||||||
|
pass
|
||||||
|
#: Okay
|
||||||
|
fake_code = """"
|
||||||
|
try:
|
||||||
|
do_something()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
#: Okay
|
||||||
|
from . import custom_types as types
|
||||||
|
|
||||||
|
red = types.ColorTypeRED
|
||||||
|
red is types.ColorType.RED
|
||||||
|
#: Okay
|
||||||
|
from . import compute_type
|
||||||
|
|
||||||
|
if compute_type(foo) == 5:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Foo:
|
class Foo:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user