Compare commits
104 Commits
v0.1.2
...
zanie/pre-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69fc2caa31 | ||
|
|
e9acb99f7d | ||
|
|
1642f4dbd9 | ||
|
|
38358980f1 | ||
|
|
43691f97d0 | ||
|
|
3076d76b0a | ||
|
|
23ed4e9616 | ||
|
|
97ae617fac | ||
|
|
a8d04cbd88 | ||
|
|
230c93459f | ||
|
|
8977b6ae11 | ||
|
|
982ae6ff08 | ||
|
|
c674db6e51 | ||
|
|
7323c12eee | ||
|
|
161c093c06 | ||
|
|
daea870c3c | ||
|
|
b6c4074836 | ||
|
|
cf74debf42 | ||
|
|
f483ed4240 | ||
|
|
98b3d716c6 | ||
|
|
03df6fa105 | ||
|
|
8cc97f70b4 | ||
|
|
951c59c6ad | ||
|
|
d177df226d | ||
|
|
703e2a9da3 | ||
|
|
bdad5e9a5f | ||
|
|
b21eb1f689 | ||
|
|
b0dc5a86a1 | ||
|
|
b5a4a9a356 | ||
|
|
230c9ce236 | ||
|
|
78bbf6d403 | ||
|
|
ee7d445ef5 | ||
|
|
5776ec1079 | ||
|
|
1f2d4f3ee1 | ||
|
|
221f7cd932 | ||
|
|
c7aa816f17 | ||
|
|
3ccca332bd | ||
|
|
2c84f911c4 | ||
|
|
e799f90782 | ||
|
|
9b89bf7d8a | ||
|
|
d7b966d6cd | ||
|
|
44e21cfada | ||
|
|
cda1c5dd35 | ||
|
|
86cdaea743 | ||
|
|
af4cb34ce2 | ||
|
|
4afff436ff | ||
|
|
f2f2e759c7 | ||
|
|
317b6e8682 | ||
|
|
2f5734d1ac | ||
|
|
c39ea6ef05 | ||
|
|
10a50bf1e2 | ||
|
|
a151e50ad3 | ||
|
|
854f5d09fa | ||
|
|
c2f6c79b3d | ||
|
|
87772c2884 | ||
|
|
aa90a425e0 | ||
|
|
81a2e74fe2 | ||
|
|
3af890f32f | ||
|
|
223873c8c7 | ||
|
|
7b4b004506 | ||
|
|
9f5102d536 | ||
|
|
af95cbaeef | ||
|
|
fc94857a20 | ||
|
|
5f26411577 | ||
|
|
40c886c3bc | ||
|
|
097e703071 | ||
|
|
cd8e1bad64 | ||
|
|
e2b5c6ac5f | ||
|
|
c36efe254e | ||
|
|
3e7b92991b | ||
|
|
25d4ddaa60 | ||
|
|
63a5a12a41 | ||
|
|
c32f943d86 | ||
|
|
d211074f59 | ||
|
|
4ffd4ed61f | ||
|
|
a4dd1e5fad | ||
|
|
be3307e9a6 | ||
|
|
317d3dd612 | ||
|
|
f5e850745c | ||
|
|
a7d1f7e1ec | ||
|
|
88c8b47326 | ||
|
|
133a745de1 | ||
|
|
6983d96d27 | ||
|
|
3c3d9ab173 | ||
|
|
ff9fb0da54 | ||
|
|
9792b1551b | ||
|
|
d1c67f91bd | ||
|
|
dbd84c947b | ||
|
|
c2ec5f0bc9 | ||
|
|
31032f4f70 | ||
|
|
f55b724254 | ||
|
|
fd07a12a52 | ||
|
|
1ee73bdedf | ||
|
|
23b55aea30 | ||
|
|
e36afc3324 | ||
|
|
8304c41714 | ||
|
|
6f31e9c00e | ||
|
|
a6cc56fd98 | ||
|
|
0236e0751c | ||
|
|
2d0769e324 | ||
|
|
80473c3f5c | ||
|
|
4d7f90e045 | ||
|
|
75bd95e58c | ||
|
|
3bbdfee69f |
126
.github/workflows/ci.yaml
vendored
126
.github/workflows/ci.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: tj-actions/changed-files@v39
|
||||
- uses: tj-actions/changed-files@v40
|
||||
id: changed
|
||||
with:
|
||||
files_yaml: |
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/*
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -57,6 +58,7 @@ jobs:
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
- scripts/*
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -82,12 +84,9 @@ jobs:
|
||||
- name: "Clippy (wasm)"
|
||||
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
|
||||
|
||||
cargo-test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "cargo test | ${{ matrix.os }}"
|
||||
cargo-test-linux:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (linux)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -97,25 +96,54 @@ jobs:
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests (Ubuntu)"
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
- name: "Run tests"
|
||||
run: cargo insta test --all --all-features --unreferenced reject
|
||||
- name: "Run tests (Windows)"
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
shell: bash
|
||||
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||
run: cargo insta test --all --all-features
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
|
||||
cargo-test-windows:
|
||||
runs-on: windows-latest
|
||||
name: "cargo test (windows)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||
run: cargo insta test --all --all-features
|
||||
|
||||
cargo-test-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run wasm-pack"
|
||||
run: |
|
||||
cd crates/ruff_wasm
|
||||
wasm-pack test --node
|
||||
|
||||
cargo-fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo fuzz"
|
||||
@@ -132,25 +160,6 @@ jobs:
|
||||
tool: cargo-fuzz@0.11
|
||||
- run: cargo fuzz build -s none
|
||||
|
||||
cargo-test-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
name: "cargo test (wasm)"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run wasm-pack"
|
||||
run: |
|
||||
cd crates/ruff_wasm
|
||||
wasm-pack test --node
|
||||
|
||||
scripts:
|
||||
name: "test scripts"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -172,10 +181,10 @@ jobs:
|
||||
name: "ecosystem"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- cargo-test
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: github.event_name == 'pull_request' && needs.determine_changes.outputs.linter == 'true'
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -183,27 +192,54 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
name: Download Ruff binary
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v2
|
||||
name: Download base results
|
||||
name: Download baseline Ruff binary
|
||||
with:
|
||||
name: ruff
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
check_artifacts: true
|
||||
|
||||
- name: Run ecosystem check
|
||||
- name: Install ruff-ecosystem
|
||||
run: |
|
||||
pip install ./python/ruff-ecosystem
|
||||
|
||||
- name: Run `ruff check` ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
|
||||
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
|
||||
cat ecosystem-result > $GITHUB_STEP_SUMMARY
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check
|
||||
|
||||
cat ecosystem-result-check > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-check > ecosystem-result
|
||||
echo "" >> ecosystem-result
|
||||
|
||||
- name: Run `ruff format` ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format
|
||||
|
||||
cat ecosystem-result-format > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-format >> ecosystem-result
|
||||
|
||||
- name: Export pull request number
|
||||
run: |
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
@@ -319,8 +355,8 @@ jobs:
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
|
||||
check-formatter-ecosystem:
|
||||
name: "Formatter ecosystem and progress checks"
|
||||
check-formatter-instability-and-black-similarity:
|
||||
name: "formatter instabilities and black similarity"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
|
||||
@@ -340,7 +376,7 @@ jobs:
|
||||
check-ruff-lsp:
|
||||
name: "test ruff-lsp"
|
||||
runs-on: ubuntu-latest
|
||||
needs: cargo-test
|
||||
needs: cargo-test-linux
|
||||
steps:
|
||||
- uses: extractions/setup-just@v1
|
||||
env:
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.3.1
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
4
.github/workflows/playground.yaml
vendored
4
.github/workflows/playground.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
@@ -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.1
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
1
.github/workflows/pr-comment.yaml
vendored
1
.github/workflows/pr-comment.yaml
vendored
@@ -41,6 +41,7 @@ jobs:
|
||||
workflow: ci.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/ecosystem
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
|
||||
- name: Generate Comment
|
||||
|
||||
36
.github/workflows/pre-commit.yaml
vendored
Normal file
36
.github/workflows/pre-commit.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Until Dependabot support is released https://github.com/dependabot/dependabot-core/issues/1524
|
||||
name: Pre-commit update
|
||||
|
||||
on:
|
||||
# every week on monday
|
||||
schedule:
|
||||
- cron: "0 0 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
upgrade:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Run autoupdate
|
||||
run: |
|
||||
pre-commit autoupdate
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git add ".pre-commit-config.yaml"
|
||||
git commit -m "Upgrade pre-commit dependencies"
|
||||
git push origin upgrade/pre-commit
|
||||
|
||||
- name: Open pull request
|
||||
run: |
|
||||
gh pr create --fill
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,9 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.3
|
||||
|
||||
This release includes a variety of improvements to the Ruff formatter, removing several known and
|
||||
unintentional deviations from Black.
|
||||
|
||||
### Formatter
|
||||
|
||||
- Avoid space around pow for `None`, `True` and `False` ([#8189](https://github.com/astral-sh/ruff/pull/8189))
|
||||
- Avoid sorting all paths in the format command ([#8181](https://github.com/astral-sh/ruff/pull/8181))
|
||||
- Insert necessary blank line between class and leading comments ([#8224](https://github.com/astral-sh/ruff/pull/8224))
|
||||
- Avoid introducing new parentheses in annotated assignments ([#8233](https://github.com/astral-sh/ruff/pull/8233))
|
||||
- Refine the warnings about incompatible linter options ([#8196](https://github.com/astral-sh/ruff/pull/8196))
|
||||
- Add test and basic implementation for formatter preview mode ([#8044](https://github.com/astral-sh/ruff/pull/8044))
|
||||
- Refine warning about incompatible `isort` settings ([#8192](https://github.com/astral-sh/ruff/pull/8192))
|
||||
- Only omit optional parentheses for starting or ending with parentheses ([#8238](https://github.com/astral-sh/ruff/pull/8238))
|
||||
- Use source type to determine parser mode for formatting ([#8205](https://github.com/astral-sh/ruff/pull/8205))
|
||||
- Don't warn about magic trailing comma when `isort.force-single-line` is true ([#8244](https://github.com/astral-sh/ruff/pull/8244))
|
||||
- Use `SourceKind::diff` for formatter ([#8240](https://github.com/astral-sh/ruff/pull/8240))
|
||||
- Fix `fmt:off` with trailing child comment ([#8234](https://github.com/astral-sh/ruff/pull/8234))
|
||||
- Formatter parentheses support for `IpyEscapeCommand` ([#8207](https://github.com/astral-sh/ruff/pull/8207))
|
||||
|
||||
### Linter
|
||||
|
||||
- \[`pylint`\] Add buffer methods to `bad-dunder-method-name` (`PLW3201`) exclusions ([#8190](https://github.com/astral-sh/ruff/pull/8190))
|
||||
- Match rule prefixes from `external` codes setting in `unused-noqa` ([#8177](https://github.com/astral-sh/ruff/pull/8177))
|
||||
- Use `line-length` setting for isort in lieu of `pycodestyle.max-line-length` ([#8235](https://github.com/astral-sh/ruff/pull/8235))
|
||||
- Update fix for `unnecessary-paren-on-raise-exception` to unsafe for unknown types ([#8231](https://github.com/astral-sh/ruff/pull/8231))
|
||||
- Correct quick fix message for `W605` ([#8255](https://github.com/astral-sh/ruff/pull/8255))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix typo in max-doc-length documentation ([#8201](https://github.com/astral-sh/ruff/pull/8201))
|
||||
- Improve documentation around linter-formatter conflicts ([#8257](https://github.com/astral-sh/ruff/pull/8257))
|
||||
- Fix link to error suppression documentation in `unused-noqa` ([#8172](https://github.com/astral-sh/ruff/pull/8172))
|
||||
- Add `external` option to `unused-noqa` documentation ([#8171](https://github.com/astral-sh/ruff/pull/8171))
|
||||
- Add title attribute to icons ([#8060](https://github.com/astral-sh/ruff/pull/8060))
|
||||
- Clarify unsafe case in RSE102 ([#8256](https://github.com/astral-sh/ruff/pull/8256))
|
||||
- Fix skipping formatting examples ([#8210](https://github.com/astral-sh/ruff/pull/8210))
|
||||
- docs: fix name of `magic-trailing-comma` option in README ([#8200](https://github.com/astral-sh/ruff/pull/8200))
|
||||
- Add note about scope of rule changing in versioning policy ([#8169](https://github.com/astral-sh/ruff/pull/8169))
|
||||
- Document: Fix default lint rules ([#8218](https://github.com/astral-sh/ruff/pull/8218))
|
||||
- Fix a wrong setting in configuration.md ([#8186](https://github.com/astral-sh/ruff/pull/8186))
|
||||
- Fix misspelled TOML headers in the tutorial ([#8209](https://github.com/astral-sh/ruff/pull/8209))
|
||||
|
||||
## 0.1.2
|
||||
|
||||
This release includes the Beta version of the Ruff formatter — an extremely fast, Black-compatible Python formatter.
|
||||
Try it today with `ruff format`.
|
||||
Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blog/the-ruff-formatter) and [read the docs](https://docs.astral.sh/ruff/formatter/).
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -18,11 +62,11 @@ Try it today with `ruff format`.
|
||||
- Add fix for `E273` and `E274` ([#8144](https://github.com/astral-sh/ruff/pull/8144))
|
||||
- Add fix for `E275` ([#8133](https://github.com/astral-sh/ruff/pull/8133))
|
||||
- Update `SIM401` to catch ternary operations ([#7415](https://github.com/astral-sh/ruff/pull/7415))
|
||||
- Update `E721` to allow `is` and `is` not for direct type comparisons ([#7905](https://github.com/astral-sh/ruff/pull/7905))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113))
|
||||
- Update `E721` to allow `is` and `is` not for direct type comparisons ([#7905](https://github.com/astral-sh/ruff/pull/7905))
|
||||
- Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140))
|
||||
- Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097))
|
||||
- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108))
|
||||
|
||||
@@ -114,7 +114,7 @@ such that all crates are contained in a flat `crates` directory.
|
||||
The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at
|
||||
`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you.
|
||||
|
||||
At time of writing, the repository includes the following crates:
|
||||
At the time of writing, the repository includes the following crates:
|
||||
|
||||
- `crates/ruff_linter`: library crate containing all lint rules and the core logic for running them.
|
||||
If you're working on a rule, this is the crate for you.
|
||||
@@ -877,5 +877,5 @@ By default, `src` is set to the project root. In the above example, we'd want to
|
||||
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
|
||||
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
|
||||
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
|
||||
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
|
||||
the same-package heuristic; but if your project contains multiple packages, you'll want to set `src`
|
||||
explicitly.
|
||||
|
||||
73
Cargo.lock
generated
73
Cargo.lock
generated
@@ -313,9 +313,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.6"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -323,9 +323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.6"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -376,9 +376,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.2"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -388,9 +388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1262,9 +1262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1309,9 +1309,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -1801,9 +1801,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
|
||||
checksum = "0774c13ff0b8b7ebb4791c050c497aefcfe3f6a222c0829c7017161ed38391ff"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
@@ -1912,6 +1912,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
@@ -2051,7 +2060,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2072,7 +2081,6 @@ dependencies = [
|
||||
"insta-cmd",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
"itoa",
|
||||
"log",
|
||||
"mimalloc",
|
||||
"notify",
|
||||
@@ -2188,7 +2196,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
@@ -2381,6 +2389,7 @@ dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"memchr",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -2438,7 +2447,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2550,9 +2559,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.10"
|
||||
version = "0.38.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
@@ -2664,9 +2673,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2684,9 +2693,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2883,13 +2892,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -3322,9 +3331,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
@@ -3334,9 +3343,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
||||
checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -15,7 +15,7 @@ license = "MIT"
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
@@ -34,7 +34,7 @@ quote = { version = "1.0.23" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
@@ -52,7 +52,7 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode_names2 = { version = "1.2.0" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -33,7 +33,7 @@ An extremely fast Python linter and code formatter, written in Rust.
|
||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations
|
||||
of popular Flake8 plugins, like flake8-bugbear
|
||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
|
||||
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for
|
||||
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
|
||||
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
|
||||
|
||||
@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
# Run the Ruff linter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.2
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
# Run the Ruff linter.
|
||||
- id: ruff
|
||||
@@ -238,7 +238,7 @@ quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
magic-trailing-comma = "respect"
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
@@ -409,6 +409,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Mypy](https://github.com/python/mypy)
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [PDM](https://github.com/pdm-project/pdm)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -44,7 +44,6 @@ glob = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
itoa = { version = "1.0.6" }
|
||||
log = { workspace = true }
|
||||
notify = { version = "6.1.1" }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
@@ -68,7 +67,7 @@ assert_cmd = { version = "2.0.8" }
|
||||
colored = { workspace = true, features = ["no-color"]}
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
tempfile = "3.6.0"
|
||||
tempfile = "3.8.1"
|
||||
test-case = { workspace = true }
|
||||
ureq = { version = "2.8.0", features = [] }
|
||||
|
||||
|
||||
@@ -11,6 +11,41 @@
|
||||
"maths = (numpy.arange(100)**2).sum()\n",
|
||||
"stats= numpy.asarray([1,2,3,4]).median()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "83a0b1b8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"A markdown cell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ae12f012",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# A cell with IPython escape command\n",
|
||||
"def some_function(foo, bar):\n",
|
||||
" pass\n",
|
||||
"%matplotlib inline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "10f3bbf9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"foo = %pwd\n",
|
||||
"def some_function(foo,bar,):\n",
|
||||
" # Another cell with IPython escape command\n",
|
||||
" foo = %pwd\n",
|
||||
" print(foo)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -50,7 +50,11 @@ pub enum Command {
|
||||
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
format: HelpFormat,
|
||||
output_format: HelpFormat,
|
||||
|
||||
/// Output format (Deprecated: Use `--output-format` instead).
|
||||
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||
format: Option<HelpFormat>,
|
||||
},
|
||||
/// List or describe the available configuration options.
|
||||
Config { option: Option<String> },
|
||||
@@ -58,7 +62,11 @@ pub enum Command {
|
||||
Linter {
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
format: HelpFormat,
|
||||
output_format: HelpFormat,
|
||||
|
||||
/// Output format (Deprecated: Use `--output-format` instead).
|
||||
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||
format: Option<HelpFormat>,
|
||||
},
|
||||
/// Clear any caches in the current directory and any subdirectories.
|
||||
#[clap(alias = "--clean")]
|
||||
@@ -499,6 +507,7 @@ impl CheckCommand {
|
||||
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,
|
||||
@@ -619,6 +628,7 @@ pub struct CliOverrides {
|
||||
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>>,
|
||||
@@ -649,6 +659,12 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::{self, File};
|
||||
use std::hash::Hasher;
|
||||
@@ -20,7 +19,7 @@ use serde::{Deserialize, Serialize};
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::warn_user;
|
||||
use ruff_linter::{warn_user, VERSION};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
@@ -102,9 +101,8 @@ impl Cache {
|
||||
pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Self {
|
||||
debug_assert!(package_root.is_absolute(), "package root not canonicalized");
|
||||
|
||||
let mut buf = itoa::Buffer::new();
|
||||
let key = Path::new(buf.format(cache_key(&package_root, settings)));
|
||||
let path = PathBuf::from_iter([&settings.cache_dir, Path::new("content"), key]);
|
||||
let key = format!("{}", cache_key(&package_root, settings));
|
||||
let path = PathBuf::from_iter([&settings.cache_dir, Path::new(VERSION), Path::new(&key)]);
|
||||
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
@@ -142,7 +140,7 @@ impl Cache {
|
||||
fn empty(path: PathBuf, package_root: PathBuf) -> Self {
|
||||
let package = PackageCache {
|
||||
package_root,
|
||||
files: HashMap::new(),
|
||||
files: FxHashMap::default(),
|
||||
};
|
||||
Cache::new(path, package)
|
||||
}
|
||||
@@ -294,7 +292,7 @@ struct PackageCache {
|
||||
/// single file "packages", e.g. scripts.
|
||||
package_root: PathBuf,
|
||||
/// Mapping of source file path to it's cached data.
|
||||
files: HashMap<RelativePathBuf, FileCache>,
|
||||
files: FxHashMap<RelativePathBuf, FileCache>,
|
||||
}
|
||||
|
||||
/// On disk representation of the cache per source file.
|
||||
@@ -350,16 +348,16 @@ struct FileCacheData {
|
||||
/// version.
|
||||
fn cache_key(package_root: &Path, settings: &Settings) -> u64 {
|
||||
let mut hasher = CacheKeyHasher::new();
|
||||
env!("CARGO_PKG_VERSION").cache_key(&mut hasher);
|
||||
package_root.cache_key(&mut hasher);
|
||||
settings.cache_key(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Initialize the cache at the specified `Path`.
|
||||
pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
// Create the cache directories.
|
||||
fs::create_dir_all(path.join("content"))?;
|
||||
fs::create_dir_all(path.join(VERSION))?;
|
||||
|
||||
// Add the CACHEDIR.TAG.
|
||||
if !cachedir::is_tagged(path)? {
|
||||
|
||||
@@ -82,7 +82,7 @@ pub(crate) fn check(
|
||||
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
if !resolved_file.is_root()
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
resolved_file.path(),
|
||||
resolved_file.file_name(),
|
||||
|
||||
@@ -18,17 +18,19 @@ pub(crate) fn check_stdin(
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
let lint_settings = &pyproject_config.settings.linter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
return Ok(Diagnostics::default());
|
||||
let lint_settings = &pyproject_config.settings.linter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
|
||||
@@ -11,7 +11,7 @@ use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use similar::TextDiff;
|
||||
use rustc_hash::FxHashSet;
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
@@ -19,12 +19,11 @@ use ruff_diagnostics::SourceMap;
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::rules::isort;
|
||||
use ruff_linter::settings::rule_table::RuleTable;
|
||||
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};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
||||
@@ -107,7 +106,7 @@ pub(crate) fn format(
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let (mut results, mut errors): (Vec<_>, Vec<_>) = paths
|
||||
let (results, mut errors): (Vec<_>, Vec<_>) = paths
|
||||
.par_iter()
|
||||
.filter_map(|entry| {
|
||||
match entry {
|
||||
@@ -118,14 +117,14 @@ pub(crate) fn format(
|
||||
return None;
|
||||
};
|
||||
|
||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
||||
let settings = resolver.resolve(path, &pyproject_config);
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if !resolved_file.is_root()
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
path,
|
||||
resolved_file.file_name(),
|
||||
&resolved_settings.formatter.exclude,
|
||||
&settings.formatter.exclude,
|
||||
)
|
||||
{
|
||||
return None;
|
||||
@@ -140,13 +139,7 @@ pub(crate) fn format(
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(
|
||||
path,
|
||||
&resolved_settings.formatter,
|
||||
source_type,
|
||||
mode,
|
||||
cache,
|
||||
)
|
||||
format_path(path, &settings.formatter, source_type, mode, cache)
|
||||
}) {
|
||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||
path: resolved_file.path().to_path_buf(),
|
||||
@@ -168,27 +161,6 @@ pub(crate) fn format(
|
||||
});
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Make output deterministic, at least as long as we have a path
|
||||
results.sort_unstable_by(|x, y| x.path.cmp(&y.path));
|
||||
errors.sort_by(|x, y| {
|
||||
fn get_key(error: &FormatCommandError) -> Option<&PathBuf> {
|
||||
match &error {
|
||||
FormatCommandError::Ignore(ignore) => {
|
||||
if let ignore::Error::WithPath { path, .. } = ignore {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
FormatCommandError::Panic(path, _)
|
||||
| FormatCommandError::Read(path, _)
|
||||
| FormatCommandError::Format(path, _)
|
||||
| FormatCommandError::Write(path, _) => path.as_ref(),
|
||||
}
|
||||
}
|
||||
get_key(x).cmp(&get_key(y))
|
||||
});
|
||||
|
||||
debug!(
|
||||
"Formatted {} files in {:.2?}",
|
||||
results.len() + errors.len(),
|
||||
@@ -198,15 +170,21 @@ pub(crate) fn format(
|
||||
caches.persist()?;
|
||||
|
||||
// Report on any errors.
|
||||
errors.sort_unstable_by(|a, b| a.path().cmp(&b.path()));
|
||||
|
||||
for error in &errors {
|
||||
error!("{error}");
|
||||
}
|
||||
|
||||
results.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
let results = FormatResults::new(results.as_slice(), mode);
|
||||
|
||||
if mode.is_diff() {
|
||||
results.write_diff(&mut stdout().lock())?;
|
||||
match mode {
|
||||
FormatMode::Write => {}
|
||||
FormatMode::Check => {
|
||||
results.write_changed(&mut stdout().lock())?;
|
||||
}
|
||||
FormatMode::Diff => {
|
||||
results.write_diff(&mut stdout().lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Report on the formatting changes.
|
||||
@@ -470,27 +448,51 @@ impl<'a> FormatResults<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a diff of the formatting changes to the given writer.
|
||||
fn write_diff(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
for result in self.results {
|
||||
if let FormatResult::Diff {
|
||||
unformatted,
|
||||
formatted,
|
||||
} = &result.result
|
||||
{
|
||||
let text_diff =
|
||||
TextDiff::from_lines(unformatted.source_code(), formatted.source_code());
|
||||
let mut unified_diff = text_diff.unified_diff();
|
||||
unified_diff.header(
|
||||
&fs::relativize_path(&result.path),
|
||||
&fs::relativize_path(&result.path),
|
||||
);
|
||||
unified_diff.to_writer(&mut *f)?;
|
||||
}
|
||||
for (path, unformatted, formatted) in self
|
||||
.results
|
||||
.iter()
|
||||
.filter_map(|result| {
|
||||
if let FormatResult::Diff {
|
||||
unformatted,
|
||||
formatted,
|
||||
} = &result.result
|
||||
{
|
||||
Some((result.path.as_path(), unformatted, formatted))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||
{
|
||||
unformatted.diff(formatted, Some(path), f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a list of the files that would be changed to the given writer.
|
||||
fn write_changed(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
for path in self
|
||||
.results
|
||||
.iter()
|
||||
.filter_map(|result| {
|
||||
if result.result.is_formatted() {
|
||||
Some(result.path.as_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_unstable()
|
||||
{
|
||||
writeln!(f, "Would reformat: {}", fs::relativize_path(path).bold())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a summary of the formatting results to the given writer.
|
||||
fn write_summary(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
// Compute the number of changed and unchanged files.
|
||||
let mut changed = 0u32;
|
||||
@@ -498,14 +500,6 @@ impl<'a> FormatResults<'a> {
|
||||
for result in self.results {
|
||||
match &result.result {
|
||||
FormatResult::Formatted => {
|
||||
// If we're running in check mode, report on any files that would be formatted.
|
||||
if self.mode.is_check() {
|
||||
writeln!(
|
||||
f,
|
||||
"Would reformat: {}",
|
||||
fs::relativize_path(&result.path).bold()
|
||||
)?;
|
||||
}
|
||||
changed += 1;
|
||||
}
|
||||
FormatResult::Unchanged => unchanged += 1,
|
||||
@@ -562,6 +556,26 @@ pub(crate) enum FormatCommandError {
|
||||
Read(Option<PathBuf>, SourceError),
|
||||
Format(Option<PathBuf>, FormatModuleError),
|
||||
Write(Option<PathBuf>, SourceError),
|
||||
Diff(Option<PathBuf>, io::Error),
|
||||
}
|
||||
|
||||
impl FormatCommandError {
|
||||
fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Ignore(err) => {
|
||||
if let ignore::Error::WithPath { path, .. } = err {
|
||||
Some(path.as_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Self::Panic(path, _)
|
||||
| Self::Read(path, _)
|
||||
| Self::Format(path, _)
|
||||
| Self::Write(path, _)
|
||||
| Self::Diff(path, _) => path.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FormatCommandError {
|
||||
@@ -627,6 +641,24 @@ impl Display for FormatCommandError {
|
||||
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||
}
|
||||
}
|
||||
Self::Diff(path, err) => {
|
||||
if let Some(path) = path {
|
||||
write!(
|
||||
f,
|
||||
"{}{}{} {err}",
|
||||
"Failed to generate diff for ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}{} {err}",
|
||||
"Failed to generate diff".bold(),
|
||||
":".bold()
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::Panic(path, err) => {
|
||||
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
@@ -658,64 +690,123 @@ pub(super) fn warn_incompatible_formatter_settings(
|
||||
pyproject_config: &PyprojectConfig,
|
||||
resolver: Option<&Resolver>,
|
||||
) {
|
||||
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
||||
let mut incompatible_rules = FxHashSet::default();
|
||||
for setting in std::iter::once(&pyproject_config.settings)
|
||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||
{
|
||||
let mut incompatible_rules = Vec::new();
|
||||
for rule in [
|
||||
// The formatter might collapse implicit string concatenation on a single line.
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
// Flags missing trailing commas when all arguments are on its own line:
|
||||
// ```python
|
||||
// def args(
|
||||
// aaaaaaaa, bbbbbbbbb, cccccccccc, ddddddddd, eeeeeeee, ffffff, gggggggggggg, hhhh
|
||||
// ):
|
||||
// pass
|
||||
// ```
|
||||
Rule::MissingTrailingComma,
|
||||
] {
|
||||
if setting.linter.rules.enabled(rule) {
|
||||
incompatible_rules.insert(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for incompatible_rule in RuleTable::from_iter([
|
||||
Rule::LineTooLong,
|
||||
Rule::TabIndentation,
|
||||
if !incompatible_rules.is_empty() {
|
||||
let mut rule_names: Vec<_> = incompatible_rules
|
||||
.into_iter()
|
||||
.map(|rule| format!("`{}`", rule.noqa_code()))
|
||||
.collect();
|
||||
rule_names.sort();
|
||||
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
|
||||
}
|
||||
|
||||
// Next, validate settings-specific incompatibilities.
|
||||
for setting in std::iter::once(&pyproject_config.settings)
|
||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
||||
{
|
||||
// Validate all rules that rely on tab styles.
|
||||
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||
&& setting.formatter.indent_style.is_tab()
|
||||
{
|
||||
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
||||
}
|
||||
|
||||
// Validate all rules that rely on tab styles.
|
||||
if setting.linter.rules.enabled(Rule::IndentWithSpaces)
|
||||
&& setting.formatter.indent_style.is_tab()
|
||||
{
|
||||
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
||||
}
|
||||
|
||||
// Validate all rules that rely on custom indent widths.
|
||||
if setting.linter.rules.any_enabled(&[
|
||||
Rule::IndentationWithInvalidMultiple,
|
||||
Rule::IndentationWithInvalidMultipleComment,
|
||||
Rule::OverIndented,
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
.iter_enabled()
|
||||
]) && setting.formatter.indent_width.value() != 4
|
||||
{
|
||||
if setting.linter.rules.enabled(incompatible_rule) {
|
||||
incompatible_rules.push(format!("'{}'", incompatible_rule.noqa_code()));
|
||||
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
|
||||
}
|
||||
|
||||
// Validate all rules that rely on quote styles.
|
||||
if setting
|
||||
.linter
|
||||
.rules
|
||||
.any_enabled(&[Rule::BadQuotesInlineString, Rule::AvoidableEscapedQuote])
|
||||
{
|
||||
match (
|
||||
setting.linter.flake8_quotes.inline_quotes,
|
||||
setting.formatter.quote_style,
|
||||
) {
|
||||
(Quote::Double, QuoteStyle::Single) => {
|
||||
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||
}
|
||||
(Quote::Single, QuoteStyle::Double) => {
|
||||
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !incompatible_rules.is_empty() {
|
||||
incompatible_rules.sort();
|
||||
warn!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.", incompatible_rules.join(", "));
|
||||
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
|
||||
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
|
||||
{
|
||||
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
|
||||
}
|
||||
|
||||
let mut incompatible_options = Vec::new();
|
||||
|
||||
let isort_defaults = isort::settings::Settings::default();
|
||||
|
||||
if setting.linter.isort.force_single_line != isort_defaults.force_single_line {
|
||||
incompatible_options.push("'isort.force-single-line'");
|
||||
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
|
||||
&& setting.linter.flake8_quotes.docstring_quotes == Quote::Single
|
||||
{
|
||||
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
|
||||
}
|
||||
|
||||
if setting.linter.isort.force_wrap_aliases != isort_defaults.force_wrap_aliases {
|
||||
incompatible_options.push("'isort.force-wrap-aliases'");
|
||||
}
|
||||
// Validate all isort settings.
|
||||
if setting.linter.rules.enabled(Rule::UnsortedImports) {
|
||||
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
|
||||
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
|
||||
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
|
||||
warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
|
||||
}
|
||||
|
||||
if setting.linter.isort.lines_after_imports != isort_defaults.lines_after_imports {
|
||||
incompatible_options.push("'isort.lines-after-imports'");
|
||||
}
|
||||
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
|
||||
if setting.linter.isort.lines_between_types > 1 {
|
||||
warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
|
||||
}
|
||||
|
||||
if setting.linter.isort.lines_between_types != isort_defaults.lines_between_types {
|
||||
incompatible_options.push("'isort.lines_between_types'");
|
||||
}
|
||||
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
|
||||
// This isn't relevant when using `force-single-line`, since isort will never include a trailing comma in that case.
|
||||
if setting.formatter.magic_trailing_comma.is_ignore()
|
||||
&& !setting.linter.isort.force_single_line
|
||||
{
|
||||
if setting.linter.isort.force_wrap_aliases {
|
||||
warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
|
||||
}
|
||||
|
||||
if setting.linter.isort.split_on_trailing_comma != isort_defaults.split_on_trailing_comma {
|
||||
incompatible_options.push("'isort.split_on_trailing_comma'");
|
||||
}
|
||||
|
||||
if !incompatible_options.is_empty() {
|
||||
warn!("The following isort options may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.", incompatible_options.join(", "));
|
||||
if setting.linter.isort.split_on_trailing_comma {
|
||||
warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use ruff_linter::fs;
|
||||
use similar::TextDiff;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
@@ -33,17 +31,19 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
|
||||
let mode = FormatMode::from_cli(cli);
|
||||
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let format_settings = &pyproject_config.settings.formatter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
return Ok(ExitStatus::Success);
|
||||
let format_settings = &pyproject_config.settings.formatter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,9 @@ fn format_source_code(
|
||||
}
|
||||
FormatMode::Check => {}
|
||||
FormatMode::Diff => {
|
||||
let mut writer = stdout().lock();
|
||||
let text_diff =
|
||||
TextDiff::from_lines(source_kind.source_code(), formatted.source_code());
|
||||
let mut unified_diff = text_diff.unified_diff();
|
||||
if let Some(path) = path {
|
||||
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
|
||||
}
|
||||
unified_diff.to_writer(&mut writer).unwrap();
|
||||
source_kind
|
||||
.diff(formatted, path, &mut stdout().lock())
|
||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
},
|
||||
FormattedSource::Unchanged => {
|
||||
|
||||
@@ -188,13 +188,8 @@ pub(crate) fn lint_path(
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> Result<Diagnostics> {
|
||||
// Check the cache.
|
||||
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
|
||||
// side-effects that aren't captured in the cache. (In practice, it's fine
|
||||
// to cache `fixer::Mode::Apply`, since a file either has no fixes, or we'll
|
||||
// write the fixes to disk, thus invalidating the cache. But it's a bit hard
|
||||
// to reason about. We need to come up with a better solution here.)
|
||||
let caching = match cache {
|
||||
Some(cache) if noqa.into() && fix_mode.is_generate() => {
|
||||
Some(cache) if noqa.into() => {
|
||||
let relative_path = cache
|
||||
.relative_path(path)
|
||||
.expect("wrong package cache for file");
|
||||
@@ -204,7 +199,17 @@ pub(crate) fn lint_path(
|
||||
.get(relative_path, &cache_key)
|
||||
.and_then(|entry| entry.to_diagnostics(path));
|
||||
if let Some(diagnostics) = cached_diagnostics {
|
||||
return Ok(diagnostics);
|
||||
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||
// and writing the diff to stdout, respectively). If a file has diagnostics, we
|
||||
// need to avoid reading from and writing to the cache in these modes.
|
||||
if match fix_mode {
|
||||
flags::FixMode::Generate => true,
|
||||
flags::FixMode::Apply | flags::FixMode::Diff => {
|
||||
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
|
||||
}
|
||||
} {
|
||||
return Ok(diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
// Stash the file metadata for later so when we update the cache it reflects the prerun
|
||||
@@ -304,15 +309,25 @@ pub(crate) fn lint_path(
|
||||
if let Some((cache, relative_path, key)) = caching {
|
||||
// We don't cache parsing errors.
|
||||
if parse_error.is_none() {
|
||||
cache.update_lint(
|
||||
relative_path.to_owned(),
|
||||
&key,
|
||||
LintCacheData::from_messages(
|
||||
&messages,
|
||||
imports.clone(),
|
||||
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||
),
|
||||
);
|
||||
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
|
||||
// and writing the diff to stdout, respectively). If a file has diagnostics, we
|
||||
// need to avoid reading from and writing to the cache in these modes.
|
||||
if match fix_mode {
|
||||
flags::FixMode::Generate => true,
|
||||
flags::FixMode::Apply | flags::FixMode::Diff => {
|
||||
messages.is_empty() && fixed.is_empty()
|
||||
}
|
||||
} {
|
||||
cache.update_lint(
|
||||
relative_path.to_owned(),
|
||||
&key,
|
||||
LintCacheData::from_messages(
|
||||
&messages,
|
||||
imports.clone(),
|
||||
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||
use ruff_workspace::Settings;
|
||||
|
||||
use crate::args::{Args, CheckCommand, Command, FormatCommand};
|
||||
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
|
||||
use crate::printer::{Flags as PrinterFlags, Printer};
|
||||
|
||||
pub mod args;
|
||||
@@ -101,6 +101,15 @@ fn is_stdin(files: &[PathBuf], stdin_filename: Option<&Path>) -> bool {
|
||||
file == Path::new("-")
|
||||
}
|
||||
|
||||
/// Get the actual value of the `format` desired from either `output_format`
|
||||
/// or `format`, and warn the user if they're using the deprecated form.
|
||||
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
|
||||
if format.is_some() {
|
||||
warn_user!("The `--format` argument is deprecated. Use `--output-format` instead.");
|
||||
}
|
||||
format.unwrap_or(output_format)
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
Args {
|
||||
command,
|
||||
@@ -141,12 +150,18 @@ pub fn run(
|
||||
commands::version::version(output_format)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Rule { rule, all, format } => {
|
||||
Command::Rule {
|
||||
rule,
|
||||
all,
|
||||
format,
|
||||
mut output_format,
|
||||
} => {
|
||||
output_format = resolve_help_output_format(output_format, format);
|
||||
if all {
|
||||
commands::rule::rules(format)?;
|
||||
commands::rule::rules(output_format)?;
|
||||
}
|
||||
if let Some(rule) = rule {
|
||||
commands::rule::rule(rule, format)?;
|
||||
commands::rule::rule(rule, output_format)?;
|
||||
}
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
@@ -154,8 +169,12 @@ pub fn run(
|
||||
commands::config::config(option.as_deref())?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Linter { format } => {
|
||||
commands::linter::linter(format)?;
|
||||
Command::Linter {
|
||||
format,
|
||||
mut output_format,
|
||||
} => {
|
||||
output_format = resolve_help_output_format(output_format, format);
|
||||
commands::linter::linter(output_format)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Clean => {
|
||||
|
||||
@@ -188,6 +188,73 @@ OTHER = "OTHER"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-exclude = ["out"]
|
||||
|
||||
[format]
|
||||
exclude = ["test.py", "generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.py"),
|
||||
r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Excluded file but passed to the CLI directly, should be formatted
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("generated.py"),
|
||||
r#"NUMBERS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
]
|
||||
OTHER = "OTHER"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let out_dir = tempdir.path().join("out");
|
||||
fs::create_dir(&out_dir)?;
|
||||
|
||||
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--no-cache", "--force-exclude", "--check", "--config"])
|
||||
.arg(ruff_toml.file_name().unwrap())
|
||||
// Explicitly pass test.py, should be respect the `format.exclude` when `--force-exclude` is present
|
||||
.arg(test_path.file_name().unwrap())
|
||||
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: main.py
|
||||
1 file would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -209,6 +276,43 @@ exclude = ["generated.py"]
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
ignore = ["Q000", "Q001", "Q002", "Q003"]
|
||||
|
||||
[format]
|
||||
exclude = ["generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "--force-exclude", "-"])
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
@@ -230,7 +334,9 @@ fn format_option_inheritance() -> Result<()> {
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend = "base.toml"
|
||||
extend-select = ["Q000"]
|
||||
|
||||
[lint]
|
||||
extend-select = ["COM812"]
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
@@ -273,7 +379,7 @@ if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'Q000'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -359,15 +465,27 @@ fn conflicting_options() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
indent-width = 2
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212"]
|
||||
|
||||
[isort]
|
||||
force-single-line = true
|
||||
force-wrap-aliases = true
|
||||
lines-after-imports = 0
|
||||
[lint.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "single"
|
||||
multiline-quotes = "single"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = true
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -389,8 +507,16 @@ def say_hy(name: str):
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'COM812', 'COM819', 'D206', 'E501', 'ISC001', 'Q000', 'Q001', 'Q002', 'Q003', 'W191'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following isort options may cause conflicts when used with the formatter: 'isort.force-single-line', 'isort.force-wrap-aliases', 'isort.lines-after-imports', 'isort.lines_between_types'. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -402,15 +528,27 @@ fn conflicting_options_stdin() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
indent-width = 2
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212"]
|
||||
|
||||
[isort]
|
||||
force-single-line = true
|
||||
force-wrap-aliases = true
|
||||
lines-after-imports = 0
|
||||
[lint.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "single"
|
||||
multiline-quotes = "single"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = true
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -425,14 +563,112 @@ def say_hy(name: str):
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")
|
||||
print(f"Hy {name}")
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'COM812', 'COM819', 'D206', 'E501', 'ISC001', 'Q000', 'Q001', 'Q002', 'Q003', 'W191'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following isort options may cause conflicts when used with the formatter: 'isort.force-single-line', 'isort.force-wrap-aliases', 'isort.lines-after-imports', 'isort.lines_between_types'. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
|
||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_linter_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212", "COM812", "ISC001"]
|
||||
|
||||
[lint.isort]
|
||||
lines-after-imports = 2
|
||||
lines-between-types = 1
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = false
|
||||
quote-style = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg(test_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_rules_default_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg(test_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diff() {
|
||||
let args = ["format", "--no-cache", "--isolated", "--diff"];
|
||||
@@ -452,8 +688,8 @@ fn test_diff() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
--- resources/test/fixtures/unformatted.ipynb
|
||||
+++ resources/test/fixtures/unformatted.ipynb
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 1
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 1
|
||||
@@ -1,3 +1,4 @@
|
||||
import numpy
|
||||
-maths = (numpy.arange(100)**2).sum()
|
||||
@@ -461,6 +697,30 @@ fn test_diff() {
|
||||
+
|
||||
+maths = (numpy.arange(100) ** 2).sum()
|
||||
+stats = numpy.asarray([1, 2, 3, 4]).median()
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 3
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 3
|
||||
@@ -1,4 +1,6 @@
|
||||
# A cell with IPython escape command
|
||||
def some_function(foo, bar):
|
||||
pass
|
||||
+
|
||||
+
|
||||
%matplotlib inline
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 4
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 4
|
||||
@@ -1,5 +1,10 @@
|
||||
foo = %pwd
|
||||
-def some_function(foo,bar,):
|
||||
+
|
||||
+
|
||||
+def some_function(
|
||||
+ foo,
|
||||
+ bar,
|
||||
+):
|
||||
# Another cell with IPython escape command
|
||||
foo = %pwd
|
||||
print(foo)
|
||||
|
||||
--- resources/test/fixtures/unformatted.py
|
||||
+++ resources/test/fixtures/unformatted.py
|
||||
@@ -1,3 +1,3 @@
|
||||
@@ -469,6 +729,7 @@ fn test_diff() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
2 files would be reformatted, 1 file left unchanged
|
||||
"###);
|
||||
@@ -498,6 +759,7 @@ fn test_diff_no_change() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
1 file would be reformatted
|
||||
"###
|
||||
@@ -531,6 +793,7 @@ fn test_diff_stdin_unformatted() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -262,9 +262,13 @@ from test import say_hy
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
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 -----
|
||||
"###);
|
||||
@@ -308,3 +312,87 @@ _ = "---------------------------------------------------------------------------
|
||||
"###);
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
tempfile = "3.6.0"
|
||||
tempfile = "3.8.1"
|
||||
toml = { workspace = true, features = ["parse"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-indicatif = { workspace = true }
|
||||
|
||||
@@ -33,7 +33,7 @@ use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
@@ -871,9 +871,7 @@ struct BlackOptions {
|
||||
line_length: NonZeroU16,
|
||||
#[serde(alias = "skip-magic-trailing-comma")]
|
||||
skip_magic_trailing_comma: bool,
|
||||
#[allow(unused)]
|
||||
#[serde(alias = "force-exclude")]
|
||||
force_exclude: Option<String>,
|
||||
preview: bool,
|
||||
}
|
||||
|
||||
impl Default for BlackOptions {
|
||||
@@ -881,7 +879,7 @@ impl Default for BlackOptions {
|
||||
Self {
|
||||
line_length: NonZeroU16::new(88).unwrap(),
|
||||
skip_magic_trailing_comma: false,
|
||||
force_exclude: None,
|
||||
preview: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -929,6 +927,11 @@ impl BlackOptions {
|
||||
} else {
|
||||
MagicTrailingComma::Respect
|
||||
})
|
||||
.with_preview(if self.preview {
|
||||
PreviewMode::Enabled
|
||||
} else {
|
||||
PreviewMode::Disabled
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push_str(&format!(
|
||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
format!(".{set_name}")
|
||||
if set_name == "format" {
|
||||
String::from(".format")
|
||||
} else {
|
||||
format!(".lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
|
||||
@@ -22,14 +22,16 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
for rule in rules {
|
||||
let fix_token = match rule.fixable() {
|
||||
FixAvailability::Always | FixAvailability::Sometimes => {
|
||||
format!("<span style='opacity: 1'>{FIX_SYMBOL}</span>")
|
||||
format!("<span title='Automatic fix available'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => {
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
|
||||
};
|
||||
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
||||
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
} else {
|
||||
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
|
||||
};
|
||||
let status_token = format!("{fix_token} {preview_token}");
|
||||
|
||||
@@ -62,7 +64,7 @@ pub(crate) fn generate() -> String {
|
||||
table_out.push('\n');
|
||||
|
||||
table_out.push_str(&format!(
|
||||
"The {PREVIEW_SYMBOL} emoji indicates that a rule in [\"preview\"](faq.md#what-is-preview)."
|
||||
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
|
||||
));
|
||||
table_out.push('\n');
|
||||
table_out.push('\n');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -53,7 +53,7 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.7.0" }
|
||||
pyproject-toml = { version = "0.8.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
@@ -79,7 +79,7 @@ pretty_assertions = "1.3.0"
|
||||
test-case = { workspace = true }
|
||||
# Disable colored output in tests
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
tempfile = "3.6.0"
|
||||
tempfile = "3.8.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -79,3 +79,6 @@ from ZeroDivisionError
|
||||
raise IndexError() from ZeroDivisionError
|
||||
|
||||
raise IndexError();
|
||||
|
||||
# RSE102
|
||||
raise Foo()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.isort]
|
||||
[tool.ruff.lint.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
known-local-folder = ["ruff"]
|
||||
|
||||
@@ -12,3 +12,19 @@ dict['key'] = list[index]
|
||||
# This is not prohibited by PEP8, but avoid it.
|
||||
class Foo (Bar, Baz):
|
||||
pass
|
||||
|
||||
|
||||
def fetch_name () -> Union[str, None]:
|
||||
"""Fetch name from --person-name in sys.argv.
|
||||
|
||||
Returns:
|
||||
name of the person if available, otherwise None
|
||||
"""
|
||||
test = len(5)
|
||||
Logger.info(test)
|
||||
# test commented code
|
||||
# Logger.info("test code")
|
||||
for i in range (0, len (sys.argv)) :
|
||||
if sys.argv[i] == "--name" :
|
||||
return sys.argv[i + 1]
|
||||
return None
|
||||
|
||||
@@ -40,5 +40,11 @@ f"{(a:=1)}"
|
||||
f"{(lambda x:x)}"
|
||||
f"normal{f"{a:.3f}"}normal"
|
||||
|
||||
#: Okay
|
||||
snapshot.file_uri[len(f's3://{self.s3_bucket_name}/'):]
|
||||
|
||||
#: E231
|
||||
{len(f's3://{self.s3_bucket_name}/'):1}
|
||||
|
||||
#: Okay
|
||||
a = (1,
|
||||
|
||||
@@ -49,6 +49,13 @@ class Apples:
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
# Added in Python 3.12
|
||||
def __buffer__(self):
|
||||
return memoryview(b'')
|
||||
|
||||
def __release_buffer__(self, buf):
|
||||
pass
|
||||
|
||||
# Allow dunder methods recommended by attrs.
|
||||
def __attrs_post_init__(self):
|
||||
pass
|
||||
@@ -63,6 +70,10 @@ class Apples:
|
||||
def __html__(self):
|
||||
pass
|
||||
|
||||
# Allow Python's __index__
|
||||
def __index__(self):
|
||||
pass
|
||||
|
||||
# Allow _missing_, used by enum.Enum.
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
|
||||
34
crates/ruff_linter/resources/test/fixtures/pylint/bad_open_mode.py
vendored
Normal file
34
crates/ruff_linter/resources/test/fixtures/pylint/bad_open_mode.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import pathlib
|
||||
|
||||
NAME = "foo.bar"
|
||||
open(NAME, "wb")
|
||||
open(NAME, "w", encoding="utf-8")
|
||||
open(NAME, "rb")
|
||||
open(NAME, "x", encoding="utf-8")
|
||||
open(NAME, "br")
|
||||
open(NAME, "+r", encoding="utf-8")
|
||||
open(NAME, "xb")
|
||||
open(NAME, "rwx") # [bad-open-mode]
|
||||
open(NAME, mode="rwx") # [bad-open-mode]
|
||||
open(NAME, "rwx", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "rr", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "+", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "xw", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "ab+")
|
||||
open(NAME, "a+b")
|
||||
open(NAME, "+ab")
|
||||
open(NAME, "+rUb")
|
||||
open(NAME, "x+b")
|
||||
open(NAME, "Ua", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "Ur++", encoding="utf-8") # [bad-open-mode]
|
||||
open(NAME, "Ut", encoding="utf-8")
|
||||
open(NAME, "Ubr")
|
||||
|
||||
mode = "rw"
|
||||
open(NAME, mode)
|
||||
|
||||
pathlib.Path(NAME).open("wb")
|
||||
pathlib.Path(NAME).open(mode)
|
||||
pathlib.Path(NAME).open("rwx") # [bad-open-mode]
|
||||
pathlib.Path(NAME).open(mode="rwx") # [bad-open-mode]
|
||||
pathlib.Path(NAME).open("rwx", encoding="utf-8") # [bad-open-mode]
|
||||
22
crates/ruff_linter/resources/test/fixtures/pylint/import_outside_top_level.py
vendored
Normal file
22
crates/ruff_linter/resources/test/fixtures/pylint/import_outside_top_level.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# Verify that statements nested in conditionals (such as top-level type-checking blocks)
|
||||
# are still considered top-level
|
||||
if TYPE_CHECKING:
|
||||
import string
|
||||
|
||||
|
||||
def import_in_function():
|
||||
import symtable # [import-outside-toplevel]
|
||||
import os, sys # [import-outside-toplevel]
|
||||
import time as thyme # [import-outside-toplevel]
|
||||
import random as rand, socket as sock # [import-outside-toplevel]
|
||||
from collections import defaultdict # [import-outside-toplevel]
|
||||
from math import sin as sign, cos as cosplay # [import-outside-toplevel]
|
||||
|
||||
|
||||
class ClassWithImports:
|
||||
import tokenize # [import-outside-toplevel]
|
||||
|
||||
def __init__(self):
|
||||
import trace # [import-outside-toplevel]
|
||||
56
crates/ruff_linter/resources/test/fixtures/pylint/useless_with_lock.py
vendored
Normal file
56
crates/ruff_linter/resources/test/fixtures/pylint/useless_with_lock.py
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
import threading
|
||||
from threading import Lock, RLock, Condition, Semaphore, BoundedSemaphore
|
||||
|
||||
|
||||
with threading.Lock(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with Lock(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with threading.Lock() as this_shouldnt_matter: # [useless-with-lock]
|
||||
...
|
||||
|
||||
with threading.RLock(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with RLock(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with threading.Condition(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with Condition(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with threading.Semaphore(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with Semaphore(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with threading.BoundedSemaphore(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
with BoundedSemaphore(): # [useless-with-lock]
|
||||
...
|
||||
|
||||
lock = threading.Lock()
|
||||
with lock: # this is ok
|
||||
...
|
||||
|
||||
rlock = threading.RLock()
|
||||
with rlock: # this is ok
|
||||
...
|
||||
|
||||
cond = threading.Condition()
|
||||
with cond: # this is ok
|
||||
...
|
||||
|
||||
sem = threading.Semaphore()
|
||||
with sem: # this is ok
|
||||
...
|
||||
|
||||
b_sem = threading.BoundedSemaphore()
|
||||
with b_sem: # this is ok
|
||||
...
|
||||
@@ -5,4 +5,6 @@ extend-exclude = [
|
||||
"migrations",
|
||||
"with_excluded_file/other_excluded_file.py",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
51
crates/ruff_linter/resources/test/fixtures/refurb/FURB168.py
vendored
Normal file
51
crates/ruff_linter/resources/test/fixtures/refurb/FURB168.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
foo: object
|
||||
|
||||
# Errors.
|
||||
|
||||
if isinstance(foo, type(None)):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (type(None))):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (type(None), type(None), type(None))):
|
||||
pass
|
||||
|
||||
if isinstance(foo, None | None):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (None | None)):
|
||||
pass
|
||||
|
||||
if isinstance(foo, None | type(None)):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (None | type(None))):
|
||||
pass
|
||||
|
||||
# A bit contrived, but is both technically valid and equivalent to the above.
|
||||
if isinstance(foo, (type(None) | ((((type(None))))) | ((None | type(None))))):
|
||||
pass
|
||||
|
||||
|
||||
# Okay.
|
||||
|
||||
if isinstance(foo, int):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (int)):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (int, str)):
|
||||
pass
|
||||
|
||||
if isinstance(foo, (int, type(None), str)):
|
||||
pass
|
||||
|
||||
# This is a TypeError, which the rule ignores.
|
||||
if isinstance(foo, None):
|
||||
pass
|
||||
|
||||
# This is also a TypeError, which the rule ignores.
|
||||
if isinstance(foo, (None,)):
|
||||
pass
|
||||
@@ -21,6 +21,9 @@ def f() -> None:
|
||||
# Invalid (but external)
|
||||
d = 1 # noqa: F841, V101
|
||||
|
||||
# Invalid (but external)
|
||||
d = 1 # noqa: V500
|
||||
|
||||
# fmt: off
|
||||
# Invalid - no space before #
|
||||
d = 1# noqa: E501
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[tool.ruff]
|
||||
src = [".", "python_modules/*"]
|
||||
exclude = ["examples/excluded"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = ["I001"]
|
||||
extend-ignore = ["F841"]
|
||||
|
||||
@@ -170,7 +170,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
expr.start(),
|
||||
));
|
||||
|
||||
if pydocstyle::helpers::should_ignore_docstring(expr) {
|
||||
if expr.implicit_concatenated {
|
||||
#[allow(deprecated)]
|
||||
let location = checker.locator.compute_source_location(expr.start());
|
||||
warn_user!(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Operator};
|
||||
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -363,20 +363,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
let attr = attr.as_str();
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(val),
|
||||
..
|
||||
}) = value.as_ref()
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value: string, .. }) =
|
||||
value.as_ref()
|
||||
{
|
||||
if attr == "join" {
|
||||
// "...".join(...) call
|
||||
if checker.enabled(Rule::StaticJoinToFString) {
|
||||
flynt::rules::static_join_to_fstring(checker, expr, val);
|
||||
flynt::rules::static_join_to_fstring(checker, expr, string);
|
||||
}
|
||||
} else if attr == "format" {
|
||||
// "...".format(...) call
|
||||
let location = expr.range();
|
||||
match pyflakes::format::FormatSummary::try_from(val.as_ref()) {
|
||||
match pyflakes::format::FormatSummary::try_from(string.as_ref()) {
|
||||
Err(e) => {
|
||||
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
@@ -421,7 +419,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::call(
|
||||
checker, val, location,
|
||||
checker, string, location,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -749,6 +747,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SysExitAlias) {
|
||||
pylint::rules::sys_exit_alias(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::BadOpenMode) {
|
||||
pylint::rules::bad_open_mode(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::BadStrStripCall) {
|
||||
pylint::rules::bad_str_strip_call(checker, func, args);
|
||||
}
|
||||
@@ -908,6 +909,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ExceptionWithoutExcInfo) {
|
||||
flake8_logging::rules::exception_without_exc_info(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::IsinstanceTypeNone) {
|
||||
refurb::rules::isinstance_type_none(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ImplicitCwd) {
|
||||
refurb::rules::no_implicit_cwd(checker, call);
|
||||
}
|
||||
@@ -987,11 +991,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
right,
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = left.as_ref()
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = left.as_ref() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::PercentFormatInvalidFormat,
|
||||
Rule::PercentFormatExpectedMapping,
|
||||
@@ -1228,38 +1228,29 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::NumberLiteral(_) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bytes(_),
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::BytesLiteral(_) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::StringLiteral(string) => {
|
||||
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_bandit::rules::hardcoded_bind_all_interfaces(value, expr.range())
|
||||
flake8_bandit::rules::hardcoded_bind_all_interfaces(string)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, string);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, value.unicode);
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, string);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
|
||||
@@ -530,6 +530,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::ImportOutsideTopLevel) {
|
||||
pylint::rules::import_outside_top_level(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
if let Some(asname) = name.asname.as_ref() {
|
||||
@@ -706,6 +709,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::ImportOutsideTopLevel) {
|
||||
pylint::rules::import_outside_top_level(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for name in names {
|
||||
if let Some(asname) = name.asname.as_ref() {
|
||||
@@ -1186,6 +1192,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ReadWholeFile) {
|
||||
refurb::rules::read_whole_file(checker, with_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UselessWithLock) {
|
||||
pylint::rules::useless_with_lock(checker, with_stmt);
|
||||
}
|
||||
}
|
||||
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
|
||||
@@ -31,9 +31,8 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
|
||||
Suite, UnaryOp,
|
||||
self as ast, Arguments, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
|
||||
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -787,11 +786,7 @@ where
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
{
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
}) = expr
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||
self.deferred.string_type_definitions.push((
|
||||
expr.range(),
|
||||
value,
|
||||
@@ -1186,10 +1181,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
if self.semantic.in_type_definition()
|
||||
&& !self.semantic.in_literal()
|
||||
&& !self.semantic.in_f_string()
|
||||
|
||||
@@ -128,7 +128,10 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
|
||||
if line.matches.iter().any(|match_| *match_ == code)
|
||||
|| settings.external.contains(code)
|
||||
|| settings
|
||||
.external
|
||||
.iter()
|
||||
.any(|external| code.starts_with(external))
|
||||
{
|
||||
valid_codes.push(code);
|
||||
} else {
|
||||
|
||||
@@ -211,6 +211,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "C0205") => (RuleGroup::Stable, rules::pylint::rules::SingleStringSlots),
|
||||
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
|
||||
(Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName),
|
||||
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
|
||||
#[allow(deprecated)]
|
||||
@@ -270,12 +271,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
|
||||
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
|
||||
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode),
|
||||
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Stable, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1510") => (RuleGroup::Stable, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
(Pylint, "W1514") => (RuleGroup::Preview, rules::pylint::rules::UnspecifiedEncoding),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
@@ -935,6 +938,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
|
||||
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
|
||||
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Stmt, Suite};
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
@@ -69,15 +69,15 @@ struct StringLinesVisitor<'a> {
|
||||
|
||||
impl StatementVisitor<'_> for StringLinesVisitor<'_> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
..
|
||||
}) = value.as_ref()
|
||||
{
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value: expr,
|
||||
range: _,
|
||||
}) = stmt
|
||||
{
|
||||
if expr.is_string_literal_expr() {
|
||||
for line in UniversalNewlineIterator::with_offset(
|
||||
self.locator.slice(value.as_ref()),
|
||||
value.start(),
|
||||
self.locator.slice(expr.as_ref()),
|
||||
expr.start(),
|
||||
) {
|
||||
self.string_lines.push(line.start());
|
||||
}
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
//! Extract docstrings from an AST.
|
||||
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||
|
||||
/// Extract a docstring from a function or class body.
|
||||
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&ast::ExprStringLiteral> {
|
||||
let stmt = suite.first()?;
|
||||
// Require the docstring to be a standalone expression.
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
return None;
|
||||
};
|
||||
// Only match strings.
|
||||
if !matches!(
|
||||
value.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some(value)
|
||||
value.as_string_literal_expr()
|
||||
}
|
||||
|
||||
/// Extract a docstring from a `Definition`.
|
||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||
pub(crate) fn extract_docstring<'a>(
|
||||
definition: &'a Definition<'a>,
|
||||
) -> Option<&'a ast::ExprStringLiteral> {
|
||||
match definition {
|
||||
Definition::Module(module) => docstring_from(module.python_ast),
|
||||
Definition::Member(member) => docstring_from(member.body()),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_ast::ExprStringLiteral;
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -14,7 +14,8 @@ pub(crate) mod styles;
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Docstring<'a> {
|
||||
pub(crate) definition: &'a Definition<'a>,
|
||||
pub(crate) expr: &'a Expr,
|
||||
/// The literal AST node representing the docstring.
|
||||
pub(crate) expr: &'a ExprStringLiteral,
|
||||
/// The content of the docstring, including the leading and trailing quotes.
|
||||
pub(crate) contents: &'a str,
|
||||
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use pyproject_toml::{BuildSystem, Project};
|
||||
use pyproject_toml::PyProjectToml;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_source_file::SourceFile;
|
||||
@@ -13,16 +12,6 @@ use crate::rules::ruff::rules::InvalidPyprojectToml;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::IOError;
|
||||
|
||||
/// Unlike [`pyproject_toml::PyProjectToml`], in our case `build_system` is also optional
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct PyProjectToml {
|
||||
/// Build-related data
|
||||
build_system: Option<BuildSystem>,
|
||||
/// Project metadata
|
||||
project: Option<Project>,
|
||||
}
|
||||
|
||||
pub fn lint_pyproject_toml(source_file: SourceFile, settings: &LinterSettings) -> Vec<Message> {
|
||||
let Some(err) = toml::from_str::<PyProjectToml>(source_file.source_text()).err() else {
|
||||
return Vec::default();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Constant;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -79,13 +78,7 @@ pub(crate) fn variable_name_task_id(
|
||||
let keyword = arguments.find_keyword("task_id")?;
|
||||
|
||||
// If the keyword argument is not a string, we can't do anything.
|
||||
let task_id = match &keyword.value {
|
||||
Expr::Constant(constant) => match &constant.value {
|
||||
Constant::Str(ast::StringConstant { value, .. }) => value,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
let ast::ExprStringLiteral { value: task_id, .. } = keyword.value.as_string_literal_expr()?;
|
||||
|
||||
// If the target name is the same as the task_id, no violation.
|
||||
if id == task_id {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -230,16 +230,16 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. })
|
||||
if is_sys(value, "version_info", checker.semantic()) =>
|
||||
{
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(i),
|
||||
..
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
if *i == 0 {
|
||||
if let (
|
||||
[CmpOp::Eq | CmpOp::NotEq],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(n),
|
||||
[Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(n),
|
||||
..
|
||||
})],
|
||||
) = (ops, comparators)
|
||||
@@ -253,8 +253,8 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
} else if *i == 1 {
|
||||
if let (
|
||||
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_),
|
||||
[Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(_),
|
||||
..
|
||||
})],
|
||||
) = (ops, comparators)
|
||||
@@ -274,8 +274,8 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
{
|
||||
if let (
|
||||
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_),
|
||||
[Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(_),
|
||||
..
|
||||
})],
|
||||
) = (ops, comparators)
|
||||
@@ -294,13 +294,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
|
||||
if is_sys(left, "version", checker.semantic()) {
|
||||
if let (
|
||||
[CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE],
|
||||
[Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(s),
|
||||
..
|
||||
})],
|
||||
[Expr::StringLiteral(ast::ExprStringLiteral { value, .. })],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if s.len() == 1 {
|
||||
if value.len() == 1 {
|
||||
if checker.enabled(Rule::SysVersionCmpStr10) {
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -177,8 +177,8 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
step: None,
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
if let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(i),
|
||||
..
|
||||
}) = upper.as_ref()
|
||||
{
|
||||
@@ -194,8 +194,8 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(i),
|
||||
..
|
||||
}) => {
|
||||
if *i == 2 && checker.enabled(Rule::SysVersion2) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
|
||||
use ruff_python_parser::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::Definition;
|
||||
@@ -265,7 +265,7 @@ impl Violation for MissingReturnTypePrivateFunction {
|
||||
/// or `ruff.toml` file:
|
||||
///
|
||||
/// ```toml
|
||||
/// [tool.ruff.flake8-annotations]
|
||||
/// [tool.ruff.lint.flake8-annotations]
|
||||
/// mypy-init-return = true
|
||||
/// ```
|
||||
///
|
||||
@@ -431,10 +431,7 @@ fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
visitor.visit_body(body);
|
||||
for stmt in visitor.returns {
|
||||
if let Some(value) = stmt.value.as_deref() {
|
||||
if !matches!(
|
||||
value,
|
||||
Expr::Constant(constant) if constant.value.is_none()
|
||||
) {
|
||||
if !value.is_none_literal_expr() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -451,9 +448,10 @@ fn check_dynamically_typed<F>(
|
||||
) where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
range,
|
||||
value: Constant::Str(string),
|
||||
value: string,
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
// Quoted annotations
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
@@ -10,10 +10,7 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
|
||||
pub(super) fn string_literal(expr: &Expr) -> Option<&str> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) => Some(string),
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Operator};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -144,8 +144,8 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||
/// an integer value, but that value is out of range.
|
||||
fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result<Option<u16>> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(int),
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(int),
|
||||
..
|
||||
}) => match int.as_u16() {
|
||||
Some(value) => Ok(Some(value)),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::ExprStringLiteral;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for hardcoded bindings to all network interfaces (`0.0.0.0`).
|
||||
@@ -35,9 +34,9 @@ impl Violation for HardcodedBindAllInterfaces {
|
||||
}
|
||||
|
||||
/// S104
|
||||
pub(crate) fn hardcoded_bind_all_interfaces(value: &str, range: TextRange) -> Option<Diagnostic> {
|
||||
if value == "0.0.0.0" {
|
||||
Some(Diagnostic::new(HardcodedBindAllInterfaces, range))
|
||||
pub(crate) fn hardcoded_bind_all_interfaces(string: &ExprStringLiteral) -> Option<Diagnostic> {
|
||||
if string.value == "0.0.0.0" {
|
||||
Some(Diagnostic::new(HardcodedBindAllInterfaces, string.range))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -55,10 +55,7 @@ fn password_target(target: &Expr) -> Option<&str> {
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
// d["password"] = "s3cr3t"
|
||||
Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) => string,
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value,
|
||||
_ => return None,
|
||||
},
|
||||
// obj.password = "s3cr3t"
|
||||
|
||||
@@ -2,7 +2,6 @@ use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -52,13 +51,13 @@ impl Violation for HardcodedTempFile {
|
||||
}
|
||||
|
||||
/// S108
|
||||
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value: &str) {
|
||||
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: &ast::ExprStringLiteral) {
|
||||
if !checker
|
||||
.settings
|
||||
.flake8_bandit
|
||||
.hardcoded_tmp_directory
|
||||
.iter()
|
||||
.any(|prefix| value.starts_with(prefix))
|
||||
.any(|prefix| string.value.starts_with(prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -77,8 +76,8 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value:
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedTempFile {
|
||||
string: value.to_string(),
|
||||
string: string.value.clone(),
|
||||
},
|
||||
expr.range(),
|
||||
string.range,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -66,10 +66,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
|
||||
{
|
||||
if let Some(keyword) = call.arguments.find_keyword("autoescape") {
|
||||
match &keyword.value {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
..
|
||||
}) => (),
|
||||
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }) => (),
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
|
||||
if id != "select_autoescape" {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCal
|
||||
})
|
||||
{
|
||||
if let Some(keyword) = call.arguments.find_keyword("timeout") {
|
||||
if is_const_none(&keyword.value) {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { implicit: false },
|
||||
keyword.range(),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -38,18 +38,22 @@ use crate::{
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct SubprocessPopenWithShellEqualsTrue {
|
||||
seems_safe: bool,
|
||||
safety: Safety,
|
||||
is_exact: bool,
|
||||
}
|
||||
|
||||
impl Violation for SubprocessPopenWithShellEqualsTrue {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
if self.seems_safe {
|
||||
format!(
|
||||
match (self.safety, self.is_exact) {
|
||||
(Safety::SeemsSafe, true) => format!(
|
||||
"`subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`"
|
||||
)
|
||||
} else {
|
||||
format!("`subprocess` call with `shell=True` identified, security issue")
|
||||
),
|
||||
(Safety::Unknown, true) => format!("`subprocess` call with `shell=True` identified, security issue"),
|
||||
(Safety::SeemsSafe, false) => format!(
|
||||
"`subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`"
|
||||
),
|
||||
(Safety::Unknown, false) => format!("`subprocess` call with truthy `shell` identified, security issue"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,16 +93,18 @@ impl Violation for SubprocessWithoutShellEqualsTrue {
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for method calls that set the `shell` parameter to `true` when
|
||||
/// invoking a subprocess.
|
||||
/// Checks for method calls that set the `shell` parameter to `true` or another
|
||||
/// truthy value when invoking a subprocess.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Setting the `shell` parameter to `true` when invoking a subprocess can
|
||||
/// introduce security vulnerabilities, as it allows shell metacharacters and
|
||||
/// whitespace to be passed to child processes, potentially leading to shell
|
||||
/// injection attacks. It is recommended to avoid using `shell=True` unless
|
||||
/// absolutely necessary, and when used, to ensure that all inputs are properly
|
||||
/// sanitized and quoted to prevent such vulnerabilities.
|
||||
/// Setting the `shell` parameter to `true` or another truthy value when
|
||||
/// invoking a subprocess can introduce security vulnerabilities, as it allows
|
||||
/// shell metacharacters and whitespace to be passed to child processes,
|
||||
/// potentially leading to shell injection attacks.
|
||||
///
|
||||
/// It is recommended to avoid using `shell=True` unless absolutely necessary
|
||||
/// and, when used, to ensure that all inputs are properly sanitized and quoted
|
||||
/// to prevent such vulnerabilities.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Prone to false positives as it is triggered on any function call with a
|
||||
@@ -115,12 +121,18 @@ impl Violation for SubprocessWithoutShellEqualsTrue {
|
||||
/// ## References
|
||||
/// - [Python documentation: Security Considerations](https://docs.python.org/3/library/subprocess.html#security-considerations)
|
||||
#[violation]
|
||||
pub struct CallWithShellEqualsTrue;
|
||||
pub struct CallWithShellEqualsTrue {
|
||||
is_exact: bool,
|
||||
}
|
||||
|
||||
impl Violation for CallWithShellEqualsTrue {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Function call with `shell=True` parameter identified, security issue")
|
||||
if self.is_exact {
|
||||
format!("Function call with `shell=True` parameter identified, security issue")
|
||||
} else {
|
||||
format!("Function call with truthy `shell` parameter identified, security issue")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,16 +174,15 @@ impl Violation for CallWithShellEqualsTrue {
|
||||
/// - [Python documentation: `subprocess`](https://docs.python.org/3/library/subprocess.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithAShell {
|
||||
seems_safe: bool,
|
||||
safety: Safety,
|
||||
}
|
||||
|
||||
impl Violation for StartProcessWithAShell {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
if self.seems_safe {
|
||||
format!("Starting a process with a shell: seems safe, but may be changed in the future; consider rewriting without `shell`")
|
||||
} else {
|
||||
format!("Starting a process with a shell, possible injection detected")
|
||||
match self.safety {
|
||||
Safety::SeemsSafe => format!("Starting a process with a shell: seems safe, but may be changed in the future; consider rewriting without `shell`"),
|
||||
Safety::Unknown => format!("Starting a process with a shell, possible injection detected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,13 +295,14 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
match shell_keyword {
|
||||
// S602
|
||||
Some(ShellKeyword {
|
||||
truthiness: Truthiness::Truthy,
|
||||
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
|
||||
keyword,
|
||||
}) => {
|
||||
if checker.enabled(Rule::SubprocessPopenWithShellEqualsTrue) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
SubprocessPopenWithShellEqualsTrue {
|
||||
seems_safe: shell_call_seems_safe(arg),
|
||||
safety: Safety::from(arg),
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
keyword.range(),
|
||||
));
|
||||
@@ -298,7 +310,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
// S603
|
||||
Some(ShellKeyword {
|
||||
truthiness: Truthiness::Falsey | Truthiness::Unknown,
|
||||
truthiness: Truthiness::False | Truthiness::Falsey | Truthiness::Unknown,
|
||||
keyword,
|
||||
}) => {
|
||||
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
|
||||
@@ -320,15 +332,18 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
}
|
||||
} else if let Some(ShellKeyword {
|
||||
truthiness: Truthiness::Truthy,
|
||||
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
|
||||
keyword,
|
||||
}) = shell_keyword
|
||||
{
|
||||
// S604
|
||||
if checker.enabled(Rule::CallWithShellEqualsTrue) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(CallWithShellEqualsTrue, keyword.range()));
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
CallWithShellEqualsTrue {
|
||||
is_exact: matches!(truthiness, Truthiness::True),
|
||||
},
|
||||
keyword.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +353,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if let Some(arg) = call.arguments.args.first() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
StartProcessWithAShell {
|
||||
seems_safe: shell_call_seems_safe(arg),
|
||||
safety: Safety::from(arg),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
@@ -376,7 +391,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
(
|
||||
Some(CallKind::Subprocess),
|
||||
Some(ShellKeyword {
|
||||
truthiness: Truthiness::Truthy,
|
||||
truthiness: Truthiness::True | Truthiness::Truthy,
|
||||
keyword: _,
|
||||
})
|
||||
)
|
||||
@@ -453,16 +468,22 @@ fn find_shell_keyword<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the value provided to the `shell` call seems safe. This is based on Bandit's
|
||||
/// definition: string literals are considered okay, but dynamically-computed values are not.
|
||||
fn shell_call_seems_safe(arg: &Expr) -> bool {
|
||||
matches!(
|
||||
arg,
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
})
|
||||
)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum Safety {
|
||||
SeemsSafe,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&Expr> for Safety {
|
||||
/// Return the [`Safety`] level for the [`Expr`]. This is based on Bandit's definition: string
|
||||
/// literals are considered okay, but dynamically-computed values are not.
|
||||
fn from(expr: &Expr) -> Self {
|
||||
if expr.is_string_literal_expr() {
|
||||
Self::SeemsSafe
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the string appears to be a full file path.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Int};
|
||||
use ruff_python_ast::{self as ast, Expr, Int};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -52,8 +52,8 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall)
|
||||
if let Some(keyword) = call.arguments.find_keyword("mpModel") {
|
||||
if matches!(
|
||||
keyword.value,
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(Int::ZERO | Int::ONE),
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(Int::ZERO | Int::ONE),
|
||||
..
|
||||
})
|
||||
) {
|
||||
|
||||
@@ -854,7 +854,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
|
||||
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "Request"] => {
|
||||
// If the `url` argument is a string literal, allow `http` and `https` schemes.
|
||||
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
|
||||
if let Some(Expr::Constant(ast::ExprConstant { value: ast::Constant::Str(url), .. })) = &call.arguments.find_argument("url", 0) {
|
||||
if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value: url, .. })) = &call.arguments.find_argument("url", 0) {
|
||||
let url = url.trim_start();
|
||||
if url.starts_with("http://") || url.starts_with("https://") {
|
||||
return None;
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ExprAttribute, ExprCall};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprAttribute, ExprCall};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -151,8 +151,8 @@ fn extract_cryptographic_key(
|
||||
|
||||
fn extract_int_argument(call: &ExprCall, name: &str, position: usize) -> Option<(u16, TextRange)> {
|
||||
let argument = call.arguments.find_argument(name, position)?;
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(i),
|
||||
let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(i),
|
||||
..
|
||||
}) = argument
|
||||
else {
|
||||
|
||||
@@ -49,7 +49,7 @@ S602.py:8:13: S602 `subprocess` call with `shell=True` seems safe, but may be ch
|
||||
10 | # Check values that truthy values are treated as true.
|
||||
|
|
||||
|
||||
S602.py:11:15: S602 `subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
S602.py:11:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
|
|
||||
10 | # Check values that truthy values are treated as true.
|
||||
11 | Popen("true", shell=1)
|
||||
@@ -58,7 +58,7 @@ S602.py:11:15: S602 `subprocess` call with `shell=True` seems safe, but may be c
|
||||
13 | Popen("true", shell={1: 1})
|
||||
|
|
||||
|
||||
S602.py:12:15: S602 `subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
S602.py:12:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
|
|
||||
10 | # Check values that truthy values are treated as true.
|
||||
11 | Popen("true", shell=1)
|
||||
@@ -68,7 +68,7 @@ S602.py:12:15: S602 `subprocess` call with `shell=True` seems safe, but may be c
|
||||
14 | Popen("true", shell=(1,))
|
||||
|
|
||||
|
||||
S602.py:13:15: S602 `subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
S602.py:13:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
|
|
||||
11 | Popen("true", shell=1)
|
||||
12 | Popen("true", shell=[1])
|
||||
@@ -77,7 +77,7 @@ S602.py:13:15: S602 `subprocess` call with `shell=True` seems safe, but may be c
|
||||
14 | Popen("true", shell=(1,))
|
||||
|
|
||||
|
||||
S602.py:14:15: S602 `subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
S602.py:14:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
|
||||
|
|
||||
12 | Popen("true", shell=[1])
|
||||
13 | Popen("true", shell={1: 1})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
@@ -62,14 +62,3 @@ pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if an expression is a boolean literal.
|
||||
pub(super) const fn is_boolean(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
&expr,
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(_),
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{is_allowed_func_def, is_boolean};
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of boolean positional arguments in function definitions,
|
||||
@@ -117,7 +117,10 @@ pub(crate) fn boolean_default_value_positional_argument(
|
||||
range: _,
|
||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||
{
|
||||
if default.as_ref().is_some_and(|default| is_boolean(default)) {
|
||||
if default
|
||||
.as_ref()
|
||||
.is_some_and(|default| default.is_boolean_literal_expr())
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanDefaultValuePositionalArgument,
|
||||
parameter.name.range(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean};
|
||||
use crate::rules::flake8_boolean_trap::helpers::allow_boolean_trap;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function calls.
|
||||
@@ -49,7 +49,7 @@ pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Ex
|
||||
if allow_boolean_trap(func) {
|
||||
return;
|
||||
}
|
||||
for arg in args.iter().filter(|arg| is_boolean(arg)) {
|
||||
for arg in args.iter().filter(|arg| arg.is_boolean_literal_expr()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Decorator, Expr, ParameterWithDefault, Parameters};
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parameters};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
@@ -126,10 +126,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
// check for both bool (python class) and 'bool' (string annotation)
|
||||
let hint = match annotation.as_ref() {
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) => value == "bool",
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
_ => false,
|
||||
};
|
||||
if !hint || !checker.semantic().is_builtin("bool") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -121,12 +121,12 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel)
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => match value.as_ref() {
|
||||
Expr::Constant(ast::ExprConstant { value, .. }) => {
|
||||
matches!(value, Constant::Str(..) | Constant::Ellipsis)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_ast::Expr;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -42,13 +42,13 @@ impl Violation for DuplicateValue {
|
||||
pub(crate) fn duplicate_value(checker: &mut Checker, elts: &Vec<Expr>) {
|
||||
let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default();
|
||||
for elt in elts {
|
||||
if let Expr::Constant(ast::ExprConstant { value, .. }) = elt {
|
||||
if elt.is_literal_expr() {
|
||||
let comparable_value: ComparableExpr = elt.into();
|
||||
|
||||
if !seen_values.insert(comparable_value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
DuplicateValue {
|
||||
value: checker.generator().constant(value),
|
||||
value: checker.generator().expr(elt),
|
||||
},
|
||||
elt.range(),
|
||||
));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::fix::edits::pad;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -66,11 +66,7 @@ pub(crate) fn getattr_with_constant(
|
||||
if obj.is_starred_expr() {
|
||||
return;
|
||||
}
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = arg
|
||||
else {
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = arg else {
|
||||
return;
|
||||
};
|
||||
if !is_identifier(value) {
|
||||
|
||||
@@ -38,7 +38,7 @@ impl Violation for RaiseLiteral {
|
||||
|
||||
/// B016
|
||||
pub(crate) fn raise_literal(checker: &mut Checker, expr: &Expr) {
|
||||
if expr.is_constant_expr() {
|
||||
if expr.is_literal_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(RaiseLiteral, expr.range()));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ExprContext, Identifier, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext, Identifier, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
@@ -80,11 +80,7 @@ pub(crate) fn setattr_with_constant(
|
||||
if obj.is_starred_expr() {
|
||||
return;
|
||||
}
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(name),
|
||||
..
|
||||
}) = name
|
||||
else {
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value: name, .. }) = name else {
|
||||
return;
|
||||
};
|
||||
if !is_identifier(name) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use itertools::Itertools;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -68,11 +68,7 @@ pub(crate) fn strip_with_multi_characters(
|
||||
return;
|
||||
}
|
||||
|
||||
let [Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
})] = args
|
||||
else {
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = args else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -67,11 +67,7 @@ pub(crate) fn unreliable_callable_check(
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(ast::StringConstant { value, .. }),
|
||||
..
|
||||
}) = attr
|
||||
else {
|
||||
let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = attr else {
|
||||
return;
|
||||
};
|
||||
if value != "__call__" {
|
||||
|
||||
@@ -32,8 +32,8 @@ impl Violation for UselessComparison {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Pointless comparison. This comparison does nothing but waste CPU instructions. \
|
||||
Either prepend `assert` or remove it."
|
||||
"Pointless comparison. Did you mean to assign a value? \
|
||||
Otherwise, prepend `assert` or remove it."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::contains_effect;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -54,11 +54,7 @@ pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
|
||||
// Ignore strings, to avoid false positives with docstrings.
|
||||
if matches!(
|
||||
value,
|
||||
Expr::FString(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Ellipsis,
|
||||
..
|
||||
})
|
||||
Expr::FString(_) | Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -81,14 +81,14 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && is_const_none(&args[1]) {
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `iterools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||
if is_const_none(&keyword.value) {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B015.py:3:1: B015 Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it.
|
||||
B015.py:3:1: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
1 | assert 1 == 1
|
||||
2 |
|
||||
@@ -11,7 +11,7 @@ B015.py:3:1: B015 Pointless comparison. This comparison does nothing but waste C
|
||||
5 | assert 1 in (1, 2)
|
||||
|
|
||||
|
||||
B015.py:7:1: B015 Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it.
|
||||
B015.py:7:1: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
5 | assert 1 in (1, 2)
|
||||
6 |
|
||||
@@ -19,7 +19,7 @@ B015.py:7:1: B015 Pointless comparison. This comparison does nothing but waste C
|
||||
| ^^^^^^^^^^^ B015
|
||||
|
|
||||
|
||||
B015.py:17:5: B015 Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it.
|
||||
B015.py:17:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
15 | assert 1 in (1, 2)
|
||||
16 |
|
||||
@@ -27,7 +27,7 @@ B015.py:17:5: B015 Pointless comparison. This comparison does nothing but waste
|
||||
| ^^^^^^^^^^^ B015
|
||||
|
|
||||
|
||||
B015.py:24:5: B015 Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it.
|
||||
B015.py:24:5: B015 Pointless comparison. Did you mean to assign a value? Otherwise, prepend `assert` or remove it.
|
||||
|
|
||||
23 | class TestClass:
|
||||
24 | 1 == 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, UnaryOp};
|
||||
use ruff_python_ast::{self as ast, Expr, UnaryOp};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -79,8 +79,8 @@ pub(crate) fn unnecessary_subscript_reversal(checker: &mut Checker, call: &ast::
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(val),
|
||||
let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(val),
|
||||
..
|
||||
}) = operand.as_ref()
|
||||
else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp
|
||||
}
|
||||
|
||||
// none args
|
||||
if call.arguments.args.len() > 1 && is_const_none(&call.arguments.args[1]) {
|
||||
if call.arguments.args.len() > 1 && call.arguments.args[1].is_none_literal_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(CallDatetimeFromtimestamp, call.range()));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_python_ast::{self as ast};
|
||||
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -77,7 +77,12 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast
|
||||
}
|
||||
|
||||
// none args
|
||||
if call.arguments.args.first().is_some_and(is_const_none) {
|
||||
if call
|
||||
.arguments
|
||||
.args
|
||||
.first()
|
||||
.is_some_and(Expr::is_none_literal_expr)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range()));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -75,10 +75,8 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
|
||||
}
|
||||
|
||||
// Does the `strptime` call contain a format string with a timezone specifier?
|
||||
if let Some(Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(format),
|
||||
range: _,
|
||||
})) = call.arguments.args.get(1).as_ref()
|
||||
if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value: format, .. })) =
|
||||
call.arguments.args.get(1).as_ref()
|
||||
{
|
||||
if format.contains("%z") {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_python_ast::{self as ast};
|
||||
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -73,7 +73,12 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex
|
||||
}
|
||||
|
||||
// Positional arg: is constant None.
|
||||
if call.arguments.args.get(7).is_some_and(is_const_none) {
|
||||
if call
|
||||
.arguments
|
||||
.args
|
||||
.get(7)
|
||||
.is_some_and(Expr::is_none_literal_expr)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(CallDatetimeWithoutTzinfo, call.range()));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_python_ast::{Arguments, Expr, ExprAttribute};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -15,5 +14,5 @@ pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool {
|
||||
pub(super) fn has_non_none_keyword(arguments: &Arguments, keyword: &str) -> bool {
|
||||
arguments
|
||||
.find_keyword(keyword)
|
||||
.is_some_and(|keyword| !is_const_none(&keyword.value))
|
||||
.is_some_and(|keyword| !keyword.value.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -80,16 +80,13 @@ pub(crate) fn all_with_model_form(
|
||||
if id != "fields" {
|
||||
continue;
|
||||
}
|
||||
let Expr::Constant(ast::ExprConstant { value, .. }) = value.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
match value {
|
||||
Constant::Str(ast::StringConstant { value, .. }) => {
|
||||
match value.as_ref() {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
if value == "__all__" {
|
||||
return Some(Diagnostic::new(DjangoAllWithModelForm, element.range()));
|
||||
}
|
||||
}
|
||||
Constant::Bytes(ast::BytesConstant { value, .. }) => {
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
|
||||
if value == "__all__".as_bytes() {
|
||||
return Some(Diagnostic::new(DjangoAllWithModelForm, element.range()));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
@@ -182,10 +182,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
|
||||
if let Some(first) = args.first() {
|
||||
match first {
|
||||
// Check for string literals.
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
}) => {
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value: string, .. }) => {
|
||||
if checker.enabled(Rule::RawStringInException) {
|
||||
if string.len() >= checker.settings.flake8_errmsg.max_string_length {
|
||||
let mut diagnostic =
|
||||
@@ -232,7 +229,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
|
||||
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) =
|
||||
func.as_ref()
|
||||
{
|
||||
if attr == "format" && value.is_constant_expr() {
|
||||
if attr == "format" && value.is_literal_expr() {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(DotFormatInException, first.range());
|
||||
if let Some(indentation) =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Operator};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -58,11 +58,7 @@ pub(crate) fn printf_in_gettext_func_call(checker: &mut Checker, args: &[Expr])
|
||||
..
|
||||
}) = &first
|
||||
{
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
}) = left.as_ref()
|
||||
{
|
||||
if left.is_string_literal_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(PrintfInGetTextFuncCall {}, first.range()));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Operator};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -51,18 +51,10 @@ pub(crate) fn explicit(expr: &Expr, locator: &Locator) -> Option<Diagnostic> {
|
||||
if matches!(op, Operator::Add) {
|
||||
if matches!(
|
||||
left.as_ref(),
|
||||
Expr::FString(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Bytes(..),
|
||||
..
|
||||
})
|
||||
Expr::FString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_)
|
||||
) && matches!(
|
||||
right.as_ref(),
|
||||
Expr::FString(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Bytes(..),
|
||||
..
|
||||
})
|
||||
Expr::FString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_)
|
||||
) && locator.contains_line_break(*range)
|
||||
{
|
||||
return Some(Diagnostic::new(ExplicitStringConcatenation, expr.range()));
|
||||
|
||||
@@ -110,18 +110,27 @@ pub(crate) fn implicit(
|
||||
{
|
||||
let (a_range, b_range) = match (a_tok, b_tok) {
|
||||
(Tok::String { .. }, Tok::String { .. }) => (*a_range, *b_range),
|
||||
(Tok::String { .. }, Tok::FStringStart) => (
|
||||
*a_range,
|
||||
indexer.fstring_ranges().innermost(b_range.start()).unwrap(),
|
||||
),
|
||||
(Tok::FStringEnd, Tok::String { .. }) => (
|
||||
indexer.fstring_ranges().innermost(a_range.start()).unwrap(),
|
||||
*b_range,
|
||||
),
|
||||
(Tok::FStringEnd, Tok::FStringStart) => (
|
||||
indexer.fstring_ranges().innermost(a_range.start()).unwrap(),
|
||||
indexer.fstring_ranges().innermost(b_range.start()).unwrap(),
|
||||
),
|
||||
(Tok::String { .. }, Tok::FStringStart) => {
|
||||
match indexer.fstring_ranges().innermost(b_range.start()) {
|
||||
Some(b_range) => (*a_range, b_range),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(Tok::FStringEnd, Tok::String { .. }) => {
|
||||
match indexer.fstring_ranges().innermost(a_range.start()) {
|
||||
Some(a_range) => (a_range, *b_range),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(Tok::FStringEnd, Tok::FStringStart) => {
|
||||
match (
|
||||
indexer.fstring_ranges().innermost(a_range.start()),
|
||||
indexer.fstring_ranges().innermost(b_range.start()),
|
||||
) {
|
||||
(Some(a_range), Some(b_range)) => (a_range, b_range),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ fn exc_info_arg_is_falsey(call: &ExprCall, checker: &mut Checker) -> bool {
|
||||
.find_keyword("exc_info")
|
||||
.map(|keyword| &keyword.value)
|
||||
.is_some_and(|value| {
|
||||
Truthiness::from_expr(value, |id| checker.semantic().is_builtin(id)).is_falsey()
|
||||
let truthiness = Truthiness::from_expr(value, |id| checker.semantic().is_builtin(id));
|
||||
matches!(truthiness, Truthiness::False | Truthiness::Falsey)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Operator};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -74,7 +74,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
if checker.enabled(Rule::LoggingStringFormat) {
|
||||
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
if attr == "format" && value.is_constant_expr() {
|
||||
if attr == "format" && value.is_literal_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingStringFormat, msg.range()));
|
||||
@@ -92,11 +92,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
|
||||
Expr::Dict(ast::ExprDict { keys, .. }) => {
|
||||
for key in keys {
|
||||
if let Some(key) = &key {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(attr),
|
||||
..
|
||||
}) = key
|
||||
{
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value: attr, .. }) = key {
|
||||
if is_reserved_attr(attr) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
LoggingExtraAttrClash(attr.to_string()),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use itertools::Itertools;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword};
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword};
|
||||
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -96,7 +96,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs
|
||||
.iter()
|
||||
.zip(values.iter())
|
||||
.map(|(kwarg, value)| {
|
||||
format!("{}={}", kwarg.value, checker.locator().slice(value.range()))
|
||||
format!("{}={}", kwarg, checker.locator().slice(value.range()))
|
||||
})
|
||||
.join(", "),
|
||||
kw.range(),
|
||||
@@ -108,12 +108,8 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs
|
||||
}
|
||||
|
||||
/// Return `Some` if a key is a valid keyword argument name, or `None` otherwise.
|
||||
fn as_kwarg(key: &Expr) -> Option<&ast::StringConstant> {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
}) = key
|
||||
{
|
||||
fn as_kwarg(key: &Expr) -> Option<&str> {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = key {
|
||||
if is_identifier(value) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -65,8 +65,8 @@ pub(crate) fn unnecessary_range_start(checker: &mut Checker, call: &ast::ExprCal
|
||||
};
|
||||
|
||||
// Verify that the `start` argument is the literal `0`.
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
let Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(value),
|
||||
..
|
||||
}) = start
|
||||
else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
// If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`,
|
||||
// or `"sys.stderr"`), don't trigger T201.
|
||||
if let Some(keyword) = call.arguments.find_keyword("file") {
|
||||
if !is_const_none(&keyword.value) {
|
||||
if !keyword.value.is_none_literal_expr() {
|
||||
if checker.semantic().resolve_call_path(&keyword.value).map_or(
|
||||
true,
|
||||
|call_path| {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_ast::ExprStringLiteral;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -36,8 +36,8 @@ impl Violation for DocstringInStub {
|
||||
}
|
||||
|
||||
/// PYI021
|
||||
pub(crate) fn docstring_in_stubs(checker: &mut Checker, docstring: Option<&Expr>) {
|
||||
if let Some(docstr) = &docstring {
|
||||
pub(crate) fn docstring_in_stubs(checker: &mut Checker, docstring: Option<&ExprStringLiteral>) {
|
||||
if let Some(docstr) = docstring {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(DocstringInStub, docstr.range()));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Constant, Expr, ExprConstant, Stmt, StmtExpr};
|
||||
use ruff_python_ast::{Stmt, StmtExpr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -54,13 +54,7 @@ pub(crate) fn ellipsis_in_non_empty_class_body(checker: &mut Checker, body: &[St
|
||||
continue;
|
||||
};
|
||||
|
||||
if matches!(
|
||||
value.as_ref(),
|
||||
Expr::Constant(ExprConstant {
|
||||
value: Constant::Ellipsis,
|
||||
..
|
||||
})
|
||||
) {
|
||||
if value.is_ellipsis_literal_expr() {
|
||||
let mut diagnostic = Diagnostic::new(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
let edit =
|
||||
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
|
||||
|
||||
@@ -8,7 +8,7 @@ use smallvec::SmallVec;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -245,7 +245,7 @@ fn non_none_annotation_element<'a>(
|
||||
// E.g., `typing.Union` or `typing.Optional`
|
||||
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
|
||||
if semantic.match_typing_expr(value, "Optional") {
|
||||
return if is_const_none(slice) {
|
||||
return if slice.is_none_literal_expr() {
|
||||
None
|
||||
} else {
|
||||
Some(slice)
|
||||
@@ -264,7 +264,7 @@ fn non_none_annotation_element<'a>(
|
||||
return None;
|
||||
};
|
||||
|
||||
return match (is_const_none(left), is_const_none(right)) {
|
||||
return match (left.is_none_literal_expr(), right.is_none_literal_expr()) {
|
||||
(false, true) => Some(left),
|
||||
(true, false) => Some(right),
|
||||
(true, true) => None,
|
||||
@@ -280,11 +280,11 @@ fn non_none_annotation_element<'a>(
|
||||
..
|
||||
}) = annotation
|
||||
{
|
||||
if !is_const_none(left) {
|
||||
if !left.is_none_literal_expr() {
|
||||
return Some(left);
|
||||
}
|
||||
|
||||
if !is_const_none(right) {
|
||||
if !right.is_none_literal_expr() {
|
||||
return Some(right);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user