Compare commits
325 Commits
v0.1.10
...
preview-bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3e160dcb7 | ||
|
|
003851b54c | ||
|
|
bb8d2034e2 | ||
|
|
f40e012b4e | ||
|
|
3e9d761b13 | ||
|
|
46db3f96ac | ||
|
|
6f9c128d77 | ||
|
|
6380c90031 | ||
|
|
d96a0dbe57 | ||
|
|
180920fdd9 | ||
|
|
1ccd8354c1 | ||
|
|
dd0ba16a79 | ||
|
|
609d0a9a65 | ||
|
|
8fba97f72f | ||
|
|
5bc0d9c324 | ||
|
|
cf77eeb913 | ||
|
|
3f4dd01e7a | ||
|
|
edfe8421ec | ||
|
|
ab2253db03 | ||
|
|
33ac2867b7 | ||
|
|
0304623878 | ||
|
|
e2785f3fb6 | ||
|
|
90f8e4baf4 | ||
|
|
8657a392ff | ||
|
|
4946a1876f | ||
|
|
6dc1b21917 | ||
|
|
2e1160e74c | ||
|
|
37ff436e4e | ||
|
|
341c2698a7 | ||
|
|
a50e2787df | ||
|
|
25868d0371 | ||
|
|
af2cba7c0a | ||
|
|
8ec56277e9 | ||
|
|
b21ba71ef4 | ||
|
|
d387d0ba82 | ||
|
|
6f0e4ad332 | ||
|
|
7ca515c0aa | ||
|
|
1ce07d65bd | ||
|
|
00ef01d035 | ||
|
|
52ebfc9718 | ||
|
|
12a91f4e90 | ||
|
|
b4f2882b72 | ||
|
|
49fe1b85f2 | ||
|
|
bd8123c0d8 | ||
|
|
49c5e715f9 | ||
|
|
fe7d965334 | ||
|
|
9027169125 | ||
|
|
688177ff6a | ||
|
|
eb2784c495 | ||
|
|
6fffde72e7 | ||
|
|
ad313b9089 | ||
|
|
f76a3e8502 | ||
|
|
ed07fa08bd | ||
|
|
45937426c7 | ||
|
|
533dcfb114 | ||
|
|
bc023f47a1 | ||
|
|
aa38307415 | ||
|
|
e9ddd4819a | ||
|
|
fdb5eefb33 | ||
|
|
daae28efc7 | ||
|
|
75553ab1c0 | ||
|
|
c34908f5ad | ||
|
|
a662c2447c | ||
|
|
df7fb95cbc | ||
|
|
83195a6030 | ||
|
|
d31d09d7cd | ||
|
|
0f436b71f3 | ||
|
|
cd5bcd815d | ||
|
|
0ccca4083a | ||
|
|
041ce1e166 | ||
|
|
36b752876e | ||
|
|
b3dc565473 | ||
|
|
e708c08b64 | ||
|
|
73902323d5 | ||
|
|
9781563ef6 | ||
|
|
84aea7f0c8 | ||
|
|
b47f85eb69 | ||
|
|
80fc02e7d5 | ||
|
|
55d0e1148c | ||
|
|
1de945e3eb | ||
|
|
e277ba20da | ||
|
|
2e836a4cbe | ||
|
|
57d6cdb8d3 | ||
|
|
602f8b8250 | ||
|
|
a6bc4b2e48 | ||
|
|
c5fa0ccffb | ||
|
|
dd77d29d0e | ||
|
|
ad0121660e | ||
|
|
5c99967c4d | ||
|
|
c53aae0b6f | ||
|
|
2352de2277 | ||
|
|
e0a6034cbb | ||
|
|
25d93053da | ||
|
|
ee5b07d4ca | ||
|
|
e50603caf6 | ||
|
|
c3ca34543f | ||
|
|
4f7fb566f0 | ||
|
|
50bfbcf568 | ||
|
|
ea1c089652 | ||
|
|
b947dde8ad | ||
|
|
d259cd0d32 | ||
|
|
af4db39205 | ||
|
|
467c091382 | ||
|
|
92d99a72d9 | ||
|
|
66d2c1e1c4 | ||
|
|
ded8c7629f | ||
|
|
1fadefa67b | ||
|
|
06ad687efd | ||
|
|
148b64ead3 | ||
|
|
99eddbd2a0 | ||
|
|
836d2eaa01 | ||
|
|
994514d686 | ||
|
|
a578414246 | ||
|
|
0d752e56cd | ||
|
|
46c0937bfa | ||
|
|
e5008ca714 | ||
|
|
85a7edcc70 | ||
|
|
7db3aea1c6 | ||
|
|
e0bc08a758 | ||
|
|
a0ef087e73 | ||
|
|
c86e14d1d4 | ||
|
|
e37b3b0742 | ||
|
|
a0f32dfa55 | ||
|
|
6aa643346f | ||
|
|
ae13d8fddf | ||
|
|
2d6fd0fc91 | ||
|
|
33fe988cfc | ||
|
|
0f674d1d90 | ||
|
|
7962bca40a | ||
|
|
b81fc5ed11 | ||
|
|
c2bf725086 | ||
|
|
c3b33e9c4d | ||
|
|
6996ff7b1e | ||
|
|
f18e7d40ac | ||
|
|
2cc8acb0b7 | ||
|
|
ad83944ded | ||
|
|
6e225cb57c | ||
|
|
7992583908 | ||
|
|
1a46c9c2a2 | ||
|
|
3f4ab87061 | ||
|
|
7ae7bf6e30 | ||
|
|
9e3ff01ce8 | ||
|
|
63a7de9018 | ||
|
|
b77021b9ed | ||
|
|
f0066e1b89 | ||
|
|
7642fb7f27 | ||
|
|
7a1fa0e5d8 | ||
|
|
ce14f4dea5 | ||
|
|
6bb126415d | ||
|
|
541aef4e6c | ||
|
|
79f0522eb7 | ||
|
|
0c8d140321 | ||
|
|
f38fb2432f | ||
|
|
f0e598ea84 | ||
|
|
b6a96452fc | ||
|
|
214563261d | ||
|
|
3c7fea769c | ||
|
|
dacda0f202 | ||
|
|
95e1444c90 | ||
|
|
a7755d7a8d | ||
|
|
11449acfd9 | ||
|
|
4ccbacd44b | ||
|
|
05a2f52206 | ||
|
|
a6f7100b55 | ||
|
|
50122d2308 | ||
|
|
ad2cfa3dba | ||
|
|
0045032905 | ||
|
|
bea8f2ee3a | ||
|
|
c8074b0e18 | ||
|
|
740d8538de | ||
|
|
341d7447a0 | ||
|
|
2e8aff647b | ||
|
|
e555cf0ae4 | ||
|
|
b9139a31d5 | ||
|
|
7329bf459c | ||
|
|
157d5bacfc | ||
|
|
d496c164d3 | ||
|
|
b61b0edeea | ||
|
|
79a0ddc112 | ||
|
|
91046e4c81 | ||
|
|
5fe0fdd0a8 | ||
|
|
ffd13e65ae | ||
|
|
dba2cb79cb | ||
|
|
45628a5883 | ||
|
|
fc3e2664f9 | ||
|
|
b0d6fd7343 | ||
|
|
87821252d7 | ||
|
|
57313d9d63 | ||
|
|
eab1a6862b | ||
|
|
395bf3dc98 | ||
|
|
47b8a897e7 | ||
|
|
e81976f754 | ||
|
|
f5061dbb8e | ||
|
|
a3d667ed04 | ||
|
|
76a069d0c9 | ||
|
|
b994fb3ce0 | ||
|
|
40a0231189 | ||
|
|
9c8a4d927e | ||
|
|
836e4406a5 | ||
|
|
e54ed28ba9 | ||
|
|
1e4b421a00 | ||
|
|
a1f3cda190 | ||
|
|
837984168a | ||
|
|
b64aa1e86d | ||
|
|
9e5f3f1b1b | ||
|
|
9dc59cbb81 | ||
|
|
a42600e9a2 | ||
|
|
49a445a23d | ||
|
|
866bea60a5 | ||
|
|
df617c3093 | ||
|
|
47ad7b4500 | ||
|
|
b3a6f0ce81 | ||
|
|
7be706641d | ||
|
|
848e473f69 | ||
|
|
368e2793b5 | ||
|
|
29c130f326 | ||
|
|
8118d29419 | ||
|
|
45d374d838 | ||
|
|
8788d57030 | ||
|
|
f426c0fdaf | ||
|
|
3aae16f1bd | ||
|
|
3064312818 | ||
|
|
f9191b07c5 | ||
|
|
21f2d0c90b | ||
|
|
2b605527bd | ||
|
|
7ef7e0ddb6 | ||
|
|
2bddde2627 | ||
|
|
9a2f3e2cef | ||
|
|
f9331c7683 | ||
|
|
da275b8572 | ||
|
|
b983ab1c6c | ||
|
|
a1e65a92bd | ||
|
|
64aac5d526 | ||
|
|
896cecddd3 | ||
|
|
0753968ef3 | ||
|
|
66804eca6d | ||
|
|
f8f38b7640 | ||
|
|
2e375860dd | ||
|
|
15740306ea | ||
|
|
6183b8e98b | ||
|
|
0c0d3db1b5 | ||
|
|
953d48b7f4 | ||
|
|
0003c730e0 | ||
|
|
b6ce4f5f3a | ||
|
|
e8d7a6dfce | ||
|
|
ef2798f758 | ||
|
|
957a1f35c4 | ||
|
|
009430e034 | ||
|
|
7504bf347b | ||
|
|
fee64b52ba | ||
|
|
3261d16e61 | ||
|
|
d16c4a2d25 | ||
|
|
1602df1643 | ||
|
|
395cdf04e5 | ||
|
|
3daf6e1b6d | ||
|
|
a31a314b2b | ||
|
|
f9dd7bb190 | ||
|
|
350dcb807a | ||
|
|
55f8f3b2cc | ||
|
|
eb4ed2471b | ||
|
|
f192c72596 | ||
|
|
25bafd2d66 | ||
|
|
7fc51d29c5 | ||
|
|
2d362e9366 | ||
|
|
501bc1c270 | ||
|
|
14d3fe6bfa | ||
|
|
4a3bb67b5f | ||
|
|
79f4abbb8d | ||
|
|
58fcd96ac1 | ||
|
|
ac02d3aedd | ||
|
|
6be73322da | ||
|
|
ad1ca72a35 | ||
|
|
381811b4a6 | ||
|
|
20af5a774f | ||
|
|
86b1ae9383 | ||
|
|
84ab21f073 | ||
|
|
985f1d10f6 | ||
|
|
f419af494f | ||
|
|
94968fedd5 | ||
|
|
ba71772d93 | ||
|
|
b1a5df8694 | ||
|
|
0c84782060 | ||
|
|
d9fc9702cf | ||
|
|
c58d1aa87d | ||
|
|
1d1824787b | ||
|
|
04afdf177b | ||
|
|
d5a439cbd3 | ||
|
|
63953431a6 | ||
|
|
f6841757eb | ||
|
|
1666c7a5cb | ||
|
|
e80b3db10d | ||
|
|
701697c37e | ||
|
|
c2c9997682 | ||
|
|
cee09765ef | ||
|
|
cde4a7d7bf | ||
|
|
62eca330a8 | ||
|
|
59078c5403 | ||
|
|
6bf6521197 | ||
|
|
c11f65381f | ||
|
|
6dfc1ccd6f | ||
|
|
60ba7a7c0d | ||
|
|
4b8b3a1ced | ||
|
|
967b2dcaf4 | ||
|
|
aaa00976ae | ||
|
|
328262bfac | ||
|
|
f0d43dafcf | ||
|
|
9a14f403c8 | ||
|
|
1293383cdc | ||
|
|
3b323a09cb | ||
|
|
5c93a524f1 | ||
|
|
e3ad163785 | ||
|
|
0e202718fd | ||
|
|
7b6baff734 | ||
|
|
1ffc738c84 | ||
|
|
dc5094d42a | ||
|
|
fd36754beb | ||
|
|
154d3b9f4b | ||
|
|
6c0734680e | ||
|
|
eac67a9464 | ||
|
|
fefc7e8199 | ||
|
|
973ae7e922 | ||
|
|
3fcc1402f6 | ||
|
|
08c60f513b | ||
|
|
38f4d9e335 | ||
|
|
f07d35051c |
8
.config/nextest.toml
Normal file
8
.config/nextest.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[profile.ci]
|
||||
# Print out output for failing tests as soon as they fail, and also at the end
|
||||
# of the run (for easy scrollability).
|
||||
failure-output = "immediate-final"
|
||||
# Do not cancel the test run on the first failure.
|
||||
fail-fast = false
|
||||
|
||||
status-level = "skip"
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -9,6 +9,10 @@ updates:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
ignore:
|
||||
# The latest versions of these are not compatible with our release workflow
|
||||
- dependency-name: "actions/upload-artifact"
|
||||
- dependency-name: "actions/download-artifact"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
|
||||
52
.github/workflows/ci.yaml
vendored
52
.github/workflows/ci.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v40
|
||||
- uses: tj-actions/changed-files@v42
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -76,6 +76,7 @@ jobs:
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -87,6 +88,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -100,21 +102,32 @@ jobs:
|
||||
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
name: "cargo test (linux)"
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
@@ -126,29 +139,32 @@ jobs:
|
||||
path: target/debug/ruff
|
||||
|
||||
cargo-test-windows:
|
||||
name: "cargo test (windows)"
|
||||
runs-on: windows-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
name: "cargo test (windows)"
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo insta"
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
tool: cargo-nextest
|
||||
- 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
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
|
||||
cargo-test-wasm:
|
||||
name: "cargo test (wasm)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
name: "cargo test (wasm)"
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -166,10 +182,11 @@ jobs:
|
||||
wasm-pack test --node
|
||||
|
||||
cargo-fuzz:
|
||||
name: "cargo fuzz"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
name: "cargo fuzz"
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -180,7 +197,7 @@ jobs:
|
||||
- name: "Install cargo-fuzz"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-fuzz@0.11
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- run: cargo fuzz build -s none
|
||||
|
||||
scripts:
|
||||
@@ -188,6 +205,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -213,6 +231,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && ${{
|
||||
needs.determine_changes.outputs.code == 'true'
|
||||
}}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
@@ -322,6 +341,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install nightly Rust toolchain"
|
||||
@@ -336,6 +356,7 @@ jobs:
|
||||
python-package:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
@@ -360,6 +381,7 @@ jobs:
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
@@ -371,7 +393,7 @@ jobs:
|
||||
- name: "Install pre-commit"
|
||||
run: pip install pre-commit
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
@@ -388,6 +410,7 @@ jobs:
|
||||
docs:
|
||||
name: "mkdocs"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
@@ -395,7 +418,7 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -425,6 +448,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -441,6 +465,7 @@ jobs:
|
||||
check-ruff-lsp:
|
||||
name: "test ruff-lsp"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
@@ -484,6 +509,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
4
.github/workflows/docs.yaml
vendored
4
.github/workflows/docs.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
uses: cloudflare/wrangler-action@v3.4.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
uses: cloudflare/wrangler-action@v3.4.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
4
.github/workflows/pr-comment.yaml
vendored
4
.github/workflows/pr-comment.yaml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
echo 'EOF' >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
uses: peter-evans/find-comment@v3
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
|
||||
@@ -4,7 +4,7 @@ exclude: |
|
||||
(?x)^(
|
||||
crates/ruff_linter/resources/.*|
|
||||
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
||||
crates/ruff_cli/resources/.*|
|
||||
crates/ruff/resources/.*|
|
||||
crates/ruff_python_formatter/resources/.*|
|
||||
crates/ruff_python_formatter/tests/snapshots/.*|
|
||||
crates/ruff_python_resolver/resources/.*|
|
||||
|
||||
379
CHANGELOG.md
379
CHANGELOG.md
@@ -1,5 +1,384 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.1
|
||||
|
||||
This release includes support for range formatting (i.e., the ability to format specific lines
|
||||
within a source file).
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`refurb`\] Implement `missing-f-string-syntax` (`RUF027`) ([#9728](https://github.com/astral-sh/ruff/pull/9728))
|
||||
- Format module-level docstrings ([#9725](https://github.com/astral-sh/ruff/pull/9725))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Add `--range` option to `ruff format` ([#9733](https://github.com/astral-sh/ruff/pull/9733))
|
||||
- Don't trim last empty line in docstrings ([#9813](https://github.com/astral-sh/ruff/pull/9813))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Skip empty lines when determining base indentation ([#9795](https://github.com/astral-sh/ruff/pull/9795))
|
||||
- Drop `__get__` and `__set__` from `unnecessary-dunder-call` ([#9791](https://github.com/astral-sh/ruff/pull/9791))
|
||||
- Respect generic `Protocol` in ellipsis removal ([#9841](https://github.com/astral-sh/ruff/pull/9841))
|
||||
- Revert "Use publicly available Apple Silicon runners (#9726)" ([#9834](https://github.com/astral-sh/ruff/pull/9834))
|
||||
|
||||
### Performance
|
||||
|
||||
- Skip LibCST parsing for standard dedent adjustments ([#9769](https://github.com/astral-sh/ruff/pull/9769))
|
||||
- Remove CST-based fixer for `C408` ([#9822](https://github.com/astral-sh/ruff/pull/9822))
|
||||
- Add our own ignored-names abstractions ([#9802](https://github.com/astral-sh/ruff/pull/9802))
|
||||
- Remove CST-based fixers for `C400`, `C401`, `C410`, and `C418` ([#9819](https://github.com/astral-sh/ruff/pull/9819))
|
||||
- Use `AhoCorasick` to speed up quote match ([#9773](https://github.com/astral-sh/ruff/pull/9773))
|
||||
- Remove CST-based fixers for `C405` and `C409` ([#9821](https://github.com/astral-sh/ruff/pull/9821))
|
||||
- Add fast-path for comment detection ([#9808](https://github.com/astral-sh/ruff/pull/9808))
|
||||
- Invert order of checks in `zero-sleep-call` ([#9766](https://github.com/astral-sh/ruff/pull/9766))
|
||||
- Short-circuit typing matches based on imports ([#9800](https://github.com/astral-sh/ruff/pull/9800))
|
||||
- Run dunder method rule on methods directly ([#9815](https://github.com/astral-sh/ruff/pull/9815))
|
||||
- Track top-level module imports in the semantic model ([#9775](https://github.com/astral-sh/ruff/pull/9775))
|
||||
- Slight speed-up for lowercase and uppercase identifier checks ([#9798](https://github.com/astral-sh/ruff/pull/9798))
|
||||
- Remove LibCST-based fixer for `C403` ([#9818](https://github.com/astral-sh/ruff/pull/9818))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update `max-pos-args` example to `max-positional-args` ([#9797](https://github.com/astral-sh/ruff/pull/9797))
|
||||
- Fixed example code in `weak_cryptographic_key.rs` ([#9774](https://github.com/astral-sh/ruff/pull/9774))
|
||||
- Fix references to deprecated `ANN` rules in changelog ([#9771](https://github.com/astral-sh/ruff/pull/9771))
|
||||
- Fix default for `max-positional-args` ([#9838](https://github.com/astral-sh/ruff/pull/9838))
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- The `NURSERY` selector cannot be used anymore
|
||||
- Legacy selection of nursery rules by exact codes is no longer allowed without preview enabled
|
||||
|
||||
See also, the "Remapped rules" section which may result in disabled rules.
|
||||
|
||||
### Deprecations
|
||||
|
||||
The following rules are now deprecated:
|
||||
|
||||
- [`missing-type-self`](https://docs.astral.sh/ruff/rules/missing-type-self/) (`ANN101`)
|
||||
- [`missing-type-cls`](https://docs.astral.sh/ruff/rules/missing-type-cls/) (`ANN102`)
|
||||
|
||||
The following command line options are now deprecated:
|
||||
|
||||
- `--show-source`; use `--output-format full` instead
|
||||
- `--no-show-source`; use `--output-format concise` instead
|
||||
- `--output-format text`; use `full` or `concise` instead
|
||||
|
||||
The following settings have moved and the previous name is deprecated:
|
||||
|
||||
- `ruff.allowed-confusables` → [`ruff.lint.allowed-confusables`](https://docs.astral.sh//ruff/settings/#lint_allowed-confusables)
|
||||
- `ruff.dummy-variable-rgx` → [`ruff.lint.dummy-variable-rgx`](https://docs.astral.sh//ruff/settings/#lint_dummy-variable-rgx)
|
||||
- `ruff.explicit-preview-rules` → [`ruff.lint.explicit-preview-rules`](https://docs.astral.sh//ruff/settings/#lint_explicit-preview-rules)
|
||||
- `ruff.extend-fixable` → [`ruff.lint.extend-fixable`](https://docs.astral.sh//ruff/settings/#lint_extend-fixable)
|
||||
- `ruff.extend-ignore` → [`ruff.lint.extend-ignore`](https://docs.astral.sh//ruff/settings/#lint_extend-ignore)
|
||||
- `ruff.extend-per-file-ignores` → [`ruff.lint.extend-per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_extend-per-file-ignores)
|
||||
- `ruff.extend-safe-fixes` → [`ruff.lint.extend-safe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-safe-fixes)
|
||||
- `ruff.extend-select` → [`ruff.lint.extend-select`](https://docs.astral.sh//ruff/settings/#lint_extend-select)
|
||||
- `ruff.extend-unfixable` → [`ruff.lint.extend-unfixable`](https://docs.astral.sh//ruff/settings/#lint_extend-unfixable)
|
||||
- `ruff.extend-unsafe-fixes` → [`ruff.lint.extend-unsafe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-unsafe-fixes)
|
||||
- `ruff.external` → [`ruff.lint.external`](https://docs.astral.sh//ruff/settings/#lint_external)
|
||||
- `ruff.fixable` → [`ruff.lint.fixable`](https://docs.astral.sh//ruff/settings/#lint_fixable)
|
||||
- `ruff.flake8-annotations` → [`ruff.lint.flake8-annotations`](https://docs.astral.sh//ruff/settings/#lint_flake8-annotations)
|
||||
- `ruff.flake8-bandit` → [`ruff.lint.flake8-bandit`](https://docs.astral.sh//ruff/settings/#lint_flake8-bandit)
|
||||
- `ruff.flake8-bugbear` → [`ruff.lint.flake8-bugbear`](https://docs.astral.sh//ruff/settings/#lint_flake8-bugbear)
|
||||
- `ruff.flake8-builtins` → [`ruff.lint.flake8-builtins`](https://docs.astral.sh//ruff/settings/#lint_flake8-builtins)
|
||||
- `ruff.flake8-comprehensions` → [`ruff.lint.flake8-comprehensions`](https://docs.astral.sh//ruff/settings/#lint_flake8-comprehensions)
|
||||
- `ruff.flake8-copyright` → [`ruff.lint.flake8-copyright`](https://docs.astral.sh//ruff/settings/#lint_flake8-copyright)
|
||||
- `ruff.flake8-errmsg` → [`ruff.lint.flake8-errmsg`](https://docs.astral.sh//ruff/settings/#lint_flake8-errmsg)
|
||||
- `ruff.flake8-gettext` → [`ruff.lint.flake8-gettext`](https://docs.astral.sh//ruff/settings/#lint_flake8-gettext)
|
||||
- `ruff.flake8-implicit-str-concat` → [`ruff.lint.flake8-implicit-str-concat`](https://docs.astral.sh//ruff/settings/#lint_flake8-implicit-str-concat)
|
||||
- `ruff.flake8-import-conventions` → [`ruff.lint.flake8-import-conventions`](https://docs.astral.sh//ruff/settings/#lint_flake8-import-conventions)
|
||||
- `ruff.flake8-pytest-style` → [`ruff.lint.flake8-pytest-style`](https://docs.astral.sh//ruff/settings/#lint_flake8-pytest-style)
|
||||
- `ruff.flake8-quotes` → [`ruff.lint.flake8-quotes`](https://docs.astral.sh//ruff/settings/#lint_flake8-quotes)
|
||||
- `ruff.flake8-self` → [`ruff.lint.flake8-self`](https://docs.astral.sh//ruff/settings/#lint_flake8-self)
|
||||
- `ruff.flake8-tidy-imports` → [`ruff.lint.flake8-tidy-imports`](https://docs.astral.sh//ruff/settings/#lint_flake8-tidy-imports)
|
||||
- `ruff.flake8-type-checking` → [`ruff.lint.flake8-type-checking`](https://docs.astral.sh//ruff/settings/#lint_flake8-type-checking)
|
||||
- `ruff.flake8-unused-arguments` → [`ruff.lint.flake8-unused-arguments`](https://docs.astral.sh//ruff/settings/#lint_flake8-unused-arguments)
|
||||
- `ruff.ignore` → [`ruff.lint.ignore`](https://docs.astral.sh//ruff/settings/#lint_ignore)
|
||||
- `ruff.ignore-init-module-imports` → [`ruff.lint.ignore-init-module-imports`](https://docs.astral.sh//ruff/settings/#lint_ignore-init-module-imports)
|
||||
- `ruff.isort` → [`ruff.lint.isort`](https://docs.astral.sh//ruff/settings/#lint_isort)
|
||||
- `ruff.logger-objects` → [`ruff.lint.logger-objects`](https://docs.astral.sh//ruff/settings/#lint_logger-objects)
|
||||
- `ruff.mccabe` → [`ruff.lint.mccabe`](https://docs.astral.sh//ruff/settings/#lint_mccabe)
|
||||
- `ruff.pep8-naming` → [`ruff.lint.pep8-naming`](https://docs.astral.sh//ruff/settings/#lint_pep8-naming)
|
||||
- `ruff.per-file-ignores` → [`ruff.lint.per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_per-file-ignores)
|
||||
- `ruff.pycodestyle` → [`ruff.lint.pycodestyle`](https://docs.astral.sh//ruff/settings/#lint_pycodestyle)
|
||||
- `ruff.pydocstyle` → [`ruff.lint.pydocstyle`](https://docs.astral.sh//ruff/settings/#lint_pydocstyle)
|
||||
- `ruff.pyflakes` → [`ruff.lint.pyflakes`](https://docs.astral.sh//ruff/settings/#lint_pyflakes)
|
||||
- `ruff.pylint` → [`ruff.lint.pylint`](https://docs.astral.sh//ruff/settings/#lint_pylint)
|
||||
- `ruff.pyupgrade` → [`ruff.lint.pyupgrade`](https://docs.astral.sh//ruff/settings/#lint_pyupgrade)
|
||||
- `ruff.select` → [`ruff.lint.select`](https://docs.astral.sh//ruff/settings/#lint_select)
|
||||
- `ruff.task-tags` → [`ruff.lint.task-tags`](https://docs.astral.sh//ruff/settings/#lint_task-tags)
|
||||
- `ruff.typing-modules` → [`ruff.lint.typing-modules`](https://docs.astral.sh//ruff/settings/#lint_typing-modules)
|
||||
- `ruff.unfixable` → [`ruff.lint.unfixable`](https://docs.astral.sh//ruff/settings/#lint_unfixable)
|
||||
|
||||
### Remapped rules
|
||||
|
||||
The following rules have been remapped to new codes:
|
||||
|
||||
- [`raise-without-from-inside-except`](https://docs.astral.sh/ruff/rules/raise-without-from-inside-except/): `TRY200` to `B904`
|
||||
- [`suspicious-eval-usage`](https://docs.astral.sh/ruff/rules/suspicious-eval-usage/): `PGH001` to `S307`
|
||||
- [`logging-warn`](https://docs.astral.sh/ruff/rules/logging-warn/): `PGH002` to `G010`
|
||||
- [`static-key-dict-comprehension`](https://docs.astral.sh/ruff/rules/static-key-dict-comprehension): `RUF011` to `B035`
|
||||
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union): `TCH006` to `TCH010`
|
||||
|
||||
### Stabilizations
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`trio-timeout-without-await`](https://docs.astral.sh/ruff/rules/trio-timeout-without-await) (`TRIO100`)
|
||||
- [`trio-sync-call`](https://docs.astral.sh/ruff/rules/trio-sync-call) (`TRIO105`)
|
||||
- [`trio-async-function-with-timeout`](https://docs.astral.sh/ruff/rules/trio-async-function-with-timeout) (`TRIO109`)
|
||||
- [`trio-unneeded-sleep`](https://docs.astral.sh/ruff/rules/trio-unneeded-sleep) (`TRIO110`)
|
||||
- [`trio-zero-sleep-call`](https://docs.astral.sh/ruff/rules/trio-zero-sleep-call) (`TRIO115`)
|
||||
- [`unnecessary-escaped-quote`](https://docs.astral.sh/ruff/rules/unnecessary-escaped-quote) (`Q004`)
|
||||
- [`enumerate-for-loop`](https://docs.astral.sh/ruff/rules/enumerate-for-loop) (`SIM113`)
|
||||
- [`zip-dict-keys-and-values`](https://docs.astral.sh/ruff/rules/zip-dict-keys-and-values) (`SIM911`)
|
||||
- [`timeout-error-alias`](https://docs.astral.sh/ruff/rules/timeout-error-alias) (`UP041`)
|
||||
- [`flask-debug-true`](https://docs.astral.sh/ruff/rules/flask-debug-true) (`S201`)
|
||||
- [`tarfile-unsafe-members`](https://docs.astral.sh/ruff/rules/tarfile-unsafe-members) (`S202`)
|
||||
- [`ssl-insecure-version`](https://docs.astral.sh/ruff/rules/ssl-insecure-version) (`S502`)
|
||||
- [`ssl-with-bad-defaults`](https://docs.astral.sh/ruff/rules/ssl-with-bad-defaults) (`S503`)
|
||||
- [`ssl-with-no-version`](https://docs.astral.sh/ruff/rules/ssl-with-no-version) (`S504`)
|
||||
- [`weak-cryptographic-key`](https://docs.astral.sh/ruff/rules/weak-cryptographic-key) (`S505`)
|
||||
- [`ssh-no-host-key-verification`](https://docs.astral.sh/ruff/rules/ssh-no-host-key-verification) (`S507`)
|
||||
- [`django-raw-sql`](https://docs.astral.sh/ruff/rules/django-raw-sql) (`S611`)
|
||||
- [`mako-templates`](https://docs.astral.sh/ruff/rules/mako-templates) (`S702`)
|
||||
- [`generator-return-from-iter-method`](https://docs.astral.sh/ruff/rules/generator-return-from-iter-method) (`PYI058`)
|
||||
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union) (`TCH006`)
|
||||
- [`numpy2-deprecation`](https://docs.astral.sh/ruff/rules/numpy2-deprecation) (`NPY201`)
|
||||
- [`quadratic-list-summation`](https://docs.astral.sh/ruff/rules/quadratic-list-summation) (`RUF017`)
|
||||
- [`assignment-in-assert`](https://docs.astral.sh/ruff/rules/assignment-in-assert) (`RUF018`)
|
||||
- [`unnecessary-key-check`](https://docs.astral.sh/ruff/rules/unnecessary-key-check) (`RUF019`)
|
||||
- [`never-union`](https://docs.astral.sh/ruff/rules/never-union) (`RUF020`)
|
||||
- [`direct-logger-instantiation`](https://docs.astral.sh/ruff/rules/direct-logger-instantiation) (`LOG001`)
|
||||
- [`invalid-get-logger-argument`](https://docs.astral.sh/ruff/rules/invalid-get-logger-argument) (`LOG002`)
|
||||
- [`exception-without-exc-info`](https://docs.astral.sh/ruff/rules/exception-without-exc-info) (`LOG007`)
|
||||
- [`undocumented-warn`](https://docs.astral.sh/ruff/rules/undocumented-warn) (`LOG009`)
|
||||
|
||||
Fixes for the following rules have been stabilized and are now available without preview:
|
||||
|
||||
- [`triple-single-quotes`](https://docs.astral.sh/ruff/rules/triple-single-quotes) (`D300`)
|
||||
- [`non-pep604-annotation`](https://docs.astral.sh/ruff/rules/non-pep604-annotation) (`UP007`)
|
||||
- [`dict-get-with-none-default`](https://docs.astral.sh/ruff/rules/dict-get-with-none-default) (`SIM910`)
|
||||
- [`in-dict-keys`](https://docs.astral.sh/ruff/rules/in-dict-keys) (`SIM118`)
|
||||
- [`collapsible-else-if`](https://docs.astral.sh/ruff/rules/collapsible-else-if) (`PLR5501`)
|
||||
- [`if-with-same-arms`](https://docs.astral.sh/ruff/rules/if-with-same-arms) (`SIM114`)
|
||||
- [`useless-else-on-loop`](https://docs.astral.sh/ruff/rules/useless-else-on-loop) (`PLW0120`)
|
||||
- [`unnecessary-literal-union`](https://docs.astral.sh/ruff/rules/unnecessary-literal-union) (`PYI030`)
|
||||
- [`unnecessary-spread`](https://docs.astral.sh/ruff/rules/unnecessary-spread) (`PIE800`)
|
||||
- [`error-instead-of-exception`](https://docs.astral.sh/ruff/rules/error-instead-of-exception) (`TRY400`)
|
||||
- [`redefined-while-unused`](https://docs.astral.sh/ruff/rules/redefined-while-unused) (`F811`)
|
||||
- [`duplicate-value`](https://docs.astral.sh/ruff/rules/duplicate-value) (`B033`)
|
||||
- [`multiple-imports-on-one-line`](https://docs.astral.sh/ruff/rules/multiple-imports-on-one-line) (`E401`)
|
||||
- [`non-pep585-annotation`](https://docs.astral.sh/ruff/rules/non-pep585-annotation) (`UP006`)
|
||||
|
||||
Fixes for the following rules have been promoted from unsafe to safe:
|
||||
|
||||
- [`unaliased-collections-abc-set-import`](https://docs.astral.sh/ruff/rules/unaliased-collections-abc-set-import) (`PYI025`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file/) (`E402`) allows `sys.path` modifications between imports
|
||||
- [`reimplemented-container-builtin`](https://docs.astral.sh/ruff/rules/reimplemented-container-builtin/) (`PIE807`) includes lambdas that can be replaced with `dict`
|
||||
- [`unnecessary-placeholder`](https://docs.astral.sh/ruff/rules/unnecessary-placeholder/) (`PIE790`) applies to unnecessary ellipses (`...`)
|
||||
- [`if-else-block-instead-of-dict-get`](https://docs.astral.sh/ruff/rules/if-else-block-instead-of-dict-get/) (`SIM401`) applies to `if-else` expressions
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`refurb`\] Implement `metaclass_abcmeta` (`FURB180`) ([#9658](https://github.com/astral-sh/ruff/pull/9658))
|
||||
- Implement `blank_line_after_nested_stub_class` preview style ([#9155](https://github.com/astral-sh/ruff/pull/9155))
|
||||
- The preview rule [`and-or-ternary`](https://docs.astral.sh/ruff/rules/and-or-ternary) (`PLR1706`) was removed
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-async`\] Take `pathlib.Path` into account when analyzing async functions ([#9703](https://github.com/astral-sh/ruff/pull/9703))
|
||||
- \[`flake8-return`\] - fix indentation syntax error (`RET505`) ([#9705](https://github.com/astral-sh/ruff/pull/9705))
|
||||
- Detect multi-statement lines in else removal ([#9748](https://github.com/astral-sh/ruff/pull/9748))
|
||||
- `RUF022`, `RUF023`: never add two trailing commas to the end of a sequence ([#9698](https://github.com/astral-sh/ruff/pull/9698))
|
||||
- `RUF023`: Don't sort `__match_args__`, only `__slots__` ([#9724](https://github.com/astral-sh/ruff/pull/9724))
|
||||
- \[`flake8-simplify`\] - Fix syntax error in autofix (`SIM114`) ([#9704](https://github.com/astral-sh/ruff/pull/9704))
|
||||
- \[`pylint`\] Show verbatim constant in `magic-value-comparison` (`PLR2004`) ([#9694](https://github.com/astral-sh/ruff/pull/9694))
|
||||
- Removing trailing whitespace inside multiline strings is unsafe ([#9744](https://github.com/astral-sh/ruff/pull/9744))
|
||||
- Support `IfExp` with dual string arms in `invalid-envvar-default` ([#9734](https://github.com/astral-sh/ruff/pull/9734))
|
||||
- \[`pylint`\] Add `__mro_entries__` to known dunder methods (`PLW3201`) ([#9706](https://github.com/astral-sh/ruff/pull/9706))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Removed rules are now retained in the documentation ([#9691](https://github.com/astral-sh/ruff/pull/9691))
|
||||
- Deprecated rules are now indicated in the documentation ([#9689](https://github.com/astral-sh/ruff/pull/9689))
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Preview features
|
||||
|
||||
- Error when `NURSERY` selector is used with `--preview` ([#9682](https://github.com/astral-sh/ruff/pull/9682))
|
||||
- Preserve indentation around multiline strings in formatter ([#9637](https://github.com/astral-sh/ruff/pull/9637))
|
||||
- \[`flake8-return`\] Add fixes for all rules (`RET505`, `RET506`, `RET507`, `RET508`) ([#9595](https://github.com/astral-sh/ruff/pull/9595))
|
||||
- \[`flake8-simplify`\] Add fix for `if-with-same-arms` (`SIM114`) ([#9591](https://github.com/astral-sh/ruff/pull/9591))
|
||||
- \[`pycodestyle`\] Add fix for `multiple-imports-on-one-line` (`E401`) ([#9518](https://github.com/astral-sh/ruff/pull/9518))
|
||||
- \[`pylint`\] Add fix for `collapsible-else-if` (`PLR5501`) ([#9594](https://github.com/astral-sh/ruff/pull/9594))
|
||||
- \[`pylint`\] Add fix for `useless-else-on-loop` (`PLW0120`) ([#9590](https://github.com/astral-sh/ruff/pull/9590))
|
||||
- \[`pylint`\] Implement `assigning-non-slot` (`E0237`) ([#9623](https://github.com/astral-sh/ruff/pull/9623))
|
||||
- \[`pylint`\] Implement `potential-index-error` (`PLE0643`) ([#9545](https://github.com/astral-sh/ruff/pull/9545))
|
||||
- \[`pylint`\] Implement `too-many-nested-blocks` (`PLR1702`) ([#9172](https://github.com/astral-sh/ruff/pull/9172))
|
||||
- \[`ruff`\] Add rule to sort `__slots__` and `__match_args__` ([#9564](https://github.com/astral-sh/ruff/pull/9564))
|
||||
- \[`ruff`\] Detect unnecessary `dict` comprehensions for iterables (`RUF025`) ([#9613](https://github.com/astral-sh/ruff/pull/9613))
|
||||
- \[`ruff`\] Guard against use of `default_factory` as a keyword argument (`RUF026`) ([#9651](https://github.com/astral-sh/ruff/pull/9651))
|
||||
- \[`ruff`\] Implement `mutable-fromkeys-value` (`RUF024`) ([#9597](https://github.com/astral-sh/ruff/pull/9597))
|
||||
|
||||
### CLI
|
||||
|
||||
- Enable auto-wrapping of `--help` output ([#9633](https://github.com/astral-sh/ruff/pull/9633))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid rendering display-only rules as fixable ([#9649](https://github.com/astral-sh/ruff/pull/9649))
|
||||
- Detect automagic-like assignments in notebooks ([#9653](https://github.com/astral-sh/ruff/pull/9653))
|
||||
- Generate custom JSON schema for dynamic setting ([#9632](https://github.com/astral-sh/ruff/pull/9632))
|
||||
- \[`flake8-no-pep420`\] Include global `--config` when determining namespace packages ([#9603](https://github.com/astral-sh/ruff/pull/9603))
|
||||
- \[`flake8-pie`\] Omit bound tuples passed to `.startswith` or `.endswith` ([#9661](https://github.com/astral-sh/ruff/pull/9661))
|
||||
- \[`flake8-return`\] Avoid panic when fixing inlined else blocks ([#9657](https://github.com/astral-sh/ruff/pull/9657))
|
||||
- \[`flake8-return`\] Consider exception suppression in unnecessary assignment ([#9673](https://github.com/astral-sh/ruff/pull/9673))
|
||||
- \[`flake8-return`\] Take `NoReturn` annotation into account when analyzing implicit returns ([#9636](https://github.com/astral-sh/ruff/pull/9636))
|
||||
- \[`flake8-simplify`\] Support inverted returns in `needless-bool` (`SIM103`) ([#9619](https://github.com/astral-sh/ruff/pull/9619))
|
||||
- \[`flake8-type-checking`\] Add Pydantic's `BaseConfig` to default-copy list ([#9650](https://github.com/astral-sh/ruff/pull/9650))
|
||||
- \[`flake8-type-checking`\] Avoid marking `InitVar` as a typing-only annotation ([#9688](https://github.com/astral-sh/ruff/pull/9688))
|
||||
- \[`pycodestyle`\] Allow `dtype` comparisons in `type-comparison` ([#9676](https://github.com/astral-sh/ruff/pull/9676))
|
||||
- \[`pydocstyle`\] Re-implement `last-line-after-section` (`D413`) ([#9654](https://github.com/astral-sh/ruff/pull/9654))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-pytest-style`\] Add fix safety documentation for `duplicate-parameterize-test-cases` ([#9678](https://github.com/astral-sh/ruff/pull/9678))
|
||||
- \[`pylint`\] Document `literal-membership` fix safety conditions ([#9677](https://github.com/astral-sh/ruff/pull/9677))
|
||||
- \[`isort`\] Fix reference to `isort` rule code ([#9598](https://github.com/astral-sh/ruff/pull/9598))
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Add fix for `duplicate-value` (`B033`) ([#9510](https://github.com/astral-sh/ruff/pull/9510))
|
||||
- \[`flake8-simplify`\] Implement `enumerate-for-loop` (`SIM113`) ([#7777](https://github.com/astral-sh/ruff/pull/7777))
|
||||
- \[`pygrep_hooks`\] Add fix for `deprecated-log-warn` (`PGH002`) ([#9519](https://github.com/astral-sh/ruff/pull/9519))
|
||||
- \[`pylint`\] Implement `import-private-name` (`C2701`) ([#5920](https://github.com/astral-sh/ruff/pull/5920))
|
||||
- \[`refurb`\] Implement `regex-flag-alias` with fix (`FURB167`) ([#9516](https://github.com/astral-sh/ruff/pull/9516))
|
||||
- \[`ruff`\] Add rule and fix to sort contents of `__all__` (`RUF022`) ([#9474](https://github.com/astral-sh/ruff/pull/9474))
|
||||
- \[`tryceratops`\] Add fix for `error-instead-of-exception` (`TRY400`) ([#9520](https://github.com/astral-sh/ruff/pull/9520))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-pyi`\] Fix `PYI047` false negatives on PEP-695 type aliases ([#9566](https://github.com/astral-sh/ruff/pull/9566))
|
||||
- \[`flake8-pyi`\] Fix `PYI049` false negatives on call-based `TypedDict`s ([#9567](https://github.com/astral-sh/ruff/pull/9567))
|
||||
- \[`pylint`\] Exclude `self` and `cls` when counting method arguments (`PLR0917`) ([#9563](https://github.com/astral-sh/ruff/pull/9563))
|
||||
|
||||
### CLI
|
||||
|
||||
- `--show-settings` displays active settings in a far more readable format ([#9464](https://github.com/astral-sh/ruff/pull/9464))
|
||||
- Add `--extension` support to the formatter ([#9483](https://github.com/astral-sh/ruff/pull/9483))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Ignore preview status for fixable and unfixable selectors ([#9538](https://github.com/astral-sh/ruff/pull/9538))
|
||||
- \[`pycodestyle`\] Use the configured tab size when expanding indents ([#9506](https://github.com/astral-sh/ruff/pull/9506))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Recursively visit deferred AST nodes ([#9541](https://github.com/astral-sh/ruff/pull/9541))
|
||||
- Visit deferred lambdas before type definitions ([#9540](https://github.com/astral-sh/ruff/pull/9540))
|
||||
- \[`flake8-simplify`\] Avoid some more `enumerate-for-loop` false positives (`SIM113`) ([#9515](https://github.com/astral-sh/ruff/pull/9515))
|
||||
- \[`pandas-vet`\] Limit inplace diagnostics to methods that accept inplace ([#9495](https://github.com/astral-sh/ruff/pull/9495))
|
||||
- \[`pylint`\] Add the `__prepare__` method to the list of recognized dunder method ([#9529](https://github.com/astral-sh/ruff/pull/9529))
|
||||
- \[`pylint`\] Ignore unnecessary dunder calls within dunder definitions ([#9496](https://github.com/astral-sh/ruff/pull/9496))
|
||||
- \[`refurb`\] Avoid bailing when `reimplemented-operator` is called on function (`FURB118`) ([#9556](https://github.com/astral-sh/ruff/pull/9556))
|
||||
- \[`ruff`\] Avoid treating named expressions as static keys (`RUF011`) ([#9494](https://github.com/astral-sh/ruff/pull/9494))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add instructions on using `noqa` with isort rules ([#9555](https://github.com/astral-sh/ruff/pull/9555))
|
||||
- Documentation update for URL giving 'page not found' ([#9565](https://github.com/astral-sh/ruff/pull/9565))
|
||||
- Fix admonition in dark mode ([#9502](https://github.com/astral-sh/ruff/pull/9502))
|
||||
- Update contributing docs to use `cargo bench -p ruff_benchmark` ([#9535](https://github.com/astral-sh/ruff/pull/9535))
|
||||
- Update emacs integration section to include `emacs-ruff-format` ([#9403](https://github.com/astral-sh/ruff/pull/9403))
|
||||
- \[`flake8-blind-except`\] Document exceptions to `blind-except` rule ([#9580](https://github.com/astral-sh/ruff/pull/9580))
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Include base pyproject when initializing cache settings ([#9480](https://github.com/astral-sh/ruff/pull/9480))
|
||||
- \[`flake8-simplify`\] Account for possibly-empty f-string values in truthiness logic ([#9484](https://github.com/astral-sh/ruff/pull/9484))
|
||||
- \[`pylint`\] Add the missing period in `unnecessary-dunder-call` ([#9485](https://github.com/astral-sh/ruff/pull/9485))
|
||||
- \[`pylint`\] Fix `__aenter__` message in `unnecessary-dunder-call` ([#9492](https://github.com/astral-sh/ruff/pull/9492))
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Preview features
|
||||
|
||||
- Formatter: Hug multiline-strings in preview style ([#9243](https://github.com/astral-sh/ruff/pull/9243))
|
||||
- \[`flake8-bandit`\] Add `ssl-with-no-version` (`S504`) ([#9384](https://github.com/astral-sh/ruff/pull/9384))
|
||||
- \[`flake8-bandit`\] Implement `ssl-insecure-version` (`S502`) ([#9390](https://github.com/astral-sh/ruff/pull/9390))
|
||||
- \[`flake8-bandit`\] Implement `ssl-with-bad-defaults` (`S503`) ([#9391](https://github.com/astral-sh/ruff/pull/9391))
|
||||
- \[`flake8-bandit`\] Implement suspicious import rules (`S4XX`) ([#8831](https://github.com/astral-sh/ruff/pull/8831))
|
||||
- \[`flake8-simplify`\] Implement `zip-dict-keys-and-values` (`SIM911`) ([#9460](https://github.com/astral-sh/ruff/pull/9460))
|
||||
- \[`pyflakes`\] Add a fix for `redefined-while-unused` (`F811`) ([#9419](https://github.com/astral-sh/ruff/pull/9419))
|
||||
- \[`pylint`\] Implement `unnecessary-dunder-call` (`C2801`) ([#9166](https://github.com/astral-sh/ruff/pull/9166))
|
||||
- \[`ruff`\] Add `parenthesize-chained-operators` (`RUF021`) to enforce parentheses in `a or b and c` ([#9440](https://github.com/astral-sh/ruff/pull/9440))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-boolean-trap`\] Allow Boolean positional arguments in setters ([#9429](https://github.com/astral-sh/ruff/pull/9429))
|
||||
- \[`flake8-builtins`\] Restrict `builtin-attribute-shadowing` (`A003`) to actual shadowed references ([#9462](https://github.com/astral-sh/ruff/pull/9462))
|
||||
- \[`flake8-pyi`\] Add fix for `generator-return-from-iter-method` (`PYI058`) ([#9355](https://github.com/astral-sh/ruff/pull/9355))
|
||||
- \[`pyflakes`\] Don't flag `redefined-while-unused` (`F811`) in `if` branches ([#9418](https://github.com/astral-sh/ruff/pull/9418))
|
||||
- \[`pyupgrade`\] Add some additional Python 3.12 typing members to `deprecated-import` ([#9445](https://github.com/astral-sh/ruff/pull/9445))
|
||||
- \[`ruff`\] Add fix for `parenthesize-chained-operators` (`RUF021`) ([#9449](https://github.com/astral-sh/ruff/pull/9449))
|
||||
- \[`ruff`\] Include subscripts and attributes in static key rule (`RUF011`) ([#9416](https://github.com/astral-sh/ruff/pull/9416))
|
||||
- \[`ruff`\] Support variable keys in static dictionary key rule (`RUF011`) ([#9411](https://github.com/astral-sh/ruff/pull/9411))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Generate deterministic IDs when formatting notebooks ([#9359](https://github.com/astral-sh/ruff/pull/9359))
|
||||
- Allow `# fmt: skip` with interspersed same-line comments ([#9395](https://github.com/astral-sh/ruff/pull/9395))
|
||||
- Parenthesize breaking named expressions in match guards ([#9396](https://github.com/astral-sh/ruff/pull/9396))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add cell indexes to all diagnostics ([#9387](https://github.com/astral-sh/ruff/pull/9387))
|
||||
- Avoid infinite loop in constant vs. `None` comparisons ([#9376](https://github.com/astral-sh/ruff/pull/9376))
|
||||
- Handle raises with implicit alternate branches ([#9377](https://github.com/astral-sh/ruff/pull/9377))
|
||||
- Ignore trailing quotes for unclosed l-brace errors ([#9388](https://github.com/astral-sh/ruff/pull/9388))
|
||||
- Respect multi-segment submodule imports when resolving qualified names ([#9382](https://github.com/astral-sh/ruff/pull/9382))
|
||||
- Use `DisplayParseError` for stdin parser errors ([#9409](https://github.com/astral-sh/ruff/pull/9409))
|
||||
- Use `comment_ranges` for isort directive extraction ([#9414](https://github.com/astral-sh/ruff/pull/9414))
|
||||
- Use transformed source code for diagnostic locations ([#9408](https://github.com/astral-sh/ruff/pull/9408))
|
||||
- \[`flake8-pyi`\] Exclude `warnings.deprecated` and `typing_extensions.deprecated` arguments ([#9423](https://github.com/astral-sh/ruff/pull/9423))
|
||||
- \[`flake8-pyi`\] Fix false negative for `unused-private-protocol` (`PYI046`) with unused generic protocols ([#9405](https://github.com/astral-sh/ruff/pull/9405))
|
||||
- \[`pydocstyle`\] Disambiguate argument descriptors from section headers ([#9427](https://github.com/astral-sh/ruff/pull/9427))
|
||||
- \[`pylint`\] Homogenize `PLR0914` message to match other `PLR09XX` rules ([#9399](https://github.com/astral-sh/ruff/pull/9399))
|
||||
- \[`ruff`\] Allow `Hashable = None` in type annotations (`RUF013`) ([#9442](https://github.com/astral-sh/ruff/pull/9442))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix admonition hyperlink colouring ([#9385](https://github.com/astral-sh/ruff/pull/9385))
|
||||
- Add missing preview link ([#9386](https://github.com/astral-sh/ruff/pull/9386))
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`pylint`\] Implement `super-without-brackets` (`W0245`) ([#9257](https://github.com/astral-sh/ruff/pull/9257))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Check path string properly in `python -m ruff` invocations ([#9367](https://github.com/astral-sh/ruff/pull/9367))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Tweak `relative-imports` message ([#9365](https://github.com/astral-sh/ruff/pull/9365))
|
||||
- Add fix safety note for `yield-in-for-loop` ([#9364](https://github.com/astral-sh/ruff/pull/9364))
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -26,6 +26,10 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
- [`cargo dev`](#cargo-dev)
|
||||
- [Subsystems](#subsystems)
|
||||
- [Compilation Pipeline](#compilation-pipeline)
|
||||
- [Import Categorization](#import-categorization)
|
||||
- [Project root](#project-root)
|
||||
- [Package root](#package-root)
|
||||
- [Import categorization](#import-categorization-1)
|
||||
|
||||
## The Basics
|
||||
|
||||
@@ -63,7 +67,7 @@ You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
||||
cargo install cargo-insta
|
||||
```
|
||||
|
||||
and pre-commit to run some validation checks:
|
||||
And you'll need pre-commit to run some validation checks:
|
||||
|
||||
```shell
|
||||
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
||||
@@ -76,12 +80,22 @@ when making a commit:
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
|
||||
though it's not strictly necessary:
|
||||
|
||||
```shell
|
||||
cargo install cargo-nextest --locked
|
||||
```
|
||||
|
||||
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
|
||||
if you choose to install `nextest`.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run Ruff locally from the repository root with:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check /path/to/file.py --no-cache
|
||||
cargo run -p ruff -- check /path/to/file.py --no-cache
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted,
|
||||
@@ -120,7 +134,7 @@ At the time of writing, the repository includes the following crates:
|
||||
If you're working on a rule, this is the crate for you.
|
||||
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
|
||||
- `crates/ruff_cache`: library crate for caching lint results.
|
||||
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
|
||||
- `crates/ruff`: binary crate containing Ruff's command-line interface.
|
||||
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
|
||||
`cargo dev generate-all`), see the [`cargo dev`](#cargo-dev) section below.
|
||||
- `crates/ruff_diagnostics`: library crate for the rule-independent abstractions in the lint
|
||||
@@ -231,7 +245,7 @@ Once you've completed the code for the rule itself, you can define tests with th
|
||||
For example, if you're adding a new rule named `E402`, you would run:
|
||||
|
||||
```shell
|
||||
cargo run -p ruff_cli -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
|
||||
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --preview --select E402
|
||||
```
|
||||
|
||||
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
|
||||
@@ -252,7 +266,7 @@ Once you've completed the code for the rule itself, you can define tests with th
|
||||
|
||||
Ruff's user-facing settings live in a few different places.
|
||||
|
||||
First, the command-line options are defined via the `Args` struct in `crates/ruff_cli/src/args.rs`.
|
||||
First, the command-line options are defined via the `Args` struct in `crates/ruff/src/args.rs`.
|
||||
|
||||
Second, the `pyproject.toml` options are defined in `crates/ruff_workspace/src/options.rs` (via the
|
||||
`Options` struct), `crates/ruff_workspace/src/configuration.rs` (via the `Configuration` struct),
|
||||
@@ -370,9 +384,14 @@ See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/pyt
|
||||
We have several ways of benchmarking and profiling Ruff:
|
||||
|
||||
- Our main performance benchmark comparing Ruff with other tools on the CPython codebase
|
||||
- Microbenchmarks which the linter or the formatter on individual files. There run on pull requests.
|
||||
- Microbenchmarks which run the linter or the formatter on individual files. These run on pull requests.
|
||||
- Profiling the linter on either the microbenchmarks or entire projects
|
||||
|
||||
> \[!NOTE\]
|
||||
> When running benchmarks, ensure that your CPU is otherwise idle (e.g., close any background
|
||||
> applications, like web browsers). You may also want to switch your CPU to a "performance"
|
||||
> mode, if it exists, especially when benchmarking short-lived processes.
|
||||
|
||||
### CPython Benchmark
|
||||
|
||||
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
|
||||
@@ -518,10 +537,10 @@ if the benchmark improved/regressed compared to that baseline.
|
||||
|
||||
```shell
|
||||
# Run once on your "baseline" code
|
||||
cargo benchmark --save-baseline=main
|
||||
cargo bench -p ruff_benchmark -- --save-baseline=main
|
||||
|
||||
# Then iterate with
|
||||
cargo benchmark --baseline=main
|
||||
cargo bench -p ruff_benchmark -- --baseline=main
|
||||
```
|
||||
|
||||
#### PR Summary
|
||||
@@ -531,10 +550,10 @@ This is useful to illustrate the improvements of a PR.
|
||||
|
||||
```shell
|
||||
# On main
|
||||
cargo benchmark --save-baseline=main
|
||||
cargo bench -p ruff_benchmark -- --save-baseline=main
|
||||
|
||||
# After applying your changes
|
||||
cargo benchmark --save-baseline=pr
|
||||
cargo bench -p ruff_benchmark -- --save-baseline=pr
|
||||
|
||||
critcmp main pr
|
||||
```
|
||||
@@ -547,10 +566,10 @@ cargo install critcmp
|
||||
|
||||
#### Tips
|
||||
|
||||
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
|
||||
to only run the pydantic tests.
|
||||
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
|
||||
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
|
||||
- Use `cargo bench -p ruff_benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark lexer`
|
||||
to only run the lexer benchmarks.
|
||||
- Use `cargo bench -p ruff_benchmark -- --quiet` for a more cleaned up output (without statistical relevance)
|
||||
- Use `cargo bench -p ruff_benchmark -- --quick` to get faster results (more prone to noise)
|
||||
|
||||
### Profiling Projects
|
||||
|
||||
@@ -618,7 +637,7 @@ Otherwise, follow the instructions from the linux section.
|
||||
utils with it:
|
||||
|
||||
- `cargo dev print-ast <file>`: Print the AST of a python file using the
|
||||
[RustPython parser](https://github.com/astral-sh/RustPython-Parser/tree/main/parser) that is
|
||||
[RustPython parser](https://github.com/astral-sh/ruff/tree/main/crates/ruff_python_parser) that is
|
||||
mainly used in Ruff. For `if True: pass # comment`, you can see the syntax tree, the byte offsets
|
||||
for start and stop of each node and also how the `:` token, the comment and whitespace are not
|
||||
represented anymore:
|
||||
|
||||
996
Cargo.lock
generated
996
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
49
Cargo.toml
49
Cargo.toml
@@ -14,14 +14,15 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.76" }
|
||||
anyhow = { version = "1.0.79" }
|
||||
argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.8" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.12", features = ["derive"] }
|
||||
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
|
||||
@@ -40,22 +41,22 @@ fs-err = { version ="2.11.0"}
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
hexf-parse = { version ="0.2.1"}
|
||||
ignore = { version = "0.4.21" }
|
||||
ignore = { version = "0.4.22" }
|
||||
imara-diff ={ version = "0.1.5"}
|
||||
imperative = { version = "1.0.4" }
|
||||
indicatif ={ version = "0.17.7"}
|
||||
indicatif ={ version = "0.17.8"}
|
||||
indoc ={ version = "2.0.4"}
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
is-macro = { version = "0.3.4" }
|
||||
is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
itertools = { version = "0.12.0" }
|
||||
js-sys = { version = "0.3.66" }
|
||||
itertools = { version = "0.12.1" }
|
||||
js-sys = { version = "0.3.67" }
|
||||
lalrpop-util = { version = "0.20.0", default-features = false }
|
||||
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = { version = "2.6.4" }
|
||||
memchr = { version = "2.7.1" }
|
||||
mimalloc = { version ="0.1.39"}
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
@@ -64,36 +65,36 @@ path-absolutize = { version = "3.1.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.73" }
|
||||
pyproject-toml = { version = "0.8.1" }
|
||||
proc-macro2 = { version = "1.0.78" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
rayon = { version = "1.8.0" }
|
||||
rayon = { version = "1.8.1" }
|
||||
regex = { version = "1.10.2" }
|
||||
result-like = { version = "0.5.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version ="4.1.0"}
|
||||
semver = { version = "1.0.20" }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
semver = { version = "1.0.21" }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.3" }
|
||||
serde_json = { version = "1.0.109" }
|
||||
serde_json = { version = "1.0.113" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.4.0", default-features = false, features = ["macros"] }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
shlex = { version ="1.2.0"}
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
smallvec = { version = "1.11.2" }
|
||||
shlex = { version ="1.3.0"}
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.1" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
tempfile = { version ="3.8.1"}
|
||||
tempfile = { version ="3.9.0"}
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.51" }
|
||||
thiserror = { version = "1.0.57" }
|
||||
tikv-jemallocator = { version ="0.5.0"}
|
||||
toml = { version = "0.8.8" }
|
||||
toml = { version = "0.8.9" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
@@ -107,7 +108,7 @@ url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
wasm-bindgen = { version = "0.2.84" }
|
||||
wasm-bindgen-test = { version = "0.3.39" }
|
||||
wasm-bindgen-test = { version = "0.3.40" }
|
||||
wild = { version = "2" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
11
README.md
11
README.md
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.10
|
||||
rev: v0.2.1
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -386,6 +386,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- CERN ([Indico](https://getindico.io/))
|
||||
- [DVC](https://github.com/iterative/dvc)
|
||||
- [Dagger](https://github.com/dagger/dagger)
|
||||
- [Dagster](https://github.com/dagster-io/dagster)
|
||||
@@ -401,6 +402,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
[Diffusers](https://github.com/huggingface/diffusers))
|
||||
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [ivy](https://github.com/unifyai/ivy)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [LangChain](https://github.com/hwchase17/langchain)
|
||||
- [Litestar](https://litestar.dev/)
|
||||
@@ -417,6 +419,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||
- [NumPyro](https://github.com/pyro-ppl/numpyro)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [PDM](https://github.com/pdm-project/pdm)
|
||||
@@ -428,7 +431,9 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [PostHog](https://github.com/PostHog/posthog)
|
||||
- Prefect ([Python SDK](https://github.com/PrefectHQ/prefect), [Marvin](https://github.com/PrefectHQ/marvin))
|
||||
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
|
||||
- [PyMC](https://github.com/pymc-devs/pymc/)
|
||||
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||
- [pytest](https://github.com/pytest-dev/pytest)
|
||||
- [PyTorch](https://github.com/pytorch/pytorch)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Pylint](https://github.com/PyCQA/pylint)
|
||||
@@ -459,7 +464,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
|
||||
### Show Your Support
|
||||
|
||||
If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
|
||||
If you're using Ruff, consider adding the Ruff badge to your project's `README.md`:
|
||||
|
||||
```md
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
@@ -485,6 +490,6 @@ MIT
|
||||
|
||||
<div align="center">
|
||||
<a target="_blank" href="https://astral.sh" style="background:none">
|
||||
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg">
|
||||
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg" alt="Made by Astral">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.10"
|
||||
name = "ruff"
|
||||
version = "0.2.1"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -10,9 +10,7 @@ documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
readme = "../../README.md"
|
||||
|
||||
[[bin]]
|
||||
name = "ruff"
|
||||
default-run = "ruff"
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
@@ -32,7 +30,7 @@ bincode = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
cachedir = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
clearscreen = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
@@ -50,12 +48,16 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
strum = { workspace = true, features = [] }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
walkdir = { workspace = true }
|
||||
wild = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
||||
assert_cmd = { workspace = true }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"]}
|
||||
1289
crates/ruff/src/args.rs
Normal file
1289
crates/ruff/src/args.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fs::{self, File};
|
||||
use std::hash::Hasher;
|
||||
use std::io::{self, BufReader, BufWriter, Write};
|
||||
use std::io::{self, BufReader, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Mutex;
|
||||
@@ -15,6 +15,7 @@ use rayon::iter::ParallelIterator;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||
@@ -25,10 +26,9 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy, Resolver};
|
||||
use ruff_workspace::resolver::Resolver;
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::cache;
|
||||
use crate::diagnostics::Diagnostics;
|
||||
|
||||
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
||||
@@ -86,6 +86,7 @@ pub(crate) struct Cache {
|
||||
changes: Mutex<Vec<Change>>,
|
||||
/// The "current" timestamp used as cache for the updates of
|
||||
/// [`FileCache::last_seen`]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
last_seen_cache: u64,
|
||||
}
|
||||
|
||||
@@ -165,15 +166,29 @@ impl Cache {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = File::create(&self.path)
|
||||
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
|
||||
let writer = BufWriter::new(file);
|
||||
bincode::serialize_into(writer, &self.package).with_context(|| {
|
||||
// Write the cache to a temporary file first and then rename it for an "atomic" write.
|
||||
// Protects against data loss if the process is killed during the write and races between different ruff
|
||||
// processes, resulting in a corrupted cache file. https://github.com/astral-sh/ruff/issues/8147#issuecomment-1943345964
|
||||
let mut temp_file =
|
||||
NamedTempFile::new_in(self.path.parent().expect("Write path must have a parent"))
|
||||
.context("Failed to create temporary file")?;
|
||||
|
||||
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
|
||||
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
|
||||
let serialized =
|
||||
bincode::serialize(&self.package).context("Failed to serialize cache data")?;
|
||||
temp_file
|
||||
.write_all(&serialized)
|
||||
.context("Failed to write serialized cache to temporary file.")?;
|
||||
|
||||
temp_file.persist(&self.path).with_context(|| {
|
||||
format!(
|
||||
"Failed to serialise cache to file '{}'",
|
||||
"Failed to rename temporary cache file to {}",
|
||||
self.path.display()
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies the pending changes without storing the cache to disk.
|
||||
@@ -442,7 +457,7 @@ pub(super) struct CacheMessage {
|
||||
pub(crate) trait PackageCaches {
|
||||
fn get(&self, package_root: &Path) -> Option<&Cache>;
|
||||
|
||||
fn persist(self) -> anyhow::Result<()>;
|
||||
fn persist(self) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T> PackageCaches for Option<T>
|
||||
@@ -468,27 +483,17 @@ 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) {
|
||||
if let Err(e) = 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);
|
||||
}
|
||||
}
|
||||
for settings in resolver.settings() {
|
||||
init_cache(&settings.cache_dir);
|
||||
}
|
||||
|
||||
Self(
|
||||
@@ -498,7 +503,7 @@ impl<'a> PackageCacheMap<'a> {
|
||||
.unique()
|
||||
.par_bridge()
|
||||
.map(|cache_root| {
|
||||
let settings = resolver.resolve(cache_root, pyproject_config);
|
||||
let settings = resolver.resolve(cache_root);
|
||||
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
||||
(cache_root, cache)
|
||||
})
|
||||
@@ -1060,6 +1065,7 @@ mod tests {
|
||||
&self.settings.formatter,
|
||||
PySourceType::Python,
|
||||
FormatMode::Write,
|
||||
None,
|
||||
Some(cache),
|
||||
)
|
||||
}
|
||||
@@ -12,17 +12,17 @@ use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Add `noqa` directives to a collection of files.
|
||||
pub(crate) fn add_noqa(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
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, config_arguments)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
@@ -38,7 +38,6 @@ pub(crate) fn add_noqa(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -57,7 +56,7 @@ pub(crate) fn add_noqa(
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let settings = resolver.resolve(path);
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
Ok(None) => return None,
|
||||
@@ -24,7 +24,7 @@ use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::panic::catch_unwind;
|
||||
@@ -34,7 +34,7 @@ use crate::panic::catch_unwind;
|
||||
pub(crate) fn check(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
cache: flags::Cache,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
@@ -42,7 +42,7 @@ pub(crate) fn check(
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the Python files to check.
|
||||
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, config_arguments)?;
|
||||
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||
|
||||
if paths.is_empty() {
|
||||
@@ -57,16 +57,11 @@ pub(crate) fn check(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
|
||||
// Load the caches.
|
||||
let caches = if bool::from(cache) {
|
||||
Some(PackageCacheMap::init(
|
||||
pyproject_config,
|
||||
&package_roots,
|
||||
&resolver,
|
||||
))
|
||||
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -81,7 +76,7 @@ pub(crate) fn check(
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let settings = resolver.resolve(path);
|
||||
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
@@ -128,7 +123,7 @@ pub(crate) fn check(
|
||||
|
||||
Some(result.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let settings = resolver.resolve(path);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
@@ -238,7 +233,7 @@ mod test {
|
||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
use super::check;
|
||||
|
||||
@@ -277,7 +272,7 @@ mod test {
|
||||
// Notebooks are not included by default
|
||||
&[tempdir.path().to_path_buf(), notebook],
|
||||
&pyproject_config,
|
||||
&CliOverrides::default(),
|
||||
&ConfigArguments::default(),
|
||||
flags::Cache::Disabled,
|
||||
flags::Noqa::Disabled,
|
||||
flags::FixMode::Generate,
|
||||
@@ -4,9 +4,9 @@ use anyhow::Result;
|
||||
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
|
||||
@@ -14,24 +14,24 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
pub(crate) fn check_stdin(
|
||||
filename: Option<&Path>,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
overrides: &ConfigArguments,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
let mut resolver = Resolver::new(pyproject_config);
|
||||
|
||||
if resolver.force_exclude() {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
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))
|
||||
{
|
||||
if filename.file_name().is_some_and(|name| {
|
||||
match_exclusion(filename, name, &resolver.base_settings().linter.exclude)
|
||||
}) {
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
@@ -41,13 +41,13 @@ pub(crate) fn check_stdin(
|
||||
}
|
||||
let stdin = read_from_stdin()?;
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
||||
packaging::detect_package_root(path, &resolver.base_settings().linter.namespace_packages)
|
||||
});
|
||||
let mut diagnostics = lint_stdin(
|
||||
filename,
|
||||
package_root,
|
||||
stdin,
|
||||
&pyproject_config.settings,
|
||||
resolver.base_settings(),
|
||||
noqa,
|
||||
fix_mode,
|
||||
)?;
|
||||
@@ -23,14 +23,13 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||
use ruff_python_formatter::{format_module_source, format_range, FormatModuleError, QuoteStyle};
|
||||
use ruff_source_file::LineIndex;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
||||
};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
use crate::resolve::resolve;
|
||||
@@ -61,25 +60,31 @@ impl FormatMode {
|
||||
/// Format a set of files, and return the exit status.
|
||||
pub(crate) fn format(
|
||||
cli: FormatArguments,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
log_level: LogLevel,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
let mode = FormatMode::from_cli(&cli);
|
||||
let files = resolve_default_files(cli.files, false);
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
warn_incompatible_formatter_settings(&pyproject_config, Some(&resolver));
|
||||
if cli.range.is_some() && paths.len() > 1 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The `--range` option is only supported when formatting a single file but the specified paths resolve to {} files.",
|
||||
paths.len()
|
||||
));
|
||||
}
|
||||
|
||||
warn_incompatible_formatter_settings(&resolver);
|
||||
|
||||
// Discover the package root for each Python file.
|
||||
let package_roots = resolver.package_roots(
|
||||
@@ -88,7 +93,6 @@ pub(crate) fn format(
|
||||
.flatten()
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
&pyproject_config,
|
||||
);
|
||||
|
||||
let caches = if cli.no_cache {
|
||||
@@ -99,11 +103,7 @@ pub(crate) fn format(
|
||||
#[cfg(debug_assertions)]
|
||||
crate::warn_user!("Detected debug build without --no-cache.");
|
||||
|
||||
Some(PackageCacheMap::init(
|
||||
&pyproject_config,
|
||||
&package_roots,
|
||||
&resolver,
|
||||
))
|
||||
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -113,12 +113,18 @@ pub(crate) fn format(
|
||||
match entry {
|
||||
Ok(resolved_file) => {
|
||||
let path = resolved_file.path();
|
||||
let SourceType::Python(source_type) = SourceType::from(&path) else {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
};
|
||||
let settings = resolver.resolve(path);
|
||||
|
||||
let settings = resolver.resolve(path, &pyproject_config);
|
||||
let source_type = match settings.formatter.extension.get(path) {
|
||||
None => match SourceType::from(path) {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
@@ -140,7 +146,14 @@ pub(crate) fn format(
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(path, &settings.formatter, source_type, mode, cache)
|
||||
format_path(
|
||||
path,
|
||||
&settings.formatter,
|
||||
source_type,
|
||||
mode,
|
||||
cli.range,
|
||||
cache,
|
||||
)
|
||||
}) {
|
||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||
path: resolved_file.path().to_path_buf(),
|
||||
@@ -227,6 +240,7 @@ pub(crate) fn format_path(
|
||||
settings: &FormatterSettings,
|
||||
source_type: PySourceType,
|
||||
mode: FormatMode,
|
||||
range: Option<FormatRange>,
|
||||
cache: Option<&Cache>,
|
||||
) -> Result<FormatResult, FormatCommandError> {
|
||||
if let Some(cache) = cache {
|
||||
@@ -251,8 +265,12 @@ pub(crate) fn format_path(
|
||||
}
|
||||
};
|
||||
|
||||
// Don't write back to the cache if formatting a range.
|
||||
let cache = cache.filter(|_| range.is_none());
|
||||
|
||||
// Format the source.
|
||||
let format_result = match format_source(&unformatted, source_type, Some(path), settings)? {
|
||||
let format_result = match format_source(&unformatted, source_type, Some(path), settings, range)?
|
||||
{
|
||||
FormattedSource::Formatted(formatted) => match mode {
|
||||
FormatMode::Write => {
|
||||
let mut writer = File::create(path).map_err(|err| {
|
||||
@@ -320,12 +338,31 @@ pub(crate) fn format_source(
|
||||
source_type: PySourceType,
|
||||
path: Option<&Path>,
|
||||
settings: &FormatterSettings,
|
||||
range: Option<FormatRange>,
|
||||
) -> Result<FormattedSource, FormatCommandError> {
|
||||
match &source_kind {
|
||||
SourceKind::Python(unformatted) => {
|
||||
let options = settings.to_format_options(source_type, unformatted);
|
||||
|
||||
let formatted = format_module_source(unformatted, options).map_err(|err| {
|
||||
let formatted = if let Some(range) = range {
|
||||
let line_index = LineIndex::from_source_text(unformatted);
|
||||
let byte_range = range.to_text_range(unformatted, &line_index);
|
||||
format_range(unformatted, byte_range, options).map(|formatted_range| {
|
||||
let mut formatted = unformatted.to_string();
|
||||
formatted.replace_range(
|
||||
std::ops::Range::<usize>::from(formatted_range.source_range()),
|
||||
formatted_range.as_code(),
|
||||
);
|
||||
|
||||
formatted
|
||||
})
|
||||
} else {
|
||||
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
|
||||
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||
format_module_source(unformatted, options).map(|formatted| formatted.into_code())
|
||||
};
|
||||
|
||||
let formatted = formatted.map_err(|err| {
|
||||
if let FormatModuleError::ParseError(err) = err {
|
||||
DisplayParseError::from_source_kind(
|
||||
err,
|
||||
@@ -338,7 +375,6 @@ pub(crate) fn format_source(
|
||||
}
|
||||
})?;
|
||||
|
||||
let formatted = formatted.into_code();
|
||||
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
||||
Ok(FormattedSource::Unchanged)
|
||||
} else {
|
||||
@@ -350,6 +386,12 @@ pub(crate) fn format_source(
|
||||
return Ok(FormattedSource::Unchanged);
|
||||
}
|
||||
|
||||
if range.is_some() {
|
||||
return Err(FormatCommandError::RangeFormatNotebook(
|
||||
path.map(Path::to_path_buf),
|
||||
));
|
||||
}
|
||||
|
||||
let options = settings.to_format_options(source_type, notebook.source_code());
|
||||
|
||||
let mut output: Option<String> = None;
|
||||
@@ -590,6 +632,7 @@ pub(crate) enum FormatCommandError {
|
||||
Format(Option<PathBuf>, FormatModuleError),
|
||||
Write(Option<PathBuf>, SourceError),
|
||||
Diff(Option<PathBuf>, io::Error),
|
||||
RangeFormatNotebook(Option<PathBuf>),
|
||||
}
|
||||
|
||||
impl FormatCommandError {
|
||||
@@ -607,7 +650,8 @@ impl FormatCommandError {
|
||||
| Self::Read(path, _)
|
||||
| Self::Format(path, _)
|
||||
| Self::Write(path, _)
|
||||
| Self::Diff(path, _) => path.as_deref(),
|
||||
| Self::Diff(path, _)
|
||||
| Self::RangeFormatNotebook(path) => path.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,9 +673,10 @@ impl Display for FormatCommandError {
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
"Encountered error:".bold(),
|
||||
err.io_error()
|
||||
"{header} {error}",
|
||||
header = "Encountered error:".bold(),
|
||||
error = err
|
||||
.io_error()
|
||||
.map_or_else(|| err.to_string(), std::string::ToString::to_string)
|
||||
)
|
||||
}
|
||||
@@ -649,7 +694,7 @@ impl Display for FormatCommandError {
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(f, "{}{} {err}", "Failed to read".bold(), ":".bold())
|
||||
write!(f, "{header} {err}", header = "Failed to read:".bold())
|
||||
}
|
||||
}
|
||||
Self::Write(path, err) => {
|
||||
@@ -662,7 +707,7 @@ impl Display for FormatCommandError {
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(f, "{}{} {err}", "Failed to write".bold(), ":".bold())
|
||||
write!(f, "{header} {err}", header = "Failed to write:".bold())
|
||||
}
|
||||
}
|
||||
Self::Format(path, err) => {
|
||||
@@ -675,7 +720,7 @@ impl Display for FormatCommandError {
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||
write!(f, "{header} {err}", header = "Failed to format:".bold())
|
||||
}
|
||||
}
|
||||
Self::Diff(path, err) => {
|
||||
@@ -690,9 +735,25 @@ impl Display for FormatCommandError {
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}{} {err}",
|
||||
"Failed to generate diff".bold(),
|
||||
":".bold()
|
||||
"{header} {err}",
|
||||
header = "Failed to generate diff:".bold(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::RangeFormatNotebook(path) => {
|
||||
if let Some(path) = path {
|
||||
write!(
|
||||
f,
|
||||
"{header}{path}{colon} Range formatting isn't supported for notebooks.",
|
||||
header = "Failed to format ".bold(),
|
||||
path = fs::relativize_path(path).bold(),
|
||||
colon = ":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{header} Range formatting isn't supported for notebooks",
|
||||
header = "Failed to format:".bold()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -723,15 +784,10 @@ impl Display for FormatCommandError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn warn_incompatible_formatter_settings(
|
||||
pyproject_config: &PyprojectConfig,
|
||||
resolver: Option<&Resolver>,
|
||||
) {
|
||||
pub(super) fn warn_incompatible_formatter_settings(resolver: &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 setting in resolver.settings() {
|
||||
for rule in [
|
||||
// The formatter might collapse implicit string concatenation on a single line.
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
@@ -760,9 +816,7 @@ pub(super) fn warn_incompatible_formatter_settings(
|
||||
}
|
||||
|
||||
// Next, validate settings-specific incompatibilities.
|
||||
for setting in std::iter::once(&pyproject_config.settings)
|
||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||
{
|
||||
for setting in resolver.settings() {
|
||||
// Validate all rules that rely on tab styles.
|
||||
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||
&& setting.formatter.indent_style.is_tab()
|
||||
@@ -6,10 +6,10 @@ use log::error;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
||||
use crate::commands::format::{
|
||||
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
||||
FormatResult, FormattedSource,
|
||||
@@ -19,32 +19,33 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
use crate::ExitStatus;
|
||||
|
||||
/// Run the formatter over a single file, read from `stdin`.
|
||||
pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> Result<ExitStatus> {
|
||||
pub(crate) fn format_stdin(
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> Result<ExitStatus> {
|
||||
let pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
|
||||
warn_incompatible_formatter_settings(&pyproject_config, None);
|
||||
let mut resolver = Resolver::new(&pyproject_config);
|
||||
warn_incompatible_formatter_settings(&resolver);
|
||||
|
||||
let mode = FormatMode::from_cli(cli);
|
||||
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if resolver.force_exclude() {
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
if !python_file_at_path(filename, &mut resolver, config_arguments)? {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
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))
|
||||
{
|
||||
if filename.file_name().is_some_and(|name| {
|
||||
match_exclusion(filename, name, &resolver.base_settings().formatter.exclude)
|
||||
}) {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
@@ -54,21 +55,23 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
}
|
||||
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
let settings = &resolver.base_settings().formatter;
|
||||
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
let source_type = match path.and_then(|path| settings.extension.get(path)) {
|
||||
None => match path.map(SourceType::from).unwrap_or_default() {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Format the file.
|
||||
match format_source_code(
|
||||
path,
|
||||
&pyproject_config.settings.formatter,
|
||||
source_type,
|
||||
mode,
|
||||
) {
|
||||
match format_source_code(path, cli.range, settings, source_type, mode) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check | FormatMode::Diff => {
|
||||
@@ -89,6 +92,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
/// Format source code read from `stdin`.
|
||||
fn format_source_code(
|
||||
path: Option<&Path>,
|
||||
range: Option<FormatRange>,
|
||||
settings: &FormatterSettings,
|
||||
source_type: PySourceType,
|
||||
mode: FormatMode,
|
||||
@@ -106,7 +110,7 @@ fn format_source_code(
|
||||
};
|
||||
|
||||
// Format the source.
|
||||
let formatted = format_source(&source_kind, source_type, path, settings)?;
|
||||
let formatted = format_source(&source_kind, source_type, path, settings, range)?;
|
||||
|
||||
match &formatted {
|
||||
FormattedSource::Formatted(formatted) => match mode {
|
||||
@@ -18,6 +18,7 @@ struct Explanation<'a> {
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
fix: String,
|
||||
#[allow(clippy::struct_field_names)]
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
}
|
||||
@@ -7,17 +7,17 @@ use itertools::Itertools;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub(crate) fn show_files(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
writer: &mut impl Write,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, _resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, _resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
@@ -6,17 +6,17 @@ use itertools::Itertools;
|
||||
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub(crate) fn show_settings(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
writer: &mut impl Write,
|
||||
) -> Result<()> {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
||||
|
||||
// Print the list of files.
|
||||
let Some(path) = paths
|
||||
@@ -29,13 +29,13 @@ pub(crate) fn show_settings(
|
||||
bail!("No files found under the given path");
|
||||
};
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
let settings = resolver.resolve(&path);
|
||||
|
||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||
writeln!(writer, "Resolved settings for: \"{}\"", path.display())?;
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
writeln!(writer, "Settings path: {settings_path:?}")?;
|
||||
writeln!(writer, "Settings path: \"{}\"", settings_path.display())?;
|
||||
}
|
||||
writeln!(writer, "{settings:#?}")?;
|
||||
write!(writer, "{settings}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/src/commands/check.rs
|
||||
source: crates/ruff/src/commands/check.rs
|
||||
---
|
||||
/home/ferris/project/code.py:1:1: E902 Permission denied (os error 13)
|
||||
/home/ferris/project/notebook.ipynb:1:1: E902 Permission denied (os error 13)
|
||||
@@ -1,5 +1,6 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::ops::{Add, AddAssign};
|
||||
@@ -16,7 +17,7 @@ use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes};
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
@@ -178,11 +179,6 @@ impl AddAssign for FixMap {
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
pub(crate) fn lint_path(
|
||||
path: &Path,
|
||||
@@ -227,7 +223,7 @@ pub(crate) fn lint_path(
|
||||
|
||||
debug!("Checking: {}", path.display());
|
||||
|
||||
let source_type = match override_source_type(Some(path), &settings.extension) {
|
||||
let source_type = match settings.extension.get(path).map(PySourceType::from) {
|
||||
Some(source_type) => source_type,
|
||||
None => match SourceType::from(path) {
|
||||
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||
@@ -273,6 +269,7 @@ pub(crate) fn lint_path(
|
||||
data: (messages, imports),
|
||||
error: parse_error,
|
||||
},
|
||||
transformed,
|
||||
fixed,
|
||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
@@ -301,7 +298,12 @@ pub(crate) fn lint_path(
|
||||
flags::FixMode::Generate => {}
|
||||
}
|
||||
}
|
||||
(result, fixed)
|
||||
let transformed = if let Cow::Owned(transformed) = transformed {
|
||||
transformed
|
||||
} else {
|
||||
source_kind
|
||||
};
|
||||
(result, transformed, fixed)
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
let result = lint_only(
|
||||
@@ -313,8 +315,9 @@ pub(crate) fn lint_path(
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
let result = lint_only(
|
||||
@@ -326,8 +329,9 @@ pub(crate) fn lint_path(
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
let imports = imports.unwrap_or_default();
|
||||
@@ -335,7 +339,7 @@ pub(crate) fn lint_path(
|
||||
if let Some((cache, relative_path, key)) = caching {
|
||||
// We don't cache parsing errors.
|
||||
if parse_error.is_none() {
|
||||
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||
// `FixMode::Apply` 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 {
|
||||
@@ -350,7 +354,7 @@ pub(crate) fn lint_path(
|
||||
LintCacheData::from_messages(
|
||||
&messages,
|
||||
imports.clone(),
|
||||
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||
transformed.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -360,11 +364,11 @@ pub(crate) fn lint_path(
|
||||
if let Some(error) = parse_error {
|
||||
error!(
|
||||
"{}",
|
||||
DisplayParseError::from_source_kind(error, Some(path.to_path_buf()), &source_kind,)
|
||||
DisplayParseError::from_source_kind(error, Some(path.to_path_buf()), &transformed)
|
||||
);
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = source_kind {
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook.into_index())])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
@@ -389,15 +393,14 @@ pub(crate) fn lint_stdin(
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// TODO(charlie): Support `pyproject.toml`.
|
||||
let source_type = if let Some(source_type) =
|
||||
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
|
||||
let source_type = match path.and_then(|path| settings.linter.extension.get(path)) {
|
||||
None => match path.map(SourceType::from).unwrap_or_default() {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
@@ -415,6 +418,7 @@ pub(crate) fn lint_stdin(
|
||||
data: (messages, imports),
|
||||
error: parse_error,
|
||||
},
|
||||
transformed,
|
||||
fixed,
|
||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
@@ -443,8 +447,12 @@ pub(crate) fn lint_stdin(
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
}
|
||||
|
||||
(result, fixed)
|
||||
let transformed = if let Cow::Owned(transformed) = transformed {
|
||||
transformed
|
||||
} else {
|
||||
source_kind
|
||||
};
|
||||
(result, transformed, fixed)
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
let result = lint_only(
|
||||
@@ -456,14 +464,15 @@ pub(crate) fn lint_stdin(
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let fixed = FxHashMap::default();
|
||||
|
||||
// Write the contents to stdout anyway.
|
||||
if fix_mode.is_apply() {
|
||||
source_kind.write(&mut io::stdout().lock())?;
|
||||
}
|
||||
|
||||
(result, fixed)
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, transformed, fixed)
|
||||
}
|
||||
} else {
|
||||
let result = lint_only(
|
||||
@@ -475,20 +484,21 @@ pub(crate) fn lint_stdin(
|
||||
source_type,
|
||||
ParseSource::None,
|
||||
);
|
||||
let transformed = source_kind;
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
let imports = imports.unwrap_or_default();
|
||||
|
||||
if let Some(err) = parse_error {
|
||||
if let Some(error) = parse_error {
|
||||
error!(
|
||||
"Failed to parse {}: {err}",
|
||||
path.map_or_else(|| "-".into(), fs::relativize_path).bold()
|
||||
"{}",
|
||||
DisplayParseError::from_source_kind(error, path.map(Path::to_path_buf), &transformed)
|
||||
);
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = source_kind {
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {
|
||||
FxHashMap::from_iter([(
|
||||
path.map_or_else(|| "-".into(), |path| path.to_string_lossy().to_string()),
|
||||
notebook.into_index(),
|
||||
@@ -204,24 +204,23 @@ pub fn run(
|
||||
}
|
||||
|
||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, overrides) = args.partition();
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
|
||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||
commands::format_stdin::format_stdin(&cli, &overrides)
|
||||
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
||||
} else {
|
||||
commands::format::format(cli, &overrides, log_level)
|
||||
commands::format::format(cli, &config_arguments, log_level)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let (cli, overrides) = args.partition();
|
||||
let (cli, config_arguments) = args.partition()?;
|
||||
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
|
||||
@@ -239,11 +238,21 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let files = resolve_default_files(cli.files, is_stdin);
|
||||
|
||||
if cli.show_settings {
|
||||
commands::show_settings::show_settings(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
commands::show_settings::show_settings(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&config_arguments,
|
||||
&mut writer,
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
if cli.show_files {
|
||||
commands::show_files::show_files(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||
commands::show_files::show_files(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&config_arguments,
|
||||
&mut writer,
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
@@ -255,7 +264,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
unsafe_fixes,
|
||||
output_format,
|
||||
show_fixes,
|
||||
show_source,
|
||||
..
|
||||
} = pyproject_config.settings;
|
||||
|
||||
@@ -284,9 +292,6 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if show_fixes {
|
||||
printer_flags |= PrinterFlags::SHOW_FIX_SUMMARY;
|
||||
}
|
||||
if show_source {
|
||||
printer_flags |= PrinterFlags::SHOW_SOURCE;
|
||||
}
|
||||
if cli.ecosystem_ci {
|
||||
warn_user!(
|
||||
"The formatting of fixes emitted by this option is a work-in-progress, subject to \
|
||||
@@ -306,7 +311,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if !fix_mode.is_generate() {
|
||||
warn_user!("--fix is incompatible with --add-noqa.");
|
||||
}
|
||||
let modifications = commands::add_noqa::add_noqa(&files, &pyproject_config, &overrides)?;
|
||||
let modifications =
|
||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[allow(clippy::print_stderr)]
|
||||
@@ -325,9 +331,18 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
printer_flags,
|
||||
);
|
||||
|
||||
// the settings should already be combined with the CLI overrides at this point
|
||||
// TODO(jane): let's make this `PreviewMode`
|
||||
// TODO: this should reference the global preview mode once https://github.com/astral-sh/ruff/issues/8232
|
||||
// is resolved.
|
||||
let preview = pyproject_config.settings.linter.preview.is_enabled();
|
||||
|
||||
if cli.watch {
|
||||
if output_format != SerializationFormat::Text {
|
||||
warn_user!("`--output-format text` is always used in watch mode.");
|
||||
if output_format != SerializationFormat::default(preview) {
|
||||
warn_user!(
|
||||
"`--output-format {}` is always used in watch mode.",
|
||||
SerializationFormat::default(preview)
|
||||
);
|
||||
}
|
||||
|
||||
// Configure the file watcher.
|
||||
@@ -347,13 +362,13 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let messages = commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
printer.write_continuously(&mut writer, &messages, preview)?;
|
||||
|
||||
// In watch mode, we may need to re-resolve the configuration.
|
||||
// TODO(charlie): Re-compute other derivative values, like the `printer`.
|
||||
@@ -369,8 +384,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if matches!(change_kind, ChangeKind::Configuration) {
|
||||
pyproject_config = resolve::resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
}
|
||||
@@ -380,13 +394,13 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let messages = commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
printer.write_continuously(&mut writer, &messages, preview)?;
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
@@ -397,7 +411,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
commands::check_stdin::check_stdin(
|
||||
cli.stdin_filename.map(fs::normalize_path).as_deref(),
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
)?
|
||||
@@ -405,7 +419,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
commands::check::check(
|
||||
&files,
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
&config_arguments,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
@@ -3,8 +3,8 @@ use std::process::ExitCode;
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
|
||||
use ruff_cli::args::{Args, Command};
|
||||
use ruff_cli::{run, ExitStatus};
|
||||
use ruff::args::{Args, Command};
|
||||
use ruff::{run, ExitStatus};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
@@ -27,8 +27,6 @@ bitflags! {
|
||||
pub(crate) struct Flags: u8 {
|
||||
/// Whether to show violations when emitting diagnostics.
|
||||
const SHOW_VIOLATIONS = 0b0000_0001;
|
||||
/// Whether to show the source code when emitting diagnostics.
|
||||
const SHOW_SOURCE = 0b000_0010;
|
||||
/// Whether to show a summary of the fixed violations when emitting diagnostics.
|
||||
const SHOW_FIX_SUMMARY = 0b0000_0100;
|
||||
/// Whether to show a diff of each fixed violation when emitting diagnostics.
|
||||
@@ -218,7 +216,10 @@ impl Printer {
|
||||
if !self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||
if matches!(
|
||||
self.format,
|
||||
SerializationFormat::Text | SerializationFormat::Grouped
|
||||
SerializationFormat::Text
|
||||
| SerializationFormat::Full
|
||||
| SerializationFormat::Concise
|
||||
| SerializationFormat::Grouped
|
||||
) {
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
if !diagnostics.fixed.is_empty() {
|
||||
@@ -245,11 +246,12 @@ impl Printer {
|
||||
SerializationFormat::Junit => {
|
||||
JunitEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
SerializationFormat::Concise
|
||||
| SerializationFormat::Full => {
|
||||
TextEmitter::default()
|
||||
.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_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.with_show_source(self.format == SerializationFormat::Full)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
|
||||
@@ -265,7 +267,6 @@ impl Printer {
|
||||
}
|
||||
SerializationFormat::Grouped => {
|
||||
GroupedEmitter::default()
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
@@ -294,6 +295,7 @@ impl Printer {
|
||||
SerializationFormat::Sarif => {
|
||||
SarifEmitter.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
SerializationFormat::Text => unreachable!("Text is deprecated and should have been automatically converted to the default serialization format")
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
@@ -342,7 +344,9 @@ impl Printer {
|
||||
}
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Text => {
|
||||
SerializationFormat::Text
|
||||
| SerializationFormat::Full
|
||||
| SerializationFormat::Concise => {
|
||||
// Compute the maximum number of digits in the count and code, for all messages,
|
||||
// to enable pretty-printing.
|
||||
let count_width = num_digits(
|
||||
@@ -403,6 +407,7 @@ impl Printer {
|
||||
&self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &Diagnostics,
|
||||
preview: bool,
|
||||
) -> Result<()> {
|
||||
if matches!(self.log_level, LogLevel::Silent) {
|
||||
return Ok(());
|
||||
@@ -430,7 +435,7 @@ impl Printer {
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
TextEmitter::default()
|
||||
.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(preview)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
@@ -11,19 +11,18 @@ use ruff_workspace::resolver::{
|
||||
Relativity,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::args::ConfigArguments;
|
||||
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
pub fn resolve(
|
||||
isolated: bool,
|
||||
config: Option<&Path>,
|
||||
overrides: &CliOverrides,
|
||||
config_arguments: &ConfigArguments,
|
||||
stdin_filename: Option<&Path>,
|
||||
) -> Result<PyprojectConfig> {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
if isolated {
|
||||
let config = overrides.transform(Configuration::default());
|
||||
let config = config_arguments.transform(Configuration::default());
|
||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||
debug!("Isolated mode, not reading any pyproject.toml");
|
||||
return Ok(PyprojectConfig::new(
|
||||
@@ -36,12 +35,13 @@ pub fn resolve(
|
||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
if let Some(pyproject) = config
|
||||
if let Some(pyproject) = config_arguments
|
||||
.config_file()
|
||||
.map(|config| config.display().to_string())
|
||||
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
|
||||
.transpose()?
|
||||
{
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
||||
debug!(
|
||||
"Using user-specified configuration file at: {}",
|
||||
pyproject.display()
|
||||
@@ -67,7 +67,7 @@ pub fn resolve(
|
||||
"Using configuration file (via parent) at: {}",
|
||||
pyproject.display()
|
||||
);
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Parent, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Parent, config_arguments)?;
|
||||
return Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
settings,
|
||||
@@ -84,7 +84,7 @@ pub fn resolve(
|
||||
"Using configuration file (via cwd) at: {}",
|
||||
pyproject.display()
|
||||
);
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
||||
return Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
settings,
|
||||
@@ -97,7 +97,7 @@ pub fn resolve(
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
debug!("Using Ruff default settings");
|
||||
let config = overrides.transform(Configuration::default());
|
||||
let config = config_arguments.transform(Configuration::default());
|
||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||
Ok(PyprojectConfig::new(
|
||||
PyprojectDiscoveryStrategy::Hierarchical,
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/version.rs
|
||||
expression: version
|
||||
---
|
||||
0.0.0
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/src/version.rs
|
||||
source: crates/ruff/src/version.rs
|
||||
expression: version
|
||||
---
|
||||
0.0.0 (53b0f5d92 2023-10-19)
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/src/version.rs
|
||||
source: crates/ruff/src/version.rs
|
||||
expression: version
|
||||
---
|
||||
0.0.0+24 (53b0f5d92 2023-10-19)
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/src/version.rs
|
||||
source: crates/ruff/src/version.rs
|
||||
expression: version
|
||||
---
|
||||
{
|
||||
149
crates/ruff/tests/deprecation.rs
Normal file
149
crates/ruff/tests/deprecation.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
//! A test suite that ensures deprecated command line options have appropriate warnings / behaviors
|
||||
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
use std::process::Command;
|
||||
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
const STDIN: &str = "l = 1";
|
||||
|
||||
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
|
||||
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
|
||||
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
|
||||
cmd.arg("--output-format");
|
||||
cmd.arg(output_format);
|
||||
cmd.arg("--no-cache");
|
||||
match show_source {
|
||||
Some(true) => {
|
||||
cmd.arg("--show-source");
|
||||
}
|
||||
Some(false) => {
|
||||
cmd.arg("--no-show-source");
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
cmd.arg("-");
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_show_source_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), None).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_no_show_source_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), None).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_output_format_is_deprecated() {
|
||||
assert_cmd_snapshot!(ruff_check(None, Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_output_format_overrides_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), Some("concise".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_full_output_format_overrides_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("full".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
|
|
||||
1 | l = 1
|
||||
| ^ E741
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=full`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_output_format_uses_concise_over_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("concise".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_deprecated_output_format_overrides_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(true), Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_deprecated_output_format_overrides_no_show_source() {
|
||||
assert_cmd_snapshot!(ruff_check(Some(false), Some("text".into())).pass_stdin(STDIN), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `l`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `--no-show-source` argument is deprecated and has been ignored in favor of `--output-format=text`.
|
||||
warning: `--output-format=text` is deprecated. Use `--output-format=full` or `--output-format=concise` instead. `text` will be treated as `concise`.
|
||||
"###);
|
||||
}
|
||||
@@ -90,6 +90,179 @@ fn format_warn_stdin_filename_with_files() {
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistent_config_file() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config", "foo.toml", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config", "foo = bar", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_config_files() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config={}` or `--config={}`.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
ruff2_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--config")
|
||||
.arg(&ruff2_dot_toml)
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: The argument `--config={}` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--isolated")
|
||||
.arg(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "line-length = 100")?;
|
||||
let fixture = r#"
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
// This overrides the long line length set in the config file
|
||||
.args(["--config", "line-length=80"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo():
|
||||
print(
|
||||
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
|
||||
)
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "line-length = 70")?;
|
||||
let fixture = r#"
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("format")
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
// This overrides the long line length set in the config file...
|
||||
.args(["--config", "line-length=80"])
|
||||
// ...but this overrides them both:
|
||||
.args(["--line-length", "100"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo():
|
||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -508,6 +681,9 @@ if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
- 'ignore' -> 'lint.ignore'
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -546,6 +722,9 @@ if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
- 'ignore' -> 'lint.ignore'
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1468,3 +1647,392 @@ fn test_notebook_trailing_semicolon() {
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
include = ["*.ipy"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.ipy"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x=1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("format")
|
||||
.arg("--no-cache")
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--extension", "ipy:ipynb"])
|
||||
.arg("."), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_formatting() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2:8-2:14"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_formatting_unicode() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2:21-3"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1="👋🏽" ): print("Format this" )
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(arg1="👋🏽" ):
|
||||
print("Format this")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_formatting_multiple_files() -> std::io::Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let file1 = tempdir.path().join("file1.py");
|
||||
|
||||
fs::write(
|
||||
&file1,
|
||||
r#"
|
||||
def file1(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let file2 = tempdir.path().join("file2.py");
|
||||
|
||||
fs::write(
|
||||
&file2,
|
||||
r#"
|
||||
def file2(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--range=1:8-1:15"])
|
||||
.arg(file1)
|
||||
.arg(file2), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The `--range` option is only supported when formatting a single file but the specified paths resolve to 2 files.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_formatting_out_of_bounds() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=100:40-200:1"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_start_larger_than_end() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=90-50"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value '90-50' for '--range <RANGE>': the start position '90:1' is greater than the end position '50:1'.
|
||||
tip: Try switching start and end: '50:1-90:1'
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_line_numbers_only() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2-3"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Shouldn't format this" )
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_start_only() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=3"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_end_only() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=-3"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Should format this" )
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_missing_line() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=1-:20"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value '1-:20' for '--range <RANGE>': the end line is not a valid number (cannot parse integer from empty string)
|
||||
tip: The format is 'line:column'.
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_line_number() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=0:2"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value '0:2' for '--range <RANGE>': the start line is 0, but it should be 1 or greater.
|
||||
tip: The line numbers start at 1.
|
||||
tip: Try 1:2 instead.
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_and_line_zero() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=0:0"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Should format this" )
|
||||
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value '0:0' for '--range <RANGE>': the start line and column are both 0, but they should be 1 or greater.
|
||||
tip: The line and column numbers start at 1.
|
||||
tip: Try 1:1 instead.
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_formatting_notebook() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--no-cache", "--stdin-filename", "main.ipynb", "--range=1-2"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x=1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to format main.ipynb: Range formatting isn't supported for notebooks.
|
||||
"###);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
923
crates/ruff/tests/lint.rs
Normal file
923
crates/ruff/tests/lint.rs
Normal file
@@ -0,0 +1,923 @@
|
||||
//! Tests the interaction of the `lint` configuration section
|
||||
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use regex::escape;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
|
||||
|
||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that configurations from the top-level and `lint` section are merged together.
|
||||
#[test]
|
||||
fn mixed_levels() -> 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"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that options in the `lint` section have higher precedence than top-level options (because they are more specific).
|
||||
#[test]
|
||||
fn precedence() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
||||
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
||||
"###);
|
||||
});
|
||||
|
||||
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""#)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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 -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
"###);
|
||||
});
|
||||
|
||||
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"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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 -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
"###);
|
||||
});
|
||||
|
||||
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
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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 -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
||||
- 'select' -> 'lint.select'
|
||||
- 'pycodestyle' -> 'lint.pycodestyle'
|
||||
"###);
|
||||
});
|
||||
|
||||
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"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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 -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
"###);
|
||||
});
|
||||
|
||||
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"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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 -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
||||
- 'extend-select' -> 'lint.extend-select'
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for [#8858](https://github.com/astral-sh/ruff/issues/8858)
|
||||
#[test]
|
||||
fn parent_configuration_override() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root_ruff = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
root_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let sub_dir = tempdir.path().join("subdirectory");
|
||||
fs::create_dir(&sub_dir)?;
|
||||
|
||||
let subdirectory_ruff = sub_dir.join("ruff.toml");
|
||||
fs::write(
|
||||
subdirectory_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
ignore = ["D203", "D212"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(sub_dir)
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: No Python files found under the given path(s)
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistent_config_file() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "foo.toml", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
It looks like you were trying to pass a path to a configuration file.
|
||||
The path `foo.toml` does not exist
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "foo = bar", "."]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_config_files() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--config")
|
||||
.arg(&ruff2_dot_toml)
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_and_isolated() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_dot_toml)
|
||||
.arg("--isolated")
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
|
||||
|
||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
||||
as `--isolated` causes ruff to ignore all configuration files.
|
||||
For more information, try `--help`.
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_override_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
line-length = 100
|
||||
|
||||
[lint]
|
||||
select = ["I"]
|
||||
|
||||
[lint.isort]
|
||||
combine-as-imports = true
|
||||
"#,
|
||||
)?;
|
||||
let fixture = r#"
|
||||
from foo import (
|
||||
aaaaaaaaaaaaaaaaaaa,
|
||||
bbbbbbbbbbb as bbbbbbbbbbbbbbbb,
|
||||
cccccccccccccccc,
|
||||
ddddddddddd as ddddddddddddd,
|
||||
eeeeeeeeeeeeeee,
|
||||
ffffffffffff as ffffffffffffff,
|
||||
ggggggggggggg,
|
||||
hhhhhhh as hhhhhhhhhhh,
|
||||
iiiiiiiiiiiiii,
|
||||
jjjjjjjjjjjjj as jjjjjj,
|
||||
)
|
||||
|
||||
x = "longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
||||
"#;
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "line-length=90"])
|
||||
.args(["--config", "lint.extend-select=['E501', 'F841']"])
|
||||
.args(["--config", "lint.isort.combine-as-imports = false"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
-:15:91: E501 Line too long (97 > 90)
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_toml_but_nonexistent_option_provided_via_config_argument() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args([".", "--config", "extend-select=['F481']"]), // No such code as F481!
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F481']' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
Could not parse the supplied argument as a `ruff.toml` configuration option:
|
||||
|
||||
Unknown rule selector: `F481`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_toml_option_requires_a_new_flag_1() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// commas can't be used to delimit different config overrides;
|
||||
// you need a new --config flag for each override
|
||||
.args([".", "--config", "extend-select=['F841'], line-length=90"]),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F841'], line-length=90' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 23
|
||||
|
|
||||
1 | extend-select=['F841'], line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_toml_option_requires_a_new_flag_2() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// spaces *also* can't be used to delimit different config overrides;
|
||||
// you need a new --config flag for each override
|
||||
.args([".", "--config", "extend-select=['F841'] line-length=90"]),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'extend-select=['F841'] line-length=90' for '--config <CONFIG_OPTION>'
|
||||
|
||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
||||
option
|
||||
|
||||
The supplied argument is not valid TOML:
|
||||
|
||||
TOML parse error at line 1, column 24
|
||||
|
|
||||
1 | extend-select=['F841'] line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
line-length = 100
|
||||
|
||||
[lint]
|
||||
select=["E501"]
|
||||
"#,
|
||||
)?;
|
||||
let fixture = "x = 'longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss'";
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
// The --line-length flag takes priority over both the config file
|
||||
// and the `--config="line-length=110"` flag,
|
||||
// despite them both being specified after this flag on the command line:
|
||||
.args(["--line-length", "90"])
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "line-length=110"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:91: E501 Line too long (97 > 90)
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_config_setting_overridden_via_cli() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(&ruff_toml, "lint.select = ['N801']")?;
|
||||
let fixture = "class violates_n801: pass";
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--config", "lint.per-file-ignores = {'generated.py' = ['N801']}"])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(fixture), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_config_option_overridden_via_cli() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "select=['N801']", "-"])
|
||||
.pass_stdin("class lowercase: ..."),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your `--config` CLI arguments:
|
||||
- 'select' -> 'lint.select'
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
include = ["*.ipy"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.ipy"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
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(["--extension", "ipy:ipynb"])
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.ipy:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -39,6 +39,8 @@ fn check_project_include_defaults() {
|
||||
[BASEPATH]/include-test/subdirectory/c.py
|
||||
|
||||
----- stderr -----
|
||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `nested-project/pyproject.toml`:
|
||||
- 'select' -> 'lint.select'
|
||||
"###);
|
||||
});
|
||||
}
|
||||
33
crates/ruff/tests/show_settings.rs
Normal file
33
crates/ruff/tests/show_settings.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[test]
|
||||
fn display_default_settings() {
|
||||
// Navigate from the crate directory to the workspace root.
|
||||
let base_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap();
|
||||
let base_path = base_path.to_string_lossy();
|
||||
|
||||
// Escape the backslashes for the regex.
|
||||
let base_path = regex::escape(&base_path);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let test_filters = &[(base_path.as_ref(), "[BASEPATH]")];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let test_filters = &[
|
||||
(base_path.as_ref(), "[BASEPATH]"),
|
||||
(r#"\\+(\w\w|\s|\.|")"#, "/$1"),
|
||||
];
|
||||
|
||||
insta::with_settings!({ filters => test_filters.to_vec() }, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures")));
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/tests/integration_test.rs
|
||||
source: crates/ruff/tests/integration_test.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -25,6 +25,15 @@ import cycles. They also increase the cognitive load of reading the code.
|
||||
If an import statement is used to check for the availability or existence
|
||||
of a module, consider using `importlib.util.find_spec` instead.
|
||||
|
||||
If an import statement is used to re-export a symbol as part of a module's
|
||||
public interface, consider using a "redundant" import alias, which
|
||||
instructs Ruff (and other tools) to respect the re-export, and avoid
|
||||
marking it as unused, as in:
|
||||
|
||||
```python
|
||||
from module import member as member
|
||||
```
|
||||
|
||||
## Example
|
||||
```python
|
||||
import numpy as np # unused import
|
||||
@@ -51,11 +60,12 @@ else:
|
||||
```
|
||||
|
||||
## Options
|
||||
- `pyflakes.extend-generics`
|
||||
- `lint.ignore-init-module-imports`
|
||||
|
||||
## References
|
||||
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
||||
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
||||
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_cli/tests/integration_test.rs
|
||||
source: crates/ruff/tests/integration_test.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
@@ -0,0 +1,369 @@
|
||||
---
|
||||
source: crates/ruff/tests/show_settings.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- check
|
||||
- "--show-settings"
|
||||
- unformatted.py
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Resolved settings for: "[BASEPATH]/crates/ruff/resources/test/fixtures/unformatted.py"
|
||||
Settings path: "[BASEPATH]/pyproject.toml"
|
||||
|
||||
# General Settings
|
||||
cache_dir = "[BASEPATH]/.ruff_cache"
|
||||
fix = false
|
||||
fix_only = false
|
||||
output_format = concise
|
||||
show_fixes = false
|
||||
unsafe_fixes = hint
|
||||
|
||||
# File Resolver Settings
|
||||
file_resolver.exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".ipynb_checkpoints",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pyenv",
|
||||
".pytest_cache",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
".vscode",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
"venv",
|
||||
]
|
||||
file_resolver.extend_exclude = [
|
||||
"crates/ruff_linter/resources/",
|
||||
"crates/ruff_python_formatter/resources/",
|
||||
]
|
||||
file_resolver.force_exclude = false
|
||||
file_resolver.include = [
|
||||
"*.py",
|
||||
"*.pyi",
|
||||
"**/pyproject.toml",
|
||||
]
|
||||
file_resolver.extend_include = []
|
||||
file_resolver.respect_gitignore = true
|
||||
file_resolver.project_root = "[BASEPATH]"
|
||||
|
||||
# Linter Settings
|
||||
linter.exclude = []
|
||||
linter.project_root = "[BASEPATH]"
|
||||
linter.rules.enabled = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
]
|
||||
linter.rules.should_fix = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
]
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.target_version = Py37
|
||||
linter.preview = disabled
|
||||
linter.explicit_preview_rules = false
|
||||
linter.extension.mapping = {}
|
||||
linter.allowed_confusables = []
|
||||
linter.builtins = []
|
||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||
linter.external = []
|
||||
linter.ignore_init_module_imports = false
|
||||
linter.logger_objects = []
|
||||
linter.namespace_packages = []
|
||||
linter.src = [
|
||||
"[BASEPATH]",
|
||||
]
|
||||
linter.tab_size = 4
|
||||
linter.line_length = 88
|
||||
linter.task_tags = [
|
||||
TODO,
|
||||
FIXME,
|
||||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
linter.flake8_annotations.suppress_dummy_args = false
|
||||
linter.flake8_annotations.suppress_none_returning = false
|
||||
linter.flake8_annotations.allow_star_arg_any = false
|
||||
linter.flake8_annotations.ignore_fully_untyped = false
|
||||
linter.flake8_bandit.hardcoded_tmp_directory = [
|
||||
/tmp,
|
||||
/var/tmp,
|
||||
/dev/shm,
|
||||
]
|
||||
linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.builtins_ignorelist = []
|
||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
]
|
||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||
linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"}
|
||||
linter.flake8_import_conventions.banned_aliases = {}
|
||||
linter.flake8_import_conventions.banned_from = []
|
||||
linter.flake8_pytest_style.fixture_parentheses = true
|
||||
linter.flake8_pytest_style.parametrize_names_type = tuple
|
||||
linter.flake8_pytest_style.parametrize_values_type = list
|
||||
linter.flake8_pytest_style.parametrize_values_row_type = tuple
|
||||
linter.flake8_pytest_style.raises_require_match_for = [
|
||||
BaseException,
|
||||
Exception,
|
||||
ValueError,
|
||||
OSError,
|
||||
IOError,
|
||||
EnvironmentError,
|
||||
socket.error,
|
||||
]
|
||||
linter.flake8_pytest_style.raises_extend_require_match_for = []
|
||||
linter.flake8_pytest_style.mark_parentheses = true
|
||||
linter.flake8_quotes.inline_quotes = double
|
||||
linter.flake8_quotes.multiline_quotes = double
|
||||
linter.flake8_quotes.docstring_quotes = double
|
||||
linter.flake8_quotes.avoid_escape = true
|
||||
linter.flake8_self.ignore_names = [
|
||||
_make,
|
||||
_asdict,
|
||||
_replace,
|
||||
_fields,
|
||||
_field_defaults,
|
||||
_name_,
|
||||
_value_,
|
||||
]
|
||||
linter.flake8_tidy_imports.ban_relative_imports = "parents"
|
||||
linter.flake8_tidy_imports.banned_api = {}
|
||||
linter.flake8_tidy_imports.banned_module_level_imports = []
|
||||
linter.flake8_type_checking.strict = false
|
||||
linter.flake8_type_checking.exempt_modules = [
|
||||
typing,
|
||||
typing_extensions,
|
||||
]
|
||||
linter.flake8_type_checking.runtime_required_base_classes = []
|
||||
linter.flake8_type_checking.runtime_required_decorators = []
|
||||
linter.flake8_type_checking.quote_annotations = false
|
||||
linter.flake8_unused_arguments.ignore_variadic_names = false
|
||||
linter.isort.required_imports = []
|
||||
linter.isort.combine_as_imports = false
|
||||
linter.isort.force_single_line = false
|
||||
linter.isort.force_sort_within_sections = false
|
||||
linter.isort.detect_same_package = true
|
||||
linter.isort.case_sensitive = false
|
||||
linter.isort.force_wrap_aliases = false
|
||||
linter.isort.force_to_top = []
|
||||
linter.isort.known_modules = {}
|
||||
linter.isort.order_by_type = true
|
||||
linter.isort.relative_imports_order = furthest_to_closest
|
||||
linter.isort.single_line_exclusions = []
|
||||
linter.isort.split_on_trailing_comma = true
|
||||
linter.isort.classes = []
|
||||
linter.isort.constants = []
|
||||
linter.isort.variables = []
|
||||
linter.isort.no_lines_before = []
|
||||
linter.isort.lines_after_imports = -1
|
||||
linter.isort.lines_between_types = 0
|
||||
linter.isort.forced_separate = []
|
||||
linter.isort.section_order = [
|
||||
known { type = future },
|
||||
known { type = standard_library },
|
||||
known { type = third_party },
|
||||
known { type = first_party },
|
||||
known { type = local_folder },
|
||||
]
|
||||
linter.isort.no_sections = false
|
||||
linter.isort.from_first = false
|
||||
linter.isort.length_sort = false
|
||||
linter.isort.length_sort_straight = false
|
||||
linter.mccabe.max_complexity = 10
|
||||
linter.pep8_naming.ignore_names = [
|
||||
setUp,
|
||||
tearDown,
|
||||
setUpClass,
|
||||
tearDownClass,
|
||||
setUpModule,
|
||||
tearDownModule,
|
||||
asyncSetUp,
|
||||
asyncTearDown,
|
||||
setUpTestData,
|
||||
failureException,
|
||||
longMessage,
|
||||
maxDiff,
|
||||
]
|
||||
linter.pep8_naming.classmethod_decorators = []
|
||||
linter.pep8_naming.staticmethod_decorators = []
|
||||
linter.pycodestyle.max_line_length = 88
|
||||
linter.pycodestyle.max_doc_length = none
|
||||
linter.pycodestyle.ignore_overlong_task_comments = false
|
||||
linter.pyflakes.extend_generics = []
|
||||
linter.pylint.allow_magic_value_types = [
|
||||
str,
|
||||
bytes,
|
||||
]
|
||||
linter.pylint.allow_dunder_method_names = []
|
||||
linter.pylint.max_args = 5
|
||||
linter.pylint.max_positional_args = 5
|
||||
linter.pylint.max_returns = 6
|
||||
linter.pylint.max_bool_expr = 5
|
||||
linter.pylint.max_branches = 12
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
formatter.target_version = Py37
|
||||
formatter.preview = disabled
|
||||
formatter.line_width = 88
|
||||
formatter.line_ending = auto
|
||||
formatter.indent_style = space
|
||||
formatter.indent_width = 4
|
||||
formatter.quote_style = double
|
||||
formatter.magic_trailing_comma = respect
|
||||
formatter.docstring_code_format = disabled
|
||||
formatter.docstring_code_line_width = dynamic
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -13,6 +13,7 @@ license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
doctest = false
|
||||
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Ruff Benchmarks
|
||||
|
||||
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
|
||||
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files:
|
||||
|
||||
```shell
|
||||
# Run once on the "baseline".
|
||||
cargo bench -p ruff_benchmark -- --save-baseline=main
|
||||
|
||||
# Compare against the "baseline".
|
||||
cargo bench -p ruff_benchmark -- --baseline=main
|
||||
|
||||
# Run the lexer benchmarks.
|
||||
cargo bench -p ruff_benchmark lexer -- --baseline=main
|
||||
```
|
||||
|
||||
See [CONTRIBUTING.md](../../CONTRIBUTING.md) on how to use these benchmarks.
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_python_formatter::{format_module_ast, PreviewMode, PyFormatOptions};
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
use ruff_python_parser::{allocate_tokens_vec, parse_tokens, Mode};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
@@ -52,7 +52,7 @@ fn benchmark_formatter(criterion: &mut Criterion) {
|
||||
BenchmarkId::from_parameter(case.name()),
|
||||
&case,
|
||||
|b, case| {
|
||||
let mut tokens = Vec::new();
|
||||
let mut tokens = allocate_tokens_vec(case.code());
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for result in lex(case.code(), Mode::Module) {
|
||||
|
||||
@@ -55,7 +55,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
&case,
|
||||
|b, case| {
|
||||
// Tokenize the source.
|
||||
let tokens = lexer::lex(case.code(), Mode::Module).collect::<Vec<_>>();
|
||||
let tokens: Vec<_> = lexer::lex(case.code(), Mode::Module).collect();
|
||||
|
||||
// Parse the source.
|
||||
let ast = parse_program_tokens(tokens.clone(), case.code(), false).unwrap();
|
||||
|
||||
@@ -1,762 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_linter::line_width::LineLength;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::{
|
||||
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
|
||||
SerializationFormat, UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::options::PycodestyleOptions;
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
author,
|
||||
name = "ruff",
|
||||
about = "Ruff: An extremely fast Python linter.",
|
||||
after_help = "For help with a specific command, see: `ruff help <command>`."
|
||||
)]
|
||||
#[command(version)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
#[clap(flatten)]
|
||||
pub log_level_args: LogLevelArgs,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
/// Run Ruff on the given files or directories (default).
|
||||
Check(CheckCommand),
|
||||
/// Explain a rule (or all rules).
|
||||
#[clap(alias = "--explain")]
|
||||
#[command(group = clap::ArgGroup::new("selector").multiple(false).required(true))]
|
||||
Rule {
|
||||
/// Rule to explain
|
||||
#[arg(value_parser=RuleParser, group = "selector", hide_possible_values = true)]
|
||||
rule: Option<Rule>,
|
||||
|
||||
/// Explain all rules
|
||||
#[arg(long, conflicts_with = "rule", group = "selector")]
|
||||
all: bool,
|
||||
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
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.
|
||||
Config { option: Option<String> },
|
||||
/// List all supported upstream linters.
|
||||
Linter {
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
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.
|
||||
#[clap(alias = "--clean")]
|
||||
Clean,
|
||||
/// Generate shell completion.
|
||||
#[clap(alias = "--generate-shell-completion", hide = true)]
|
||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
/// Run the Ruff formatter on the given files or directories.
|
||||
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
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CheckCommand {
|
||||
/// List of files or directories to check.
|
||||
#[clap(help = "List of files or directories to check [default: .]")]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Apply fixes to resolve lint violations.
|
||||
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||
#[arg(long, overrides_with("no_fix"))]
|
||||
fix: bool,
|
||||
#[clap(long, overrides_with("fix"), hide = true)]
|
||||
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.
|
||||
/// Use `--no-show-source` to disable.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
show_source: bool,
|
||||
#[clap(long, overrides_with("show_source"), hide = true)]
|
||||
no_show_source: bool,
|
||||
/// Show an enumeration of all fixed lint violations.
|
||||
/// Use `--no-show-fixes` to disable.
|
||||
#[arg(long, overrides_with("no_show_fixes"))]
|
||||
show_fixes: bool,
|
||||
#[clap(long, overrides_with("show_fixes"), hide = true)]
|
||||
no_show_fixes: bool,
|
||||
/// Avoid writing any fixed files back; instead, output a diff for each changed file to stdout. Implies `--fix-only`.
|
||||
#[arg(long, conflicts_with = "show_fixes")]
|
||||
pub diff: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
pub watch: bool,
|
||||
/// Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`.
|
||||
/// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||
#[arg(long, overrides_with("no_fix_only"))]
|
||||
fix_only: bool,
|
||||
#[clap(long, overrides_with("fix_only"), hide = true)]
|
||||
no_fix_only: bool,
|
||||
/// Ignore any `# noqa` comments.
|
||||
#[arg(long)]
|
||||
ignore_noqa: bool,
|
||||
|
||||
/// Output serialization format for violations.
|
||||
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
|
||||
/// Specify file to write the linter output to (default: stdout).
|
||||
#[arg(short, long)]
|
||||
pub output_file: Option<PathBuf>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, value_enum)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Enable preview mode; checks will include unstable rules and fixes.
|
||||
/// Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||
/// configuration.
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub select: Option<Vec<RuleSelector>>,
|
||||
/// Comma-separated list of rule codes to disable.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub ignore: Option<Vec<RuleSelector>>,
|
||||
/// Like --select, but adds additional rule codes on top of those already specified.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub extend_select: Option<Vec<RuleSelector>>,
|
||||
/// Like --ignore. (Deprecated: You can just use --ignore instead.)
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
pub extend_ignore: Option<Vec<RuleSelector>>,
|
||||
/// List of mappings from file pattern to code to exclude.
|
||||
#[arg(long, value_delimiter = ',', help_heading = "Rule selection")]
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
/// Like `--per-file-ignores`, but adds additional ignores on top of those already specified.
|
||||
#[arg(long, value_delimiter = ',', help_heading = "Rule selection")]
|
||||
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
/// 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>>,
|
||||
/// Like --exclude, but adds additional files and directories on top of those already excluded.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "FILE_PATTERN",
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
/// List of rule codes to treat as eligible for fix. Only applicable when fix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub fixable: Option<Vec<RuleSelector>>,
|
||||
/// List of rule codes to treat as ineligible for fix. Only applicable when fix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub unfixable: Option<Vec<RuleSelector>>,
|
||||
/// Like --fixable, but adds additional rule codes on top of those already specified.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub extend_fixable: Option<Vec<RuleSelector>>,
|
||||
/// Like --unfixable. (Deprecated: You can just use --unfixable instead.)
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
pub extend_unfixable: Option<Vec<RuleSelector>>,
|
||||
/// Respect file exclusions via `.gitignore` and other standard ignore files.
|
||||
/// Use `--no-respect-gitignore` to disable.
|
||||
#[arg(
|
||||
long,
|
||||
overrides_with("no_respect_gitignore"),
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
no_respect_gitignore: bool,
|
||||
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
|
||||
/// Use `--no-force-exclude` to disable.
|
||||
#[arg(
|
||||
long,
|
||||
overrides_with("no_force_exclude"),
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
force_exclude: bool,
|
||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||
no_force_exclude: bool,
|
||||
/// Set the line-length for length-associated rules and automatic formatting.
|
||||
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
||||
pub line_length: Option<LineLength>,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
pub no_cache: bool,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Exit with status code "0", even upon detecting lint violations.
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help_heading = "Miscellaneous",
|
||||
conflicts_with = "exit_non_zero_on_fix"
|
||||
)]
|
||||
pub exit_zero: bool,
|
||||
/// Exit with a non-zero status code if any files were modified via fix, even if no lint violations remain.
|
||||
#[arg(long, help_heading = "Miscellaneous", conflicts_with = "exit_zero")]
|
||||
pub exit_non_zero_on_fix: bool,
|
||||
/// Show counts for every rule with at least one violation.
|
||||
#[arg(
|
||||
long,
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "diff",
|
||||
conflicts_with = "show_source",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub statistics: bool,
|
||||
/// Enable automatic additions of `noqa` directives to failing lines.
|
||||
#[arg(
|
||||
long,
|
||||
// conflicts_with = "add_noqa",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "ignore_noqa",
|
||||
conflicts_with = "statistics",
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
conflicts_with = "fix",
|
||||
)]
|
||||
pub add_noqa: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
// conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "ignore_noqa",
|
||||
conflicts_with = "statistics",
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_files: bool,
|
||||
/// See the settings Ruff will use to lint a given Python file.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
conflicts_with = "add_noqa",
|
||||
conflicts_with = "show_files",
|
||||
// conflicts_with = "show_settings",
|
||||
// Unsupported default-command arguments.
|
||||
conflicts_with = "ignore_noqa",
|
||||
conflicts_with = "statistics",
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
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
|
||||
#[arg(long, hide = true)]
|
||||
pub ecosystem_ci: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct FormatCommand {
|
||||
/// List of files or directories to format.
|
||||
#[clap(help = "List of files or directories to format [default: .]")]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Avoid writing any formatted files back; instead, exit with a non-zero status code if any
|
||||
/// files would have been modified, and zero otherwise.
|
||||
#[arg(long)]
|
||||
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.
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
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.
|
||||
/// Use `--no-respect-gitignore` to disable.
|
||||
#[arg(
|
||||
long,
|
||||
overrides_with("no_respect_gitignore"),
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
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.
|
||||
/// Use `--no-force-exclude` to disable.
|
||||
#[arg(
|
||||
long,
|
||||
overrides_with("no_force_exclude"),
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
force_exclude: bool,
|
||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||
no_force_exclude: bool,
|
||||
/// Set the line-length.
|
||||
#[arg(long, help_heading = "Format configuration")]
|
||||
pub line_length: Option<LineLength>,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||
pub isolated: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, value_enum)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Enable preview mode; enables unstable formatting.
|
||||
/// Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub enum HelpFormat {
|
||||
Text,
|
||||
Json,
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub struct LogLevelArgs {
|
||||
/// Enable verbose logging.
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
global = true,
|
||||
group = "verbosity",
|
||||
help_heading = "Log levels"
|
||||
)]
|
||||
pub verbose: bool,
|
||||
/// Print diagnostics, but nothing else.
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
global = true,
|
||||
group = "verbosity",
|
||||
help_heading = "Log levels"
|
||||
)]
|
||||
pub quiet: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting diagnostics).
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
global = true,
|
||||
group = "verbosity",
|
||||
help_heading = "Log levels"
|
||||
)]
|
||||
pub silent: bool,
|
||||
}
|
||||
|
||||
impl From<&LogLevelArgs> for LogLevel {
|
||||
fn from(args: &LogLevelArgs) -> Self {
|
||||
if args.silent {
|
||||
Self::Silent
|
||||
} else if args.quiet {
|
||||
Self::Quiet
|
||||
} else if args.verbose {
|
||||
Self::Verbose
|
||||
} else {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> (CheckArguments, CliOverrides) {
|
||||
(
|
||||
CheckArguments {
|
||||
add_noqa: self.add_noqa,
|
||||
config: self.config,
|
||||
diff: self.diff,
|
||||
ecosystem_ci: self.ecosystem_ci,
|
||||
exit_non_zero_on_fix: self.exit_non_zero_on_fix,
|
||||
exit_zero: self.exit_zero,
|
||||
files: self.files,
|
||||
ignore_noqa: self.ignore_noqa,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
output_file: self.output_file,
|
||||
show_files: self.show_files,
|
||||
show_settings: self.show_settings,
|
||||
statistics: self.statistics,
|
||||
stdin_filename: self.stdin_filename,
|
||||
watch: self.watch,
|
||||
},
|
||||
CliOverrides {
|
||||
dummy_variable_rgx: self.dummy_variable_rgx,
|
||||
exclude: self.exclude,
|
||||
extend_exclude: self.extend_exclude,
|
||||
extend_fixable: self.extend_fixable,
|
||||
extend_ignore: self.extend_ignore,
|
||||
extend_per_file_ignores: self.extend_per_file_ignores,
|
||||
extend_select: self.extend_select,
|
||||
extend_unfixable: self.extend_unfixable,
|
||||
fixable: self.fixable,
|
||||
ignore: self.ignore,
|
||||
line_length: self.line_length,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
select: self.select,
|
||||
show_source: resolve_bool_arg(self.show_source, self.no_show_source),
|
||||
target_version: self.target_version,
|
||||
unfixable: self.unfixable,
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
cache_dir: self.cache_dir,
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
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),
|
||||
output_format: self.output_format,
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
extension: self.extension,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatCommand {
|
||||
/// Partition the CLI into command-line arguments and configuration
|
||||
/// overrides.
|
||||
pub fn partition(self) -> (FormatArguments, CliOverrides) {
|
||||
(
|
||||
FormatArguments {
|
||||
check: self.check,
|
||||
diff: self.diff,
|
||||
config: self.config,
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
stdin_filename: self.stdin_filename,
|
||||
},
|
||||
CliOverrides {
|
||||
line_length: self.line_length,
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
exclude: self.exclude,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
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`.
|
||||
..CliOverrides::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
match (yes, no) {
|
||||
(true, false) => Some(true),
|
||||
(false, true) => Some(false),
|
||||
(false, false) => None,
|
||||
(..) => unreachable!("Clap should make this impossible"),
|
||||
}
|
||||
}
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
/// etc.).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CheckArguments {
|
||||
pub add_noqa: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub diff: bool,
|
||||
pub ecosystem_ci: bool,
|
||||
pub exit_non_zero_on_fix: bool,
|
||||
pub exit_zero: bool,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub ignore_noqa: bool,
|
||||
pub isolated: bool,
|
||||
pub no_cache: bool,
|
||||
pub output_file: Option<PathBuf>,
|
||||
pub show_files: bool,
|
||||
pub show_settings: bool,
|
||||
pub statistics: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
pub watch: bool,
|
||||
}
|
||||
|
||||
/// CLI settings that are distinct from configuration (commands, lists of files,
|
||||
/// etc.).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct FormatArguments {
|
||||
pub check: bool,
|
||||
pub no_cache: bool,
|
||||
pub diff: bool,
|
||||
pub config: Option<PathBuf>,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub isolated: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
#[derive(Clone, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CliOverrides {
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_fixable: Option<Vec<RuleSelector>>,
|
||||
pub extend_ignore: Option<Vec<RuleSelector>>,
|
||||
pub extend_select: Option<Vec<RuleSelector>>,
|
||||
pub extend_unfixable: Option<Vec<RuleSelector>>,
|
||||
pub fixable: Option<Vec<RuleSelector>>,
|
||||
pub ignore: Option<Vec<RuleSelector>>,
|
||||
pub line_length: Option<LineLength>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub respect_gitignore: Option<bool>,
|
||||
pub select: Option<Vec<RuleSelector>>,
|
||||
pub show_source: Option<bool>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
pub unfixable: Option<Vec<RuleSelector>>,
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub show_fixes: Option<bool>,
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for CliOverrides {
|
||||
fn transform(&self, mut config: Configuration) -> Configuration {
|
||||
if let Some(cache_dir) = &self.cache_dir {
|
||||
config.cache_dir = Some(cache_dir.clone());
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = &self.dummy_variable_rgx {
|
||||
config.lint.dummy_variable_rgx = Some(dummy_variable_rgx.clone());
|
||||
}
|
||||
if let Some(exclude) = &self.exclude {
|
||||
config.exclude = Some(exclude.clone());
|
||||
}
|
||||
if let Some(extend_exclude) = &self.extend_exclude {
|
||||
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 {
|
||||
config.fix = Some(*fix);
|
||||
}
|
||||
if let Some(fix_only) = &self.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 {
|
||||
select: self.select.clone(),
|
||||
ignore: self
|
||||
.ignore
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.extend_ignore.iter().cloned())
|
||||
.flatten()
|
||||
.collect(),
|
||||
extend_select: self.extend_select.clone().unwrap_or_default(),
|
||||
fixable: self.fixable.clone(),
|
||||
unfixable: self
|
||||
.unfixable
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.extend_unfixable.iter().cloned())
|
||||
.flatten()
|
||||
.collect(),
|
||||
extend_fixable: self.extend_fixable.clone().unwrap_or_default(),
|
||||
});
|
||||
if let Some(output_format) = &self.output_format {
|
||||
config.output_format = Some(*output_format);
|
||||
}
|
||||
if let Some(force_exclude) = &self.force_exclude {
|
||||
config.force_exclude = Some(*force_exclude);
|
||||
}
|
||||
if let Some(line_length) = self.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 {
|
||||
config.preview = Some(*preview);
|
||||
config.lint.preview = Some(*preview);
|
||||
config.format.preview = Some(*preview);
|
||||
}
|
||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||
}
|
||||
if let Some(respect_gitignore) = &self.respect_gitignore {
|
||||
config.respect_gitignore = Some(*respect_gitignore);
|
||||
}
|
||||
if let Some(show_source) = &self.show_source {
|
||||
config.show_source = Some(*show_source);
|
||||
}
|
||||
if let Some(show_fixes) = &self.show_fixes {
|
||||
config.show_fixes = Some(*show_fixes);
|
||||
}
|
||||
if let Some(target_version) = &self.target_version {
|
||||
config.target_version = Some(*target_version);
|
||||
}
|
||||
if let Some(extension) = &self.extension {
|
||||
config.lint.extension = Some(extension.clone().into_iter().collect());
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
.or_default()
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
.into_iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes, None))
|
||||
.collect()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_cli/src/version.rs
|
||||
expression: version
|
||||
---
|
||||
0.0.0
|
||||
@@ -1,438 +0,0 @@
|
||||
//! Tests the interaction of the `lint` configuration section
|
||||
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "text"];
|
||||
|
||||
#[test]
|
||||
fn top_level_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
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#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that configurations from the top-level and `lint` section are merged together.
|
||||
#[test]
|
||||
fn mixed_levels() -> 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))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that options in the `lint` section have higher precedence than top-level options (because they are more specific).
|
||||
#[test]
|
||||
fn precedence() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 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 -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for [#8858](https://github.com/astral-sh/ruff/issues/8858)
|
||||
#[test]
|
||||
fn parent_configuration_override() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root_ruff = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
root_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let sub_dir = tempdir.path().join("subdirectory");
|
||||
fs::create_dir(&sub_dir)?;
|
||||
|
||||
let subdirectory_ruff = sub_dir.join("ruff.toml");
|
||||
fs::write(
|
||||
subdirectory_ruff,
|
||||
r#"
|
||||
[lint]
|
||||
ignore = ["D203", "D212"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(sub_dir)
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: No Python files found under the given path(s)
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -11,21 +11,21 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff_linter = { path = "../ruff_linter", features = ["schemars"] }
|
||||
ruff_cli = { path = "../ruff_cli" }
|
||||
ruff = { path = "../ruff" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics" }
|
||||
ruff_formatter = { path = "../ruff_formatter" }
|
||||
ruff_linter = { path = "../ruff_linter", features = ["schemars"] }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help"] }
|
||||
ignore = { workspace = true }
|
||||
imara-diff = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
|
||||
@@ -27,8 +27,8 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use ruff_cli::args::{FormatCommand, LogLevelArgs};
|
||||
use ruff_cli::resolve::resolve;
|
||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs};
|
||||
use ruff::resolve::resolve;
|
||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
@@ -38,24 +38,23 @@ use ruff_python_formatter::{
|
||||
use ruff_python_parser::ParseError;
|
||||
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`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths(
|
||||
dirs: &[PathBuf],
|
||||
) -> anyhow::Result<(
|
||||
Vec<Result<ResolvedFile, ignore::Error>>,
|
||||
Resolver,
|
||||
PyprojectConfig,
|
||||
)> {
|
||||
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
||||
let args_matches = FormatCommand::command()
|
||||
.no_binary_name(true)
|
||||
.get_matches_from(dirs);
|
||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||
let (cli, overrides) = arguments.partition();
|
||||
let (cli, config_arguments) = arguments.partition()?;
|
||||
Ok((cli, config_arguments))
|
||||
}
|
||||
|
||||
/// Find the [`PyprojectConfig`] to use for formatting.
|
||||
fn find_pyproject_config(
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> anyhow::Result<PyprojectConfig> {
|
||||
let mut pyproject_config = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
config_arguments,
|
||||
cli.stdin_filename.as_deref(),
|
||||
)?;
|
||||
// We don't want to format pyproject.toml
|
||||
@@ -64,11 +63,18 @@ fn ruff_check_paths(
|
||||
FilePattern::Builtin("*.pyi"),
|
||||
])
|
||||
.unwrap();
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, &overrides)?;
|
||||
if paths.is_empty() {
|
||||
bail!("no python files in {:?}", dirs)
|
||||
}
|
||||
Ok((paths, resolver, pyproject_config))
|
||||
Ok(pyproject_config)
|
||||
}
|
||||
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths<'a>(
|
||||
pyproject_config: &'a PyprojectConfig,
|
||||
cli: &FormatArguments,
|
||||
config_arguments: &ConfigArguments,
|
||||
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, config_arguments)?;
|
||||
Ok((paths, resolver))
|
||||
}
|
||||
|
||||
/// Collects statistics over the formatted files to compute the Jaccard index or the similarity
|
||||
@@ -216,6 +222,7 @@ pub(crate) struct Args {
|
||||
#[arg(long)]
|
||||
pub(crate) files_with_errors: Option<u32>,
|
||||
#[clap(flatten)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub(crate) log_level_args: LogLevelArgs,
|
||||
}
|
||||
|
||||
@@ -279,7 +286,7 @@ fn setup_logging(log_level_args: &LogLevelArgs, log_file: Option<&Path>) -> io::
|
||||
LogLevel::Quiet => tracing::Level::WARN,
|
||||
LogLevel::Silent => tracing::Level::ERROR,
|
||||
};
|
||||
// 1. `RUST_LOG=`, 2. explicit CLI log level, 3. info, the ruff_cli default
|
||||
// 1. `RUST_LOG=`, 2. explicit CLI log level, 3. info, the ruff default
|
||||
let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(log_level.into())
|
||||
@@ -451,11 +458,17 @@ fn format_dev_project(
|
||||
files[0].display()
|
||||
);
|
||||
|
||||
// TODO(konstin): black excludes
|
||||
// TODO(konstin): Respect black's excludes.
|
||||
|
||||
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
||||
// Find files to check (or in this case, format twice). Adapted from ruff
|
||||
// First argument is ignored
|
||||
let (paths, resolver, pyproject_config) = ruff_check_paths(files)?;
|
||||
let (cli, overrides) = parse_cli(files)?;
|
||||
let pyproject_config = find_pyproject_config(&cli, &overrides)?;
|
||||
let (paths, resolver) = ruff_check_paths(&pyproject_config, &cli, &overrides)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
bail!("No Python files found under the given path(s)");
|
||||
}
|
||||
|
||||
let results = {
|
||||
let pb_span =
|
||||
@@ -468,14 +481,7 @@ fn format_dev_project(
|
||||
#[cfg(feature = "singlethreaded")]
|
||||
let iter = { paths.into_iter() };
|
||||
iter.map(|path| {
|
||||
let result = format_dir_entry(
|
||||
path,
|
||||
stability_check,
|
||||
write,
|
||||
&black_options,
|
||||
&resolver,
|
||||
&pyproject_config,
|
||||
);
|
||||
let result = format_dir_entry(path, stability_check, write, &black_options, &resolver);
|
||||
pb_span.pb_inc(1);
|
||||
result
|
||||
})
|
||||
@@ -525,14 +531,13 @@ 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(
|
||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
options: &BlackOptions,
|
||||
resolver: &Resolver,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||
// For some reason it does not filter in the beginning
|
||||
@@ -543,7 +548,7 @@ fn format_dir_entry(
|
||||
let path = resolved_file.into_path();
|
||||
let mut options = options.to_py_format_options(&path);
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
let settings = resolver.resolve(&path);
|
||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||
if settings.formatter.line_width != LineWidth::default() {
|
||||
options = options.with_line_width(settings.formatter.line_width);
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::{bail, Context, Result};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use ruff_cli::args;
|
||||
use ruff::args;
|
||||
|
||||
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
|
||||
use crate::ROOT_DIR;
|
||||
@@ -114,12 +114,15 @@ pub(super) fn main(args: &Args) -> Result<()> {
|
||||
|
||||
/// Returns the output of `ruff help`.
|
||||
fn help_text() -> String {
|
||||
args::Args::command().render_help().to_string()
|
||||
args::Args::command()
|
||||
.term_width(79)
|
||||
.render_help()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Returns the output of a given subcommand (e.g., `ruff help check`).
|
||||
fn subcommand_help_text(subcommand: &str) -> Result<String> {
|
||||
let mut cmd = args::Args::command();
|
||||
let mut cmd = args::Args::command().term_width(79);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -26,9 +26,9 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
for rule in Rule::iter() {
|
||||
if let Some(explanation) = rule.explanation() {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||
if linter.url().is_some() {
|
||||
@@ -37,6 +37,22 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_deprecated() {
|
||||
output.push_str(
|
||||
r"**Warning: This rule is deprecated and will be removed in a future release.**",
|
||||
);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_removed() {
|
||||
output.push_str(
|
||||
r"**Warning: This rule has been removed and its documentation is only available for historical reasons.**",
|
||||
);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let fix_availability = rule.fixable();
|
||||
if matches!(
|
||||
fix_availability,
|
||||
@@ -116,7 +132,7 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
|
||||
}
|
||||
}
|
||||
|
||||
let anchor = option.replace('.', "-");
|
||||
let anchor = option.replace('.', "_");
|
||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
|
||||
|
||||
@@ -142,13 +158,13 @@ mod tests {
|
||||
let mut output = String::new();
|
||||
process_documentation(
|
||||
"
|
||||
See also [`mccabe.max-complexity`] and [`task-tags`].
|
||||
See also [`lint.mccabe.max-complexity`] and [`lint.task-tags`].
|
||||
Something [`else`][other].
|
||||
|
||||
## Options
|
||||
|
||||
- `task-tags`
|
||||
- `mccabe.max-complexity`
|
||||
- `lint.task-tags`
|
||||
- `lint.mccabe.max-complexity`
|
||||
|
||||
[other]: http://example.com.",
|
||||
&mut output,
|
||||
@@ -157,18 +173,18 @@ Something [`else`][other].
|
||||
assert_eq!(
|
||||
output,
|
||||
"
|
||||
See also [`mccabe.max-complexity`][mccabe.max-complexity] and [`task-tags`][task-tags].
|
||||
See also [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity] and [`lint.task-tags`][lint.task-tags].
|
||||
Something [`else`][other].
|
||||
|
||||
## Options
|
||||
|
||||
- [`task-tags`][task-tags]
|
||||
- [`mccabe.max-complexity`][mccabe.max-complexity]
|
||||
- [`lint.task-tags`][lint.task-tags]
|
||||
- [`lint.mccabe.max-complexity`][lint.mccabe.max-complexity]
|
||||
|
||||
[other]: http://example.com.
|
||||
|
||||
[task-tags]: ../settings.md#task-tags
|
||||
[mccabe.max-complexity]: ../settings.md#mccabe-max-complexity
|
||||
[lint.task-tags]: ../settings.md#lint_task-tags
|
||||
[lint.mccabe.max-complexity]: ../settings.md#lint_mccabe_max-complexity
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
//!
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruff_python_trivia::textwrap;
|
||||
@@ -9,16 +10,29 @@ use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visi
|
||||
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
generate_set(&mut output, &Set::Toplevel(Options::metadata()));
|
||||
|
||||
generate_set(
|
||||
&mut output,
|
||||
Set::Toplevel(Options::metadata()),
|
||||
&mut Vec::new(),
|
||||
);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_set(output: &mut String, set: &Set) {
|
||||
if set.level() < 2 {
|
||||
writeln!(output, "### {title}\n", title = set.title()).unwrap();
|
||||
} else {
|
||||
writeln!(output, "#### {title}\n", title = set.title()).unwrap();
|
||||
fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||
match &set {
|
||||
Set::Toplevel(_) => {
|
||||
output.push_str("### Top-level\n");
|
||||
}
|
||||
Set::Named { name, .. } => {
|
||||
let title = parents
|
||||
.iter()
|
||||
.filter_map(|set| set.name())
|
||||
.chain(std::iter::once(name.as_str()))
|
||||
.join(".");
|
||||
writeln!(output, "#### `{title}`\n",).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(documentation) = set.metadata().documentation() {
|
||||
@@ -35,72 +49,68 @@ fn generate_set(output: &mut String, set: &Set) {
|
||||
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
|
||||
parents.push(set);
|
||||
|
||||
// Generate the fields.
|
||||
for (name, field) in &fields {
|
||||
emit_field(output, name, field, set);
|
||||
emit_field(output, name, field, parents.as_slice());
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
// Generate all the sub-sets.
|
||||
for (set_name, sub_set) in &sets {
|
||||
generate_set(output, &Set::Named(set_name, *sub_set, set.level() + 1));
|
||||
generate_set(
|
||||
output,
|
||||
Set::Named {
|
||||
name: set_name.to_string(),
|
||||
set: *sub_set,
|
||||
},
|
||||
parents,
|
||||
);
|
||||
}
|
||||
|
||||
parents.pop();
|
||||
}
|
||||
|
||||
enum Set<'a> {
|
||||
enum Set {
|
||||
Toplevel(OptionSet),
|
||||
Named(&'a str, OptionSet, u32),
|
||||
Named { name: String, set: OptionSet },
|
||||
}
|
||||
|
||||
impl<'a> Set<'a> {
|
||||
fn name(&self) -> Option<&'a str> {
|
||||
impl Set {
|
||||
fn name(&self) -> Option<&str> {
|
||||
match self {
|
||||
Set::Toplevel(_) => None,
|
||||
Set::Named(name, _, _) => Some(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> &'a str {
|
||||
match self {
|
||||
Set::Toplevel(_) => "Top-level",
|
||||
Set::Named(name, _, _) => name,
|
||||
Set::Named { name, .. } => Some(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &OptionSet {
|
||||
match self {
|
||||
Set::Toplevel(set) => set,
|
||||
Set::Named(_, set, _) => set,
|
||||
}
|
||||
}
|
||||
|
||||
fn level(&self) -> u32 {
|
||||
match self {
|
||||
Set::Toplevel(_) => 0,
|
||||
Set::Named(_, _, level) => *level,
|
||||
Set::Named { set, .. } => set,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set: &Set) {
|
||||
let header_level = if parent_set.level() < 2 {
|
||||
"####"
|
||||
} else {
|
||||
"#####"
|
||||
};
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
|
||||
let header_level = if parents.is_empty() { "####" } else { "#####" };
|
||||
let parents_anchor = parents.iter().filter_map(|parent| parent.name()).join("_");
|
||||
|
||||
if parents_anchor.is_empty() {
|
||||
output.push_str(&format!(
|
||||
"{header_level} [`{name}`](#{name}) {{: #{name} }}\n"
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format!(
|
||||
"{header_level} [`{name}`](#{parents_anchor}_{name}) {{: #{parents_anchor}_{name} }}\n"
|
||||
));
|
||||
|
||||
// if there's a set name, we need to add it to the anchor
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
// the anchor used to just be the name, but now it's the group name
|
||||
// for backwards compatibility, we need to keep the old anchor
|
||||
output.push_str(&format!("<span id=\"{name}\"></span>\n"));
|
||||
|
||||
output.push_str(&format!(
|
||||
"{header_level} [`{name}`](#{set_name}-{name}) {{: #{set_name}-{name} }}\n"
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
@@ -129,12 +139,12 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str(&format_tab(
|
||||
"pyproject.toml",
|
||||
&format_header(field.scope, parent_set, ConfigurationFile::PyprojectToml),
|
||||
&format_header(field.scope, parents, ConfigurationFile::PyprojectToml),
|
||||
field.example,
|
||||
));
|
||||
output.push_str(&format_tab(
|
||||
"ruff.toml",
|
||||
&format_header(field.scope, parent_set, ConfigurationFile::RuffToml),
|
||||
&format_header(field.scope, parents, ConfigurationFile::RuffToml),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
@@ -152,52 +162,22 @@ fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
scope: Option<&str>,
|
||||
parent_set: &Set,
|
||||
configuration: ConfigurationFile,
|
||||
) -> String {
|
||||
match configuration {
|
||||
ConfigurationFile::PyprojectToml => {
|
||||
let mut header = if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from("tool.ruff.format")
|
||||
} else {
|
||||
format!("tool.ruff.lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
"tool.ruff".to_string()
|
||||
};
|
||||
if let Some(scope) = scope {
|
||||
if !header.is_empty() {
|
||||
header.push('.');
|
||||
}
|
||||
header.push_str(scope);
|
||||
}
|
||||
format!("[{header}]")
|
||||
}
|
||||
ConfigurationFile::RuffToml => {
|
||||
let mut header = if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from("format")
|
||||
} else {
|
||||
format!("lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if let Some(scope) = scope {
|
||||
if !header.is_empty() {
|
||||
header.push('.');
|
||||
}
|
||||
header.push_str(scope);
|
||||
}
|
||||
if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
fn format_header(scope: Option<&str>, parents: &[Set], configuration: ConfigurationFile) -> String {
|
||||
let tool_parent = match configuration {
|
||||
ConfigurationFile::PyprojectToml => Some("tool.ruff"),
|
||||
ConfigurationFile::RuffToml => None,
|
||||
};
|
||||
|
||||
let header = tool_parent
|
||||
.into_iter()
|
||||
.chain(parents.iter().filter_map(|parent| parent.name()))
|
||||
.chain(scope)
|
||||
.join(".");
|
||||
|
||||
if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Used for <https://docs.astral.sh/ruff/rules/>.
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_linter::codes::RuleGroup;
|
||||
use std::borrow::Cow;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@@ -14,6 +15,10 @@ use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
const FIX_SYMBOL: &str = "🛠️";
|
||||
const PREVIEW_SYMBOL: &str = "🧪";
|
||||
const REMOVED_SYMBOL: &str = "❌";
|
||||
const WARNING_SYMBOL: &str = "⚠️";
|
||||
const STABLE_SYMBOL: &str = "✔️";
|
||||
const SPACER: &str = " ";
|
||||
|
||||
fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>, linter: &Linter) {
|
||||
table_out.push_str("| Code | Name | Message | |");
|
||||
@@ -21,20 +26,33 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
table_out.push_str("| ---- | ---- | ------- | ------: |");
|
||||
table_out.push('\n');
|
||||
for rule in rules {
|
||||
let status_token = match rule.group() {
|
||||
RuleGroup::Removed => {
|
||||
format!("<span title='Rule has been removed'>{REMOVED_SYMBOL}</span>")
|
||||
}
|
||||
RuleGroup::Deprecated => {
|
||||
format!("<span title='Rule has been deprecated'>{WARNING_SYMBOL}</span>")
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
RuleGroup::Preview | RuleGroup::Nursery => {
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
}
|
||||
RuleGroup::Stable => {
|
||||
// A full opacity checkmark is a bit aggressive for indicating stable
|
||||
format!("<span title='Rule is stable' style='opacity: 0.6'>{STABLE_SYMBOL}</span>")
|
||||
}
|
||||
};
|
||||
|
||||
let fix_token = match rule.fixable() {
|
||||
FixAvailability::Always | FixAvailability::Sometimes => {
|
||||
format!("<span title='Automatic fix available'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => {
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
|
||||
format!("<span title='Automatic fix not available' style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
};
|
||||
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
} else {
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
|
||||
};
|
||||
let status_token = format!("{fix_token} {preview_token}");
|
||||
|
||||
let tokens = format!("{status_token} {fix_token}");
|
||||
|
||||
let rule_name = rule.as_ref();
|
||||
|
||||
@@ -48,9 +66,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
Cow::Borrowed(message)
|
||||
};
|
||||
|
||||
// Start and end of style spans
|
||||
let mut ss = "";
|
||||
let mut se = "";
|
||||
if rule.is_removed() {
|
||||
ss = "<span style='opacity: 0.5', title='This rule has been removed'>";
|
||||
se = "</span>";
|
||||
} else if rule.is_deprecated() {
|
||||
ss = "<span style='opacity: 0.8', title='This rule has been deprecated'>";
|
||||
se = "</span>";
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
table_out.push_str(&format!(
|
||||
"| {0}{1} {{ #{0}{1} }} | {2} | {3} | {4} |",
|
||||
"| {ss}{0}{1}{se} {{ #{0}{1} }} | {ss}{2}{se} | {ss}{3}{se} | {ss}{4}{se} |",
|
||||
linter.common_prefix(),
|
||||
linter.code_for_rule(rule).unwrap(),
|
||||
rule.explanation()
|
||||
@@ -58,7 +87,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
.then_some(format_args!("[{rule_name}](rules/{rule_name}.md)"))
|
||||
.unwrap_or(format_args!("{rule_name}")),
|
||||
message,
|
||||
status_token,
|
||||
tokens,
|
||||
));
|
||||
table_out.push('\n');
|
||||
}
|
||||
@@ -69,15 +98,33 @@ pub(crate) fn generate() -> String {
|
||||
// Generate the table string.
|
||||
let mut table_out = String::new();
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"The {FIX_SYMBOL} emoji indicates that a rule is automatically fixable by the `--fix` command-line option."));
|
||||
table_out.push('\n');
|
||||
table_out.push_str("### Legend");
|
||||
table_out.push('\n');
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
|
||||
"{SPACER}{STABLE_SYMBOL}{SPACER} The rule is stable."
|
||||
));
|
||||
table_out.push('\n');
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{PREVIEW_SYMBOL}{SPACER} The rule is unstable and is in [\"preview\"](faq.md#what-is-preview)."
|
||||
));
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{WARNING_SYMBOL}{SPACER} The rule has been deprecated and will be removed in a future release."
|
||||
));
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{REMOVED_SYMBOL}{SPACER} The rule has been removed only the documentation is available."
|
||||
));
|
||||
table_out.push_str("<br />");
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"{SPACER}{FIX_SYMBOL}{SPACER} The rule is automatically fixable by the `--fix` command-line option."
|
||||
));
|
||||
table_out.push_str("<br />");
|
||||
table_out.push('\n');
|
||||
|
||||
for linter in Linter::iter() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_cli::check;
|
||||
use ruff::check;
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -56,9 +56,9 @@ enum Command {
|
||||
/// Run a ruff command n times for profiling/benchmarking
|
||||
Repeat {
|
||||
#[clap(flatten)]
|
||||
args: ruff_cli::args::CheckCommand,
|
||||
args: ruff::args::CheckCommand,
|
||||
#[clap(flatten)]
|
||||
log_level_args: ruff_cli::args::LogLevelArgs,
|
||||
log_level_args: ruff::args::LogLevelArgs,
|
||||
/// Run this many times
|
||||
#[clap(long)]
|
||||
repeat: usize,
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
@@ -308,11 +308,8 @@ impl std::fmt::Debug for Token {
|
||||
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
||||
/// assert_eq!(printed.sourcemap(), [
|
||||
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
||||
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(7) },
|
||||
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
||||
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(13) },
|
||||
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
||||
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(14) },
|
||||
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
||||
/// ]);
|
||||
///
|
||||
@@ -328,24 +325,30 @@ pub struct SourcePosition(TextSize);
|
||||
|
||||
impl<Context> Format<Context> for SourcePosition {
|
||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||
if let Some(FormatElement::SourcePosition(last_position)) = f.buffer.elements().last() {
|
||||
if *last_position == self.0 {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
f.write_element(FormatElement::SourcePosition(self.0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a text from a dynamic string with its optional start-position in the source document.
|
||||
/// Creates a text from a dynamic string.
|
||||
///
|
||||
/// This is done by allocating a new string internally.
|
||||
pub fn text(text: &str, position: Option<TextSize>) -> Text {
|
||||
pub fn text(text: &str) -> Text {
|
||||
debug_assert_no_newlines(text);
|
||||
|
||||
Text { text, position }
|
||||
Text { text }
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct Text<'a> {
|
||||
text: &'a str,
|
||||
position: Option<TextSize>,
|
||||
}
|
||||
|
||||
impl<Context> Format<Context> for Text<'_>
|
||||
@@ -353,10 +356,6 @@ where
|
||||
Context: FormatContext,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||
if let Some(source_position) = self.position {
|
||||
f.write_element(FormatElement::SourcePosition(source_position));
|
||||
}
|
||||
|
||||
f.write_element(FormatElement::Text {
|
||||
text: self.text.to_string().into_boxed_str(),
|
||||
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
|
||||
@@ -2286,7 +2285,7 @@ impl<Context, T> std::fmt::Debug for FormatWith<Context, T> {
|
||||
/// let mut join = f.join_with(&separator);
|
||||
///
|
||||
/// for item in &self.items {
|
||||
/// join.entry(&format_with(|f| write!(f, [text(item, None)])));
|
||||
/// join.entry(&format_with(|f| write!(f, [text(item)])));
|
||||
/// }
|
||||
/// join.finish()
|
||||
/// })),
|
||||
@@ -2371,7 +2370,7 @@ where
|
||||
/// let mut count = 0;
|
||||
///
|
||||
/// let value = format_once(|f| {
|
||||
/// write!(f, [text(&std::format!("Formatted {count}."), None)])
|
||||
/// write!(f, [text(&std::format!("Formatted {count}."))])
|
||||
/// });
|
||||
///
|
||||
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
||||
|
||||
@@ -346,10 +346,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
}
|
||||
|
||||
FormatElement::SourcePosition(position) => {
|
||||
write!(
|
||||
f,
|
||||
[text(&std::format!("source_position({position:?})"), None)]
|
||||
)?;
|
||||
write!(f, [text(&std::format!("source_position({position:?})"))])?;
|
||||
}
|
||||
|
||||
FormatElement::LineSuffixBoundary => {
|
||||
@@ -360,7 +357,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
write!(f, [token("best_fitting(")])?;
|
||||
|
||||
if *mode != BestFittingMode::FirstLine {
|
||||
write!(f, [text(&std::format!("mode: {mode:?}, "), None)])?;
|
||||
write!(f, [text(&std::format!("mode: {mode:?}, "))])?;
|
||||
}
|
||||
|
||||
write!(f, [token("[")])?;
|
||||
@@ -392,17 +389,14 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(&std::format!("<interned {index}>"), None),
|
||||
text(&std::format!("<interned {index}>")),
|
||||
space(),
|
||||
&&**interned,
|
||||
]
|
||||
)?;
|
||||
}
|
||||
Some(reference) => {
|
||||
write!(
|
||||
f,
|
||||
[text(&std::format!("<ref interned *{reference}>"), None)]
|
||||
)?;
|
||||
write!(f, [text(&std::format!("<ref interned *{reference}>"))])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,7 +415,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
f,
|
||||
[
|
||||
token("<END_TAG_WITHOUT_START<"),
|
||||
text(&std::format!("{:?}", tag.kind()), None),
|
||||
text(&std::format!("{:?}", tag.kind())),
|
||||
token(">>"),
|
||||
]
|
||||
)?;
|
||||
@@ -436,9 +430,9 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
token(")"),
|
||||
soft_line_break_or_space(),
|
||||
token("ERROR<START_END_TAG_MISMATCH<start: "),
|
||||
text(&std::format!("{start_kind:?}"), None),
|
||||
text(&std::format!("{start_kind:?}")),
|
||||
token(", end: "),
|
||||
text(&std::format!("{:?}", tag.kind()), None),
|
||||
text(&std::format!("{:?}", tag.kind())),
|
||||
token(">>")
|
||||
]
|
||||
)?;
|
||||
@@ -470,7 +464,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
f,
|
||||
[
|
||||
token("align("),
|
||||
text(&count.to_string(), None),
|
||||
text(&count.to_string()),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
@@ -482,7 +476,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
f,
|
||||
[
|
||||
token("line_suffix("),
|
||||
text(&std::format!("{reserved_width:?}"), None),
|
||||
text(&std::format!("{reserved_width:?}")),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
@@ -499,11 +493,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
if let Some(group_id) = group.id() {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(&std::format!("\"{group_id:?}\""), None),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -524,11 +514,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
if let Some(group_id) = id {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(&std::format!("\"{group_id:?}\""), None),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -561,7 +547,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
f,
|
||||
[
|
||||
token("indent_if_group_breaks("),
|
||||
text(&std::format!("\"{id:?}\""), None),
|
||||
text(&std::format!("\"{id:?}\"")),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
@@ -581,11 +567,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
if let Some(group_id) = condition.group_id {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(&std::format!("\"{group_id:?}\""), None),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space()]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -595,7 +577,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
f,
|
||||
[
|
||||
token("label("),
|
||||
text(&std::format!("\"{label_id:?}\""), None),
|
||||
text(&std::format!("\"{label_id:?}\"")),
|
||||
token(","),
|
||||
space(),
|
||||
]
|
||||
@@ -664,7 +646,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||
ContentArrayEnd,
|
||||
token(")"),
|
||||
soft_line_break_or_space(),
|
||||
text(&std::format!("<START_WITHOUT_END<{top:?}>>"), None),
|
||||
text(&std::format!("<START_WITHOUT_END<{top:?}>>")),
|
||||
]
|
||||
)?;
|
||||
}
|
||||
@@ -807,7 +789,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
||||
f,
|
||||
[
|
||||
token("if_group_fits_on_line("),
|
||||
text(&std::format!("\"{id:?}\""), None),
|
||||
text(&std::format!("\"{id:?}\"")),
|
||||
token(")")
|
||||
]
|
||||
),
|
||||
@@ -816,7 +798,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
||||
f,
|
||||
[
|
||||
token("if_group_breaks("),
|
||||
text(&std::format!("\"{id:?}\""), None),
|
||||
text(&std::format!("\"{id:?}\"")),
|
||||
token(")")
|
||||
]
|
||||
),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::format_element::PrintMode;
|
||||
use crate::{GroupId, TextSize};
|
||||
use std::cell::Cell;
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use crate::format_element::PrintMode;
|
||||
use crate::{GroupId, TextSize};
|
||||
|
||||
/// A Tag marking the start and end of some content to which some special formatting should be applied.
|
||||
///
|
||||
/// Tags always come in pairs of a start and an end tag and the styling defined by this tag
|
||||
@@ -99,6 +100,10 @@ pub enum Tag {
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub const fn align(count: NonZeroU8) -> Tag {
|
||||
Tag::StartAlign(Align(count))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is any start tag.
|
||||
pub const fn is_start(&self) -> bool {
|
||||
matches!(
|
||||
|
||||
@@ -32,7 +32,7 @@ pub trait MemoizeFormat<Context> {
|
||||
/// let value = self.value.get();
|
||||
/// self.value.set(value + 1);
|
||||
///
|
||||
/// write!(f, [text(&std::format!("Formatted {value} times."), None)])
|
||||
/// write!(f, [text(&std::format!("Formatted {value} times."))])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -110,7 +110,7 @@ where
|
||||
/// write!(f, [
|
||||
/// token("Count:"),
|
||||
/// space(),
|
||||
/// text(&std::format!("{current}"), None),
|
||||
/// text(&std::format!("{current}")),
|
||||
/// hard_line_break()
|
||||
/// ])?;
|
||||
///
|
||||
|
||||
@@ -35,12 +35,13 @@ mod source_code;
|
||||
use crate::formatter::Formatter;
|
||||
use crate::group_id::UniqueGroupIdBuilder;
|
||||
use crate::prelude::TagKind;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::marker::PhantomData;
|
||||
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
||||
|
||||
use crate::format_element::document::Document;
|
||||
use crate::printer::{Printer, PrinterOptions, SourceMapGeneration};
|
||||
use crate::printer::{Printer, PrinterOptions};
|
||||
pub use arguments::{Argument, Arguments};
|
||||
pub use buffer::{
|
||||
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
|
||||
@@ -52,7 +53,7 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
|
||||
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
|
||||
pub use group_id::GroupId;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
|
||||
#[cfg_attr(
|
||||
@@ -113,6 +114,12 @@ impl Default for IndentWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IndentWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for IndentWidth {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
@@ -146,6 +153,12 @@ impl Default for LineWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LineWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for LineWidth {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
@@ -256,7 +269,6 @@ impl FormatOptions for SimpleFormatOptions {
|
||||
line_width: self.line_width,
|
||||
indent_style: self.indent_style,
|
||||
indent_width: self.indent_width,
|
||||
source_map_generation: SourceMapGeneration::Enabled,
|
||||
..PrinterOptions::default()
|
||||
}
|
||||
}
|
||||
@@ -419,6 +431,129 @@ impl Printed {
|
||||
pub fn take_verbatim_ranges(&mut self) -> Vec<TextRange> {
|
||||
std::mem::take(&mut self.verbatim_ranges)
|
||||
}
|
||||
|
||||
/// Slices the formatted code to the sub-slices that covers the passed `source_range` in `source`.
|
||||
///
|
||||
/// The implementation uses the source map generated during formatting to find the closest range
|
||||
/// in the formatted document that covers `source_range` or more. The returned slice
|
||||
/// matches the `source_range` exactly (except indent, see below) if the formatter emits [`FormatElement::SourcePosition`] for
|
||||
/// the range's offsets.
|
||||
///
|
||||
/// ## Indentation
|
||||
/// The indentation before `source_range.start` is replaced with the indentation returned by the formatter
|
||||
/// to fix up incorrectly intended code.
|
||||
///
|
||||
/// Returns the entire document if the source map is empty.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `source_range` points to offsets that are not in the bounds of `source`.
|
||||
#[must_use]
|
||||
pub fn slice_range(self, source_range: TextRange, source: &str) -> PrintedRange {
|
||||
let mut start_marker: Option<SourceMarker> = None;
|
||||
let mut end_marker: Option<SourceMarker> = None;
|
||||
|
||||
// Note: The printer can generate multiple source map entries for the same source position.
|
||||
// For example if you have:
|
||||
// * token("a + b")
|
||||
// * `source_position(276)`
|
||||
// * `token(")")`
|
||||
// * `source_position(276)`
|
||||
// * `hard_line_break`
|
||||
// The printer uses the source position 276 for both the tokens `)` and the `\n` because
|
||||
// there were multiple `source_position` entries in the IR with the same offset.
|
||||
// This can happen if multiple nodes start or end at the same position. A common example
|
||||
// for this are expressions and expression statement that always end at the same offset.
|
||||
//
|
||||
// Warning: Source markers are often emitted sorted by their source position but it's not guaranteed
|
||||
// and depends on the emitted `IR`.
|
||||
// They are only guaranteed to be sorted in increasing order by their destination position.
|
||||
for marker in self.sourcemap {
|
||||
// Take the closest start marker, but skip over start_markers that have the same start.
|
||||
if marker.source <= source_range.start()
|
||||
&& !start_marker.is_some_and(|existing| existing.source >= marker.source)
|
||||
{
|
||||
start_marker = Some(marker);
|
||||
}
|
||||
|
||||
if marker.source >= source_range.end()
|
||||
&& !end_marker.is_some_and(|existing| existing.source <= marker.source)
|
||||
{
|
||||
end_marker = Some(marker);
|
||||
}
|
||||
}
|
||||
|
||||
let (source_start, formatted_start) = start_marker
|
||||
.map(|marker| (marker.source, marker.dest))
|
||||
.unwrap_or_default();
|
||||
|
||||
let (source_end, formatted_end) = end_marker
|
||||
.map_or((source.text_len(), self.code.text_len()), |marker| {
|
||||
(marker.source, marker.dest)
|
||||
});
|
||||
|
||||
let source_range = TextRange::new(source_start, source_end);
|
||||
let formatted_range = TextRange::new(formatted_start, formatted_end);
|
||||
|
||||
// Extend both ranges to include the indentation
|
||||
let source_range = extend_range_to_include_indent(source_range, source);
|
||||
let formatted_range = extend_range_to_include_indent(formatted_range, &self.code);
|
||||
|
||||
PrintedRange {
|
||||
code: self.code[formatted_range].to_string(),
|
||||
source_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends `range` backwards (by reducing `range.start`) to include any directly preceding whitespace (`\t` or ` `).
|
||||
///
|
||||
/// # Panics
|
||||
/// If `range.start` is out of `source`'s bounds.
|
||||
fn extend_range_to_include_indent(range: TextRange, source: &str) -> TextRange {
|
||||
let whitespace_len: TextSize = source[..usize::from(range.start())]
|
||||
.chars()
|
||||
.rev()
|
||||
.take_while(|c| matches!(c, ' ' | '\t'))
|
||||
.map(TextLen::text_len)
|
||||
.sum();
|
||||
|
||||
TextRange::new(range.start() - whitespace_len, range.end())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PrintedRange {
|
||||
code: String,
|
||||
source_range: TextRange,
|
||||
}
|
||||
|
||||
impl PrintedRange {
|
||||
pub fn new(code: String, source_range: TextRange) -> Self {
|
||||
Self { code, source_range }
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
code: String::new(),
|
||||
source_range: TextRange::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The formatted code.
|
||||
pub fn as_code(&self) -> &str {
|
||||
&self.code
|
||||
}
|
||||
|
||||
/// The range the formatted code corresponds to in the source document.
|
||||
pub fn source_range(&self) -> TextRange {
|
||||
self.source_range
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_code(self, code: String) -> Self {
|
||||
Self { code, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Public return type of the formatter
|
||||
@@ -440,7 +575,7 @@ pub type FormatResult<F> = Result<F, FormatError>;
|
||||
/// impl Format<SimpleFormatContext> for Paragraph {
|
||||
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
||||
/// write!(f, [
|
||||
/// text(&self.0, None),
|
||||
/// text(&self.0),
|
||||
/// hard_line_break(),
|
||||
/// ])
|
||||
/// }
|
||||
|
||||
@@ -21,8 +21,7 @@ impl<'a> LineSuffixes<'a> {
|
||||
/// Takes all the pending line suffixes.
|
||||
pub(super) fn take_pending<'l>(
|
||||
&'l mut self,
|
||||
) -> impl Iterator<Item = LineSuffixEntry<'a>> + DoubleEndedIterator + 'l + ExactSizeIterator
|
||||
{
|
||||
) -> impl DoubleEndedIterator<Item = LineSuffixEntry<'a>> + 'l + ExactSizeIterator {
|
||||
self.suffixes.drain(..)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use drop_bomb::DebugDropBomb;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
pub use printer_options::*;
|
||||
use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
use ruff_text_size::{TextLen, TextSize};
|
||||
|
||||
use crate::format_element::document::Document;
|
||||
use crate::format_element::tag::{Condition, GroupMode};
|
||||
@@ -60,7 +60,10 @@ impl<'a> Printer<'a> {
|
||||
document: &'a Document,
|
||||
indent: u16,
|
||||
) -> PrintResult<Printed> {
|
||||
let mut stack = PrintCallStack::new(PrintElementArgs::new(Indention::Level(indent)));
|
||||
let indentation = Indention::Level(indent);
|
||||
self.state.pending_indent = indentation;
|
||||
|
||||
let mut stack = PrintCallStack::new(PrintElementArgs::new(indentation));
|
||||
let mut queue: PrintQueue<'a> = PrintQueue::new(document.as_ref());
|
||||
|
||||
loop {
|
||||
@@ -73,6 +76,9 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Push any pending marker
|
||||
self.push_marker();
|
||||
|
||||
Ok(Printed::new(
|
||||
self.state.buffer,
|
||||
None,
|
||||
@@ -94,42 +100,38 @@ impl<'a> Printer<'a> {
|
||||
let args = stack.top();
|
||||
|
||||
match element {
|
||||
FormatElement::Space => self.print_text(Text::Token(" "), None),
|
||||
FormatElement::Token { text } => self.print_text(Text::Token(text), None),
|
||||
FormatElement::Text { text, text_width } => self.print_text(
|
||||
Text::Text {
|
||||
text,
|
||||
text_width: *text_width,
|
||||
},
|
||||
None,
|
||||
),
|
||||
FormatElement::Space => self.print_text(Text::Token(" ")),
|
||||
FormatElement::Token { text } => self.print_text(Text::Token(text)),
|
||||
FormatElement::Text { text, text_width } => self.print_text(Text::Text {
|
||||
text,
|
||||
text_width: *text_width,
|
||||
}),
|
||||
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||
let text = slice.text(self.source_code);
|
||||
self.print_text(
|
||||
Text::Text {
|
||||
text,
|
||||
text_width: *text_width,
|
||||
},
|
||||
Some(slice.range()),
|
||||
);
|
||||
self.print_text(Text::Text {
|
||||
text,
|
||||
text_width: *text_width,
|
||||
});
|
||||
}
|
||||
FormatElement::Line(line_mode) => {
|
||||
if args.mode().is_flat()
|
||||
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
|
||||
{
|
||||
if line_mode == &LineMode::SoftOrSpace {
|
||||
self.print_text(Text::Token(" "), None);
|
||||
self.print_text(Text::Token(" "));
|
||||
}
|
||||
} else if self.state.line_suffixes.has_pending() {
|
||||
self.flush_line_suffixes(queue, stack, Some(element));
|
||||
} else {
|
||||
// Only print a newline if the current line isn't already empty
|
||||
if self.state.line_width > 0 {
|
||||
self.push_marker();
|
||||
self.print_char('\n');
|
||||
}
|
||||
|
||||
// Print a second line break if this is an empty line
|
||||
if line_mode == &LineMode::Empty {
|
||||
self.push_marker();
|
||||
self.print_char('\n');
|
||||
}
|
||||
|
||||
@@ -142,8 +144,11 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
|
||||
FormatElement::SourcePosition(position) => {
|
||||
self.state.source_position = *position;
|
||||
self.push_marker();
|
||||
// The printer defers printing indents until the next text
|
||||
// is printed. Pushing the marker now would mean that the
|
||||
// mapped range includes the indent range, which we don't want.
|
||||
// Queue the source map position and emit it when printing the next character
|
||||
self.state.pending_source_position = Some(*position);
|
||||
}
|
||||
|
||||
FormatElement::LineSuffixBoundary => {
|
||||
@@ -435,7 +440,7 @@ impl<'a> Printer<'a> {
|
||||
Ok(print_mode)
|
||||
}
|
||||
|
||||
fn print_text(&mut self, text: Text, source_range: Option<TextRange>) {
|
||||
fn print_text(&mut self, text: Text) {
|
||||
if !self.state.pending_indent.is_empty() {
|
||||
let (indent_char, repeat_count) = match self.options.indent_style() {
|
||||
IndentStyle::Tab => ('\t', 1),
|
||||
@@ -458,19 +463,6 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Insert source map markers before and after the token
|
||||
//
|
||||
// If the token has source position information the start marker
|
||||
// will use the start position of the original token, and the end
|
||||
// marker will use that position + the text length of the token
|
||||
//
|
||||
// If the token has no source position (was created by the formatter)
|
||||
// both the start and end marker will use the last known position
|
||||
// in the input source (from state.source_position)
|
||||
if let Some(range) = source_range {
|
||||
self.state.source_position = range.start();
|
||||
}
|
||||
|
||||
self.push_marker();
|
||||
|
||||
match text {
|
||||
@@ -493,29 +485,24 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(range) = source_range {
|
||||
self.state.source_position = range.end();
|
||||
}
|
||||
|
||||
self.push_marker();
|
||||
}
|
||||
|
||||
fn push_marker(&mut self) {
|
||||
if self.options.source_map_generation.is_disabled() {
|
||||
let Some(source_position) = self.state.pending_source_position.take() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let marker = SourceMarker {
|
||||
source: self.state.source_position,
|
||||
source: source_position,
|
||||
dest: self.state.buffer.text_len(),
|
||||
};
|
||||
|
||||
if let Some(last) = self.state.source_markers.last() {
|
||||
if last != &marker {
|
||||
self.state.source_markers.push(marker);
|
||||
}
|
||||
} else {
|
||||
if self
|
||||
.state
|
||||
.source_markers
|
||||
.last()
|
||||
.map_or(true, |last| last != &marker)
|
||||
{
|
||||
self.state.source_markers.push(marker);
|
||||
}
|
||||
}
|
||||
@@ -887,7 +874,7 @@ enum FillPairLayout {
|
||||
struct PrinterState<'a> {
|
||||
buffer: String,
|
||||
source_markers: Vec<SourceMarker>,
|
||||
source_position: TextSize,
|
||||
pending_source_position: Option<TextSize>,
|
||||
pending_indent: Indention,
|
||||
measured_group_fits: bool,
|
||||
line_width: u32,
|
||||
@@ -1472,6 +1459,11 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
}
|
||||
|
||||
fn fits_text(&mut self, text: Text, args: PrintElementArgs) -> Fits {
|
||||
fn exceeds_width(fits: &FitsMeasurer, args: PrintElementArgs) -> bool {
|
||||
fits.state.line_width > fits.options().line_width.into()
|
||||
&& !args.measure_mode().allows_text_overflow()
|
||||
}
|
||||
|
||||
let indent = std::mem::take(&mut self.state.pending_indent);
|
||||
self.state.line_width +=
|
||||
u32::from(indent.level()) * self.options().indent_width() + u32::from(indent.align());
|
||||
@@ -1493,7 +1485,13 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
return Fits::No;
|
||||
}
|
||||
match args.measure_mode() {
|
||||
MeasureMode::FirstLine => return Fits::Yes,
|
||||
MeasureMode::FirstLine => {
|
||||
return if exceeds_width(self, args) {
|
||||
Fits::No
|
||||
} else {
|
||||
Fits::Yes
|
||||
};
|
||||
}
|
||||
MeasureMode::AllLines
|
||||
| MeasureMode::AllLinesAllowTextOverflow => {
|
||||
self.state.line_width = 0;
|
||||
@@ -1511,9 +1509,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.state.line_width > self.options().line_width.into()
|
||||
&& !args.measure_mode().allows_text_overflow()
|
||||
{
|
||||
if exceeds_width(self, args) {
|
||||
return Fits::No;
|
||||
}
|
||||
|
||||
@@ -1733,7 +1729,7 @@ a",
|
||||
let result = format_with_options(
|
||||
&format_args![
|
||||
token("function main() {"),
|
||||
block_indent(&text("let x = `This is a multiline\nstring`;", None)),
|
||||
block_indent(&text("let x = `This is a multiline\nstring`;")),
|
||||
token("}"),
|
||||
hard_line_break()
|
||||
],
|
||||
@@ -1750,7 +1746,7 @@ a",
|
||||
fn it_breaks_a_group_if_a_string_contains_a_newline() {
|
||||
let result = format(&FormatArrayElements {
|
||||
items: vec![
|
||||
&text("`This is a string spanning\ntwo lines`", None),
|
||||
&text("`This is a string spanning\ntwo lines`"),
|
||||
&token("\"b\""),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -14,10 +14,6 @@ pub struct PrinterOptions {
|
||||
|
||||
/// The type of line ending to apply to the printed input
|
||||
pub line_ending: LineEnding,
|
||||
|
||||
/// Whether the printer should build a source map that allows mapping positions in the source document
|
||||
/// to positions in the formatted document.
|
||||
pub source_map_generation: SourceMapGeneration,
|
||||
}
|
||||
|
||||
impl<'a, O> From<&'a O> for PrinterOptions
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.10"
|
||||
version = "0.2.1"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -85,8 +85,8 @@ tempfile = { workspace = true }
|
||||
[features]
|
||||
default = []
|
||||
schemars = ["dep:schemars"]
|
||||
# Enables the UnreachableCode rule
|
||||
unreachable-code = []
|
||||
# Enables rules for internal integration tests
|
||||
test-rules = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
0
crates/ruff_linter/__init__.py
Normal file
0
crates/ruff_linter/__init__.py
Normal file
0
crates/ruff_linter/resources/__init__.py
Normal file
0
crates/ruff_linter/resources/__init__.py
Normal file
0
crates/ruff_linter/resources/test/__init__.py
Normal file
0
crates/ruff_linter/resources/test/__init__.py
Normal file
0
crates/ruff_linter/resources/test/fixtures/__init__.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/__init__.py
vendored
Normal file
@@ -264,3 +264,41 @@ def func(x: int):
|
||||
if x > 0:
|
||||
return 1
|
||||
raise ValueError
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 5:
|
||||
raise ValueError
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 5:
|
||||
raise ValueError
|
||||
elif x > 10:
|
||||
pass
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 5:
|
||||
raise ValueError
|
||||
elif x > 10:
|
||||
return 5
|
||||
|
||||
|
||||
def func():
|
||||
try:
|
||||
return 5
|
||||
except:
|
||||
pass
|
||||
|
||||
raise ValueError
|
||||
|
||||
|
||||
def func(x: int):
|
||||
match x:
|
||||
case [1, 2, 3]:
|
||||
return 1
|
||||
case y:
|
||||
return "foo"
|
||||
|
||||
@@ -1,31 +1,87 @@
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Violation cases:
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
open("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
subprocess.run("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
subprocess.call("foo")
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
subprocess.foo(0)
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
os.wait4(10)
|
||||
|
||||
|
||||
async def foo():
|
||||
async def func():
|
||||
os.wait(12)
|
||||
|
||||
|
||||
# Violation cases for pathlib:
|
||||
|
||||
|
||||
async def func():
|
||||
Path("foo").open() # ASYNC101
|
||||
|
||||
|
||||
async def func():
|
||||
p = Path("foo")
|
||||
p.open() # ASYNC101
|
||||
|
||||
|
||||
async def func():
|
||||
with Path("foo").open() as f: # ASYNC101
|
||||
pass
|
||||
|
||||
|
||||
async def func() -> None:
|
||||
p = Path("foo")
|
||||
|
||||
async def bar():
|
||||
p.open() # ASYNC101
|
||||
|
||||
|
||||
async def func() -> None:
|
||||
(p1, p2) = (Path("foo"), Path("bar"))
|
||||
|
||||
p1.open() # ASYNC101
|
||||
|
||||
|
||||
# Non-violation cases for pathlib:
|
||||
|
||||
|
||||
class Foo:
|
||||
def open(self):
|
||||
pass
|
||||
|
||||
|
||||
async def func():
|
||||
Foo().open() # OK
|
||||
|
||||
|
||||
async def func():
|
||||
def open():
|
||||
pass
|
||||
|
||||
open() # OK
|
||||
|
||||
|
||||
def func():
|
||||
Path("foo").open() # OK
|
||||
|
||||
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py
vendored
Normal file
22
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
def some_func():
|
||||
return mark_safe('<script>alert("evil!")</script>')
|
||||
|
||||
|
||||
@mark_safe
|
||||
def some_func():
|
||||
return '<script>alert("evil!")</script>'
|
||||
|
||||
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
|
||||
def some_func():
|
||||
return mark_safe('<script>alert("evil!")</script>')
|
||||
|
||||
|
||||
@mark_safe
|
||||
def some_func():
|
||||
return '<script>alert("evil!")</script>'
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S401.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S401.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import telnetlib # S401
|
||||
from telnetlib import Telnet # S401
|
||||
2
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S402.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S402.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import ftplib # S402
|
||||
from ftplib import FTP # S402
|
||||
8
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S403.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S403.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import dill # S403
|
||||
from dill import objects # S403
|
||||
import shelve
|
||||
from shelve import open
|
||||
import cPickle
|
||||
from cPickle import load
|
||||
import pickle
|
||||
from pickle import load
|
||||
3
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S404.py
vendored
Normal file
3
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S404.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import subprocess # S404
|
||||
from subprocess import Popen # S404
|
||||
from subprocess import Popen as pop # S404
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user