Compare commits

..

1 Commits

Author SHA1 Message Date
Zanie
0e36b9e709 Allow globs in external codes setting 2023-10-24 12:22:04 -05:00
592 changed files with 10938 additions and 24454 deletions

View File

@@ -30,7 +30,7 @@ jobs:
with:
fetch-depth: 0
- uses: tj-actions/changed-files@v40
- uses: tj-actions/changed-files@v39
id: changed
with:
files_yaml: |
@@ -43,7 +43,6 @@ jobs:
- "!crates/ruff_dev/**"
- "!crates/ruff_shrinking/**"
- scripts/*
- .github/workflows/ci.yaml
formatter:
- Cargo.toml
@@ -58,7 +57,6 @@ jobs:
- crates/ruff_python_parser/**
- crates/ruff_dev/**
- scripts/*
- .github/workflows/ci.yaml
cargo-fmt:
name: "cargo fmt"
@@ -84,9 +82,12 @@ jobs:
- name: "Clippy (wasm)"
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test-linux:
runs-on: ubuntu-latest
name: "cargo test (linux)"
cargo-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
name: "cargo test | ${{ matrix.os }}"
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -96,54 +97,25 @@ jobs:
with:
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
- name: "Run tests"
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
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"
@@ -160,6 +132,25 @@ 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
@@ -181,14 +172,10 @@ jobs:
name: "ecosystem"
runs-on: ubuntu-latest
needs:
- cargo-test-linux
- cargo-test
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes.
if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
}}
if: github.event_name == 'pull_request' && needs.determine_changes.outputs.linter == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
@@ -196,89 +183,27 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v3
name: Download comparison Ruff binary
name: Download Ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v2
name: Download baseline Ruff binary
name: Download base results
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
check_artifacts: true
- name: Install ruff-ecosystem
run: |
pip install ./python/ruff-ecosystem
- name: Run `ruff check` stable ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
- name: Run ecosystem check
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
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
cat ecosystem-result > $GITHUB_STEP_SUMMARY
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
cat ecosystem-result-check-stable > $GITHUB_STEP_SUMMARY
echo "### Linter (stable)" > ecosystem-result
cat ecosystem-result-check-stable >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff check` preview ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
cat ecosystem-result-check-preview > $GITHUB_STEP_SUMMARY
echo "### Linter (preview)" >> ecosystem-result
cat ecosystem-result-check-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` stable ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
cat ecosystem-result-format-stable > $GITHUB_STEP_SUMMARY
echo "### Formatter (stable)" >> ecosystem-result
cat ecosystem-result-format-stable >> ecosystem-result
echo "" >> ecosystem-result
- name: Run `ruff format` preview ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
cat ecosystem-result-format-preview > $GITHUB_STEP_SUMMARY
echo "### Formatter (preview)" >> ecosystem-result
cat ecosystem-result-format-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Export pull request number
run: |
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v3
@@ -394,8 +319,8 @@ jobs:
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.generated.yml
check-formatter-instability-and-black-similarity:
name: "formatter instabilities and black similarity"
check-formatter-ecosystem:
name: "Formatter ecosystem and progress checks"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main'
@@ -415,7 +340,7 @@ jobs:
check-ruff-lsp:
name: "test ruff-lsp"
runs-on: ubuntu-latest
needs: cargo-test-linux
needs: cargo-test
steps:
- uses: extractions/setup-just@v1
env:

View File

@@ -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.2
uses: cloudflare/wrangler-action@v3.3.1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
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.2
uses: cloudflare/wrangler-action@v3.3.1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -1,4 +1,4 @@
name: Ecosystem check comment
name: PR Check Comment
on:
workflow_run:
@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download pull request number
name: Download PR Number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Parse pull request number
- name: Extract PR Number
id: pr-number
run: |
if [[ -f pr-number ]]
@@ -33,7 +33,7 @@ jobs:
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download ecosystem results"
name: "Download Ecosystem Result"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
@@ -41,18 +41,15 @@ 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 content
- name: Generate Comment
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
run: |
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment ecosystem -->' >> comment.txt
echo '## PR Check Results' >> comment.txt
echo '## `ruff-ecosystem` results' >> comment.txt
echo "### Ecosystem" >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
@@ -60,14 +57,14 @@ jobs:
cat comment.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Find existing comment
- name: Find Comment
uses: peter-evans/find-comment@v2
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ecosystem -->"
body-includes: PR Check Results
- name: Create or update comment
if: steps.find-comment.outcome == 'success'

View File

@@ -48,6 +48,7 @@ jobs:
args: --out dist
- name: "Test sdist"
run: |
rustup default $(cat rust-toolchain)
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
ruff --help
python -m ruff --help

View File

@@ -13,12 +13,12 @@ exclude: |
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.15
rev: v0.12.1
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.17
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
@@ -26,22 +26,16 @@ repos:
- mdformat-admon
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
docs/formatter/black.md
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.37.0
rev: v0.33.0
hooks:
- id: markdownlint-fix
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
)$
- repo: https://github.com/crate-ci/typos
rev: v1.16.22
rev: v1.14.12
hooks:
- id: typos
@@ -53,13 +47,10 @@ repos:
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: ruff-format
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
name: ruff
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -68,9 +59,15 @@ repos:
crates/ruff_python_formatter/resources/.*
)$
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.3
rev: v3.0.0
hooks:
- id: prettier
types: [yaml]

View File

@@ -1,114 +1,9 @@
# Changelog
## 0.1.4
### Preview features
- \[`flake8-trio`\] Implement `timeout-without-await` (`TRIO001`) ([#8439](https://github.com/astral-sh/ruff/pull/8439))
- \[`numpy`\] Implement NumPy 2.0 migration rule (`NPY200`) ([#7702](https://github.com/astral-sh/ruff/pull/7702))
- \[`pylint`\] Implement `bad-open-mode` (`W1501`) ([#8294](https://github.com/astral-sh/ruff/pull/8294))
- \[`pylint`\] Implement `import-outside-toplevel` (`C0415`) rule ([#5180](https://github.com/astral-sh/ruff/pull/5180))
- \[`pylint`\] Implement `useless-with-lock` (`W2101`) ([#8321](https://github.com/astral-sh/ruff/pull/8321))
- \[`pyupgrade`\] Implement `timeout-error-alias` (`UP041`) ([#8476](https://github.com/astral-sh/ruff/pull/8476))
- \[`refurb`\] Implement `isinstance-type-none` (`FURB168`) ([#8308](https://github.com/astral-sh/ruff/pull/8308))
- Detect confusable Unicode-to-Unicode units in `RUF001`, `RUF002`, and `RUF003` ([#4430](https://github.com/astral-sh/ruff/pull/4430))
- Add newline after module docstrings in preview style ([#8283](https://github.com/astral-sh/ruff/pull/8283))
### Formatter
- Add a note on line-too-long to the formatter docs ([#8314](https://github.com/astral-sh/ruff/pull/8314))
- Preserve trailing statement semicolons when using `fmt: skip` ([#8273](https://github.com/astral-sh/ruff/pull/8273))
- Preserve trailing semicolons when using `fmt: off` ([#8275](https://github.com/astral-sh/ruff/pull/8275))
- Avoid duplicating linter-formatter compatibility warnings ([#8292](https://github.com/astral-sh/ruff/pull/8292))
- Avoid inserting a newline after function docstrings ([#8375](https://github.com/astral-sh/ruff/pull/8375))
- Insert newline between docstring and following own line comment ([#8216](https://github.com/astral-sh/ruff/pull/8216))
- Split tuples in return positions by comma first ([#8280](https://github.com/astral-sh/ruff/pull/8280))
- Avoid treating byte strings as docstrings ([#8350](https://github.com/astral-sh/ruff/pull/8350))
- Add `--line-length` option to `format` command ([#8363](https://github.com/astral-sh/ruff/pull/8363))
- Avoid parenthesizing unsplittable because of comments ([#8431](https://github.com/astral-sh/ruff/pull/8431))
### CLI
- Add `--output-format` to `ruff rule` and `ruff linter` ([#8203](https://github.com/astral-sh/ruff/pull/8203))
### Bug fixes
- Respect `--force-exclude` in `lint.exclude` and `format.exclude` ([#8393](https://github.com/astral-sh/ruff/pull/8393))
- Respect `--extend-per-file-ignores` on the CLI ([#8329](https://github.com/astral-sh/ruff/pull/8329))
- Extend `bad-dunder-method-name` to permit `__index__` ([#8300](https://github.com/astral-sh/ruff/pull/8300))
- Fix panic with 8 in octal escape ([#8356](https://github.com/astral-sh/ruff/pull/8356))
- Avoid raising `D300` when both triple quote styles are present ([#8462](https://github.com/astral-sh/ruff/pull/8462))
- Consider unterminated f-strings in `FStringRanges` ([#8154](https://github.com/astral-sh/ruff/pull/8154))
- Avoid including literal `shell=True` for truthy, non-`True` diagnostics ([#8359](https://github.com/astral-sh/ruff/pull/8359))
- Avoid triggering single-element test for starred expressions ([#8433](https://github.com/astral-sh/ruff/pull/8433))
- Detect and ignore Jupyter automagics ([#8398](https://github.com/astral-sh/ruff/pull/8398))
- Fix invalid E231 error with f-strings ([#8369](https://github.com/astral-sh/ruff/pull/8369))
- Avoid triggering `NamedTuple` rewrite with starred annotation ([#8434](https://github.com/astral-sh/ruff/pull/8434))
- Avoid un-setting bracket flag in logical lines ([#8380](https://github.com/astral-sh/ruff/pull/8380))
- Place 'r' prefix before 'f' for raw format strings ([#8464](https://github.com/astral-sh/ruff/pull/8464))
- Remove trailing periods from NumPy 2.0 code actions ([#8475](https://github.com/astral-sh/ruff/pull/8475))
- Fix bug where `PLE1307` was raised when formatting `%c` with characters ([#8407](https://github.com/astral-sh/ruff/pull/8407))
- Remove unicode flag from comparable ([#8440](https://github.com/astral-sh/ruff/pull/8440))
- Improve B015 message ([#8295](https://github.com/astral-sh/ruff/pull/8295))
- Use `fixedOverflowWidgets` for playground popover ([#8458](https://github.com/astral-sh/ruff/pull/8458))
- Mark `byte_bounds` as a non-backwards-compatible NumPy 2.0 change ([#8474](https://github.com/astral-sh/ruff/pull/8474))
### Internals
- Add a dedicated cache directory per Ruff version ([#8333](https://github.com/astral-sh/ruff/pull/8333))
- Allow selective caching for `--fix` and `--diff` ([#8316](https://github.com/astral-sh/ruff/pull/8316))
- Improve performance of comment parsing ([#8193](https://github.com/astral-sh/ruff/pull/8193))
- Improve performance of string parsing ([#8227](https://github.com/astral-sh/ruff/pull/8227))
- Use a dedicated sort key for isort import sorting ([#7963](https://github.com/astral-sh/ruff/pull/7963))
## 0.1.3
This release includes a variety of improvements to the Ruff formatter, removing several known and
unintentional deviations from Black.
### Formatter
- Avoid space around pow for `None`, `True` and `False` ([#8189](https://github.com/astral-sh/ruff/pull/8189))
- Avoid sorting all paths in the format command ([#8181](https://github.com/astral-sh/ruff/pull/8181))
- Insert necessary blank line between class and leading comments ([#8224](https://github.com/astral-sh/ruff/pull/8224))
- Avoid introducing new parentheses in annotated assignments ([#8233](https://github.com/astral-sh/ruff/pull/8233))
- Refine the warnings about incompatible linter options ([#8196](https://github.com/astral-sh/ruff/pull/8196))
- Add test and basic implementation for formatter preview mode ([#8044](https://github.com/astral-sh/ruff/pull/8044))
- Refine warning about incompatible `isort` settings ([#8192](https://github.com/astral-sh/ruff/pull/8192))
- Only omit optional parentheses for starting or ending with parentheses ([#8238](https://github.com/astral-sh/ruff/pull/8238))
- Use source type to determine parser mode for formatting ([#8205](https://github.com/astral-sh/ruff/pull/8205))
- Don't warn about magic trailing comma when `isort.force-single-line` is true ([#8244](https://github.com/astral-sh/ruff/pull/8244))
- Use `SourceKind::diff` for formatter ([#8240](https://github.com/astral-sh/ruff/pull/8240))
- Fix `fmt:off` with trailing child comment ([#8234](https://github.com/astral-sh/ruff/pull/8234))
- Formatter parentheses support for `IpyEscapeCommand` ([#8207](https://github.com/astral-sh/ruff/pull/8207))
### Linter
- \[`pylint`\] Add buffer methods to `bad-dunder-method-name` (`PLW3201`) exclusions ([#8190](https://github.com/astral-sh/ruff/pull/8190))
- Match rule prefixes from `external` codes setting in `unused-noqa` ([#8177](https://github.com/astral-sh/ruff/pull/8177))
- Use `line-length` setting for isort in lieu of `pycodestyle.max-line-length` ([#8235](https://github.com/astral-sh/ruff/pull/8235))
- Update fix for `unnecessary-paren-on-raise-exception` to unsafe for unknown types ([#8231](https://github.com/astral-sh/ruff/pull/8231))
- Correct quick fix message for `W605` ([#8255](https://github.com/astral-sh/ruff/pull/8255))
### Documentation
- Fix typo in max-doc-length documentation ([#8201](https://github.com/astral-sh/ruff/pull/8201))
- Improve documentation around linter-formatter conflicts ([#8257](https://github.com/astral-sh/ruff/pull/8257))
- Fix link to error suppression documentation in `unused-noqa` ([#8172](https://github.com/astral-sh/ruff/pull/8172))
- Add `external` option to `unused-noqa` documentation ([#8171](https://github.com/astral-sh/ruff/pull/8171))
- Add title attribute to icons ([#8060](https://github.com/astral-sh/ruff/pull/8060))
- Clarify unsafe case in RSE102 ([#8256](https://github.com/astral-sh/ruff/pull/8256))
- Fix skipping formatting examples ([#8210](https://github.com/astral-sh/ruff/pull/8210))
- docs: fix name of `magic-trailing-comma` option in README ([#8200](https://github.com/astral-sh/ruff/pull/8200))
- Add note about scope of rule changing in versioning policy ([#8169](https://github.com/astral-sh/ruff/pull/8169))
- Document: Fix default lint rules ([#8218](https://github.com/astral-sh/ruff/pull/8218))
- Fix a wrong setting in configuration.md ([#8186](https://github.com/astral-sh/ruff/pull/8186))
- Fix misspelled TOML headers in the tutorial ([#8209](https://github.com/astral-sh/ruff/pull/8209))
## 0.1.2
This release includes the Beta version of the Ruff formatter — an extremely fast, Black-compatible Python formatter.
Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blog/the-ruff-formatter) and [read the docs](https://docs.astral.sh/ruff/formatter/).
Try it today with `ruff format`.
### Preview features

View File

@@ -72,7 +72,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<charlie.r.marsh@gmail.com>.
charlie.r.marsh@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@@ -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 the time of writing, the repository includes the following crates:
At 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.
@@ -337,15 +337,16 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
## Ecosystem CI
GitHub Actions will run your changes against a number of real-world projects from GitHub and
report on any linter or formatter differences. You can also run those checks locally via:
report on any diagnostic differences. You can also run those checks locally via:
```shell
pip install -e ./python/ruff-ecosystem
ruff-ecosystem check ruff "./target/debug/ruff"
ruff-ecosystem format ruff "./target/debug/ruff"
python scripts/check_ecosystem.py path/to/your/ruff path/to/older/ruff
```
See the [ruff-ecosystem package](https://github.com/astral-sh/ruff/tree/main/python/ruff-ecosystem) for more details.
You can also run the Ecosystem CI check in a Docker container across a larger set of projects by
downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-aggregate/blob/master/data/known-github-tomls.jsonl)
as `github_search.jsonl` and following the instructions in [scripts/Dockerfile.ecosystem](https://github.com/astral-sh/ruff/blob/main/scripts/Dockerfile.ecosystem).
Note that this check will take a while to run.
## Benchmarking and Profiling
@@ -876,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 if your project contains multiple packages, you'll want to set `src`
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
explicitly.

147
Cargo.lock generated
View File

@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bstr"
@@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.7"
version = "4.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
dependencies = [
"clap_builder",
"clap_derive",
@@ -323,9 +323,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.7"
version = "4.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
dependencies = [
"anstream",
"anstyle",
@@ -376,21 +376,21 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "clearscreen"
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
checksum = "d680ccd1eedd2dd7c7a3649a78c7d06e0f16b191b30d81cc58e7bc906488d344"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
checksum = "58b48b6c8e890d7d4ad0ed85e9ab4949bf7023198c006000ef6338ba84cf5b71"
dependencies = [
"codspeed",
"colored",
@@ -608,7 +608,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.4"
version = "0.1.2"
dependencies = [
"anyhow",
"clap",
@@ -1129,7 +1129,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -1262,9 +1262,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.149"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libcst"
@@ -1309,9 +1309,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.10"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "lock_api"
@@ -1440,7 +1440,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@@ -1707,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -1801,9 +1801,9 @@ dependencies = [
[[package]]
name = "pyproject-toml"
version = "0.8.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0774c13ff0b8b7ebb4791c050c497aefcfe3f6a222c0829c7017161ed38391ff"
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
dependencies = [
"indexmap",
"pep440_rs",
@@ -1912,15 +1912,6 @@ 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"
@@ -2060,14 +2051,14 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.4"
version = "0.1.2"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
"argfile",
"assert_cmd",
"bincode",
"bitflags 2.4.1",
"bitflags 2.4.0",
"cachedir",
"chrono",
"clap",
@@ -2081,6 +2072,7 @@ dependencies = [
"insta-cmd",
"is-macro",
"itertools 0.11.0",
"itoa",
"log",
"mimalloc",
"notify",
@@ -2196,12 +2188,12 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.4"
version = "0.1.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"chrono",
"clap",
"colored",
@@ -2267,7 +2259,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2293,7 +2285,7 @@ dependencies = [
name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"insta",
"is-macro",
"itertools 0.11.0",
@@ -2325,7 +2317,7 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"clap",
"countme",
"insta",
@@ -2369,7 +2361,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"hexf-parse",
"is-macro",
"itertools 0.11.0",
@@ -2383,13 +2375,12 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.0",
"insta",
"is-macro",
"itertools 0.11.0",
"lalrpop",
"lalrpop-util",
"memchr",
"ruff_python_ast",
"ruff_text_size",
"rustc-hash",
@@ -2413,7 +2404,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"is-macro",
"ruff_index",
"ruff_python_ast",
@@ -2447,7 +2438,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.4"
version = "0.1.2"
dependencies = [
"anyhow",
"clap",
@@ -2559,11 +2550,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.21"
version = "0.38.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
@@ -2673,18 +2664,18 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
version = "1.0.190"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
dependencies = [
"js-sys",
"serde",
@@ -2693,13 +2684,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.190"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2715,9 +2706,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
@@ -2761,7 +2752,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2856,7 +2847,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2872,9 +2863,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.39"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@@ -2892,13 +2883,13 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.8.1"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.4.1",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.48.0",
]
@@ -2961,7 +2952,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -2973,7 +2964,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"test-case-core",
]
@@ -2994,7 +2985,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3131,7 +3122,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3331,9 +3322,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.5.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom",
"rand",
@@ -3343,13 +3334,13 @@ dependencies = [
[[package]]
name = "uuid-macro-internal"
version = "1.5.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
]
[[package]]
@@ -3443,7 +3434,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"wasm-bindgen-shared",
]
@@ -3477,7 +3468,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
"syn 2.0.38",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -13,9 +13,9 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.4.1" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
clap = { version = "4.4.7", features = ["derive"] }
clap = { version = "4.4.6", features = ["derive"] }
colored = { version = "2.0.0" }
filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
@@ -34,15 +34,15 @@ 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.190", features = ["derive"] }
serde_json = { version = "1.0.108" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }
smallvec = { version = "1.11.1" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.3" }
syn = { version = "2.0.39" }
syn = { version = "2.0.38" }
test-case = { version = "3.2.1" }
thiserror = { version = "1.0.50" }
toml = { version = "0.7.8" }
@@ -52,11 +52,11 @@ 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.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
wsl = { version = "0.1.0" }
[profile.release]
lto = "thin"
lto = "fat"
codegen-units = 1
[profile.dev.package.insta]

25
LICENSE
View File

@@ -1269,31 +1269,6 @@ are:
SOFTWARE.
"""
- flake8-trio, licensed as follows:
"""
MIT License
Copyright (c) 2022 Zac Hatfield-Dodds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Pyright, licensed as follows:
"""
MIT License

View File

@@ -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/integrations/) for
- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery)
@@ -148,9 +148,10 @@ ruff format @arguments.txt # Format using an input file, treating its
Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):
```yaml
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.4
rev: v0.1.2
hooks:
# Run the Ruff linter.
- id: ruff
@@ -237,7 +238,7 @@ quote-style = "double"
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
magic-trailing-comma = "respect"
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
@@ -313,7 +314,6 @@ quality tools, including:
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-trio](https://pypi.org/project/flake8-trio/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
@@ -409,13 +409,11 @@ 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)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [Pandas](https://github.com/pandas-dev/pandas)
- [Pillow](https://github.com/python-pillow/Pillow)
- [Poetry](https://github.com/python-poetry/poetry)
- [Polars](https://github.com/pola-rs/polars)
- [PostHog](https://github.com/PostHog/posthog)
@@ -424,7 +422,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint)
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
- [Reflex](https://github.com/reflex-dev/reflex)
- [Rippling](https://rippling.com)
- [Robyn](https://github.com/sansyrox/robyn)

View File

@@ -1,6 +1,5 @@
[files]
# https://github.com/crate-ci/typos/issues/868
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
extend-exclude = ["resources", "snapshots"]
[default.extend-words]
hel = "hel"

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.4"
version = "0.1.2"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -37,7 +37,7 @@ serde_json.workspace = true
url = "2.3.1"
ureq = "2.8.0"
criterion = { version = "0.5.1", default-features = false }
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
codspeed-criterion-compat = { version="2.3.0", default-features = false, optional = true}
[dev-dependencies]
ruff_linter.path = "../ruff_linter"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.4"
version = "0.1.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -44,6 +44,7 @@ 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"] }
@@ -67,7 +68,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.8.1"
tempfile = "3.6.0"
test-case = { workspace = true }
ureq = { version = "2.8.0", features = [] }

View File

@@ -11,41 +11,6 @@
"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": {

View File

@@ -50,11 +50,7 @@ pub enum Command {
/// Output format
#[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
format: HelpFormat,
},
/// List or describe the available configuration options.
Config { option: Option<String> },
@@ -62,11 +58,7 @@ pub enum Command {
Linter {
/// Output format
#[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat,
/// Output format (Deprecated: Use `--output-format` instead).
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
format: Option<HelpFormat>,
format: HelpFormat,
},
/// Clear any caches in the current directory and any subdirectories.
#[clap(alias = "--clean")]
@@ -278,7 +270,7 @@ pub struct CheckCommand {
#[arg(long, help_heading = "Rule configuration", hide = true)]
pub dummy_variable_rgx: Option<Regex>,
/// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
#[arg(short, long, help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
@@ -374,7 +366,7 @@ pub struct FormatCommand {
pub config: Option<PathBuf>,
/// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
#[arg(short, long, help_heading = "Miscellaneous")]
pub no_cache: bool,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
@@ -409,9 +401,6 @@ pub struct FormatCommand {
force_exclude: bool,
#[clap(long, overrides_with("force_exclude"), hide = true)]
no_force_exclude: bool,
/// Set the line-length.
#[arg(long, help_heading = "Format configuration")]
pub line_length: Option<LineLength>,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
pub isolated: bool,
@@ -510,7 +499,6 @@ 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,
@@ -555,7 +543,6 @@ impl FormatCommand {
stdin_filename: self.stdin_filename,
},
CliOverrides {
line_length: self.line_length,
respect_gitignore: resolve_bool_arg(
self.respect_gitignore,
self.no_respect_gitignore,
@@ -632,7 +619,6 @@ 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>>,
@@ -663,12 +649,6 @@ 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);
}

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::{self, File};
use std::hash::Hasher;
@@ -19,7 +20,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, VERSION};
use ruff_linter::warn_user;
use ruff_macros::CacheKey;
use ruff_notebook::NotebookIndex;
use ruff_python_ast::imports::ImportMap;
@@ -101,8 +102,9 @@ impl Cache {
pub(crate) fn open(package_root: PathBuf, settings: &Settings) -> Self {
debug_assert!(package_root.is_absolute(), "package root not canonicalized");
let key = format!("{}", cache_key(&package_root, settings));
let path = PathBuf::from_iter([&settings.cache_dir, Path::new(VERSION), Path::new(&key)]);
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 file = match File::open(&path) {
Ok(file) => file,
@@ -140,7 +142,7 @@ impl Cache {
fn empty(path: PathBuf, package_root: PathBuf) -> Self {
let package = PackageCache {
package_root,
files: FxHashMap::default(),
files: HashMap::new(),
};
Cache::new(path, package)
}
@@ -292,7 +294,7 @@ struct PackageCache {
/// single file "packages", e.g. scripts.
package_root: PathBuf,
/// Mapping of source file path to it's cached data.
files: FxHashMap<RelativePathBuf, FileCache>,
files: HashMap<RelativePathBuf, FileCache>,
}
/// On disk representation of the cache per source file.
@@ -348,16 +350,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(VERSION))?;
fs::create_dir_all(path.join("content"))?;
// Add the CACHEDIR.TAG.
if !cachedir::is_tagged(path)? {

View File

@@ -82,7 +82,7 @@ pub(crate) fn check(
let settings = resolver.resolve(path, pyproject_config);
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
if !resolved_file.is_root()
&& match_exclusion(
resolved_file.path(),
resolved_file.file_name(),

View File

@@ -18,19 +18,17 @@ pub(crate) fn check_stdin(
noqa: flags::Noqa,
fix_mode: flags::FixMode,
) -> Result<Diagnostics> {
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());
}
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| {

View File

@@ -11,7 +11,7 @@ use itertools::Itertools;
use log::{error, warn};
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rustc_hash::FxHashSet;
use similar::TextDiff;
use thiserror::Error;
use tracing::debug;
@@ -19,11 +19,12 @@ use ruff_diagnostics::SourceMap;
use ruff_linter::fs;
use ruff_linter::logging::LogLevel;
use ruff_linter::registry::Rule;
use ruff_linter::rules::flake8_quotes::settings::Quote;
use ruff_linter::rules::isort;
use ruff_linter::settings::rule_table::RuleTable;
use ruff_linter::source_kind::{SourceError, SourceKind};
use ruff_linter::warn_user_once;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
use ruff_python_formatter::{format_module_source, FormatModuleError};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_workspace::resolver::{
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
@@ -106,7 +107,7 @@ pub(crate) fn format(
};
let start = Instant::now();
let (results, mut errors): (Vec<_>, Vec<_>) = paths
let (mut results, mut errors): (Vec<_>, Vec<_>) = paths
.par_iter()
.filter_map(|entry| {
match entry {
@@ -117,14 +118,14 @@ pub(crate) fn format(
return None;
};
let settings = resolver.resolve(path, &pyproject_config);
let resolved_settings = resolver.resolve(path, &pyproject_config);
// Ignore files that are excluded from formatting
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
if !resolved_file.is_root()
&& match_exclusion(
path,
resolved_file.file_name(),
&settings.formatter.exclude,
&resolved_settings.formatter.exclude,
)
{
return None;
@@ -139,7 +140,13 @@ pub(crate) fn format(
Some(
match catch_unwind(|| {
format_path(path, &settings.formatter, source_type, mode, cache)
format_path(
path,
&resolved_settings.formatter,
source_type,
mode,
cache,
)
}) {
Ok(inner) => inner.map(|result| FormatPathResult {
path: resolved_file.path().to_path_buf(),
@@ -161,6 +168,27 @@ 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(),
@@ -170,21 +198,15 @@ 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);
match mode {
FormatMode::Write => {}
FormatMode::Check => {
results.write_changed(&mut stdout().lock())?;
}
FormatMode::Diff => {
results.write_diff(&mut stdout().lock())?;
}
if mode.is_diff() {
results.write_diff(&mut stdout().lock())?;
}
// Report on the formatting changes.
@@ -448,51 +470,27 @@ 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 (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)?;
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)?;
}
}
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;
@@ -500,6 +498,14 @@ 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,
@@ -556,26 +562,6 @@ 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 {
@@ -641,24 +627,6 @@ 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:
@@ -690,123 +658,64 @@ pub(super) fn warn_incompatible_formatter_settings(
pyproject_config: &PyprojectConfig,
resolver: Option<&Resolver>,
) {
// First, collect all rules that are incompatible regardless of the linter-specific settings.
let mut incompatible_rules = FxHashSet::default();
for setting in std::iter::once(&pyproject_config.settings)
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
{
for rule in [
// The formatter might collapse implicit string concatenation on a single line.
Rule::SingleLineImplicitStringConcatenation,
// Flags missing trailing commas when all arguments are on its own line:
// ```python
// def args(
// aaaaaaaa, bbbbbbbbb, cccccccccc, ddddddddd, eeeeeeee, ffffff, gggggggggggg, hhhh
// ):
// pass
// ```
Rule::MissingTrailingComma,
] {
if setting.linter.rules.enabled(rule) {
incompatible_rules.insert(rule);
}
}
}
let mut incompatible_rules = Vec::new();
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(&[
for incompatible_rule in RuleTable::from_iter([
Rule::LineTooLong,
Rule::TabIndentation,
Rule::IndentationWithInvalidMultiple,
Rule::IndentationWithInvalidMultipleComment,
]) && setting.formatter.indent_width.value() != 4
Rule::OverIndented,
Rule::IndentWithSpaces,
Rule::SingleLineImplicitStringConcatenation,
Rule::MissingTrailingComma,
Rule::ProhibitedTrailingComma,
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
])
.iter_enabled()
{
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
}
// Validate all rules that rely on quote styles.
if setting
.linter
.rules
.any_enabled(&[Rule::BadQuotesInlineString, Rule::AvoidableEscapedQuote])
{
match (
setting.linter.flake8_quotes.inline_quotes,
setting.formatter.quote_style,
) {
(Quote::Double, QuoteStyle::Single) => {
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
}
(Quote::Single, QuoteStyle::Double) => {
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
}
_ => {}
if setting.linter.rules.enabled(incompatible_rule) {
incompatible_rules.push(format!("'{}'", incompatible_rule.noqa_code()));
}
}
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
{
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
if !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::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\"`.`");
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'");
}
// 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.force_wrap_aliases != isort_defaults.force_wrap_aliases {
incompatible_options.push("'isort.force-wrap-aliases'");
}
// 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_after_imports != isort_defaults.lines_after_imports {
incompatible_options.push("'isort.lines-after-imports'");
}
// 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.lines_between_types != isort_defaults.lines_between_types {
incompatible_options.push("'isort.lines_between_types'");
}
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`.");
}
}
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(", "));
}
}
}

View File

@@ -3,6 +3,8 @@ 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};
@@ -31,19 +33,17 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
let mode = FormatMode::from_cli(cli);
if pyproject_config.settings.file_resolver.force_exclude {
if let Some(filename) = cli.stdin_filename.as_deref() {
if !python_file_at_path(filename, &pyproject_config, overrides)? {
return Ok(ExitStatus::Success);
}
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,9 +109,14 @@ fn format_source_code(
}
FormatMode::Check => {}
FormatMode::Diff => {
source_kind
.diff(formatted, path, &mut stdout().lock())
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
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();
}
},
FormattedSource::Unchanged => {

View File

@@ -188,8 +188,13 @@ 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() => {
Some(cache) if noqa.into() && fix_mode.is_generate() => {
let relative_path = cache
.relative_path(path)
.expect("wrong package cache for file");
@@ -199,17 +204,7 @@ pub(crate) fn lint_path(
.get(relative_path, &cache_key)
.and_then(|entry| entry.to_diagnostics(path));
if let Some(diagnostics) = cached_diagnostics {
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics, we
// need to avoid reading from and writing to the cache in these modes.
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
}
} {
return Ok(diagnostics);
}
return Ok(diagnostics);
}
// Stash the file metadata for later so when we update the cache it reflects the prerun
@@ -309,25 +304,15 @@ pub(crate) fn lint_path(
if let Some((cache, relative_path, key)) = caching {
// We don't cache parsing errors.
if parse_error.is_none() {
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
// 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(),
),
);
}
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_messages(
&messages,
imports.clone(),
source_kind.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
}
}

View File

@@ -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, HelpFormat};
use crate::args::{Args, CheckCommand, Command, FormatCommand};
use crate::printer::{Flags as PrinterFlags, Printer};
pub mod args;
@@ -101,15 +101,6 @@ 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,
@@ -150,18 +141,12 @@ pub fn run(
commands::version::version(output_format)?;
Ok(ExitStatus::Success)
}
Command::Rule {
rule,
all,
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
Command::Rule { rule, all, format } => {
if all {
commands::rule::rules(output_format)?;
commands::rule::rules(format)?;
}
if let Some(rule) = rule {
commands::rule::rule(rule, output_format)?;
commands::rule::rule(rule, format)?;
}
Ok(ExitStatus::Success)
}
@@ -169,12 +154,8 @@ pub fn run(
commands::config::config(option.as_deref())?;
Ok(ExitStatus::Success)
}
Command::Linter {
format,
mut output_format,
} => {
output_format = resolve_help_output_format(output_format, format);
commands::linter::linter(output_format)?;
Command::Linter { format } => {
commands::linter::linter(format)?;
Ok(ExitStatus::Success)
}
Command::Clean => {

View File

@@ -188,73 +188,6 @@ 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()?;
@@ -276,43 +209,6 @@ 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###"
@@ -334,9 +230,7 @@ fn format_option_inheritance() -> Result<()> {
&ruff_toml,
r#"
extend = "base.toml"
[lint]
extend-select = ["COM812"]
extend-select = ["Q000"]
[format]
quote-style = "single"
@@ -379,7 +273,7 @@ if condition:
print('Should change quotes')
----- stderr -----
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.
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.
"###);
Ok(())
}
@@ -465,27 +359,15 @@ fn conflicting_options() -> Result<()> {
fs::write(
&ruff_toml,
r#"
indent-width = 2
[lint]
select = ["ALL"]
ignore = ["D203", "D212"]
[lint.isort]
lines-after-imports = 3
lines-between-types = 2
[isort]
force-single-line = true
force-wrap-aliases = true
combine-as-imports = true
lines-after-imports = 0
lines-between-types = 2
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"
"#,
)?;
@@ -507,16 +389,8 @@ def say_hy(name: str):
1 file reformatted
----- stderr -----
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
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.
"###);
Ok(())
}
@@ -528,27 +402,15 @@ fn conflicting_options_stdin() -> Result<()> {
fs::write(
&ruff_toml,
r#"
indent-width = 2
[lint]
select = ["ALL"]
ignore = ["D203", "D212"]
[lint.isort]
lines-after-imports = 3
lines-between-types = 2
[isort]
force-single-line = true
force-wrap-aliases = true
combine-as-imports = true
lines-after-imports = 0
lines-between-types = 2
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"
"#,
)?;
@@ -563,112 +425,14 @@ 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`, `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`.
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.
"###);
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"];
@@ -688,8 +452,8 @@ fn test_diff() {
success: false
exit_code: 1
----- stdout -----
--- resources/test/fixtures/unformatted.ipynb:cell 1
+++ resources/test/fixtures/unformatted.ipynb:cell 1
--- resources/test/fixtures/unformatted.ipynb
+++ resources/test/fixtures/unformatted.ipynb
@@ -1,3 +1,4 @@
import numpy
-maths = (numpy.arange(100)**2).sum()
@@ -697,30 +461,6 @@ 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 @@
@@ -729,7 +469,6 @@ fn test_diff() {
+y = 2
z = 3
----- stderr -----
2 files would be reformatted, 1 file left unchanged
"###);
@@ -759,7 +498,6 @@ fn test_diff_no_change() {
+y = 2
z = 3
----- stderr -----
1 file would be reformatted
"###
@@ -793,7 +531,6 @@ fn test_diff_stdin_unformatted() {
+y = 2
z = 3
----- stderr -----
"###);
}

View File

@@ -262,13 +262,9 @@ from test import say_hy
if __name__ == "__main__":
say_hy("dear Ruff contributor")
"#), @r###"
success: false
exit_code: 1
success: true
exit_code: 0
----- 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 -----
"###);
@@ -312,87 +308,3 @@ _ = "---------------------------------------------------------------------------
"###);
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(())
}

View File

@@ -41,7 +41,7 @@ serde_json = { workspace = true }
similar = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
tempfile = "3.8.1"
tempfile = "3.6.0"
toml = { workspace = true, features = ["parse"] }
tracing = { workspace = true }
tracing-indicatif = { workspace = true }

View File

@@ -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, PreviewMode, PyFormatOptions,
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
};
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
@@ -871,7 +871,9 @@ struct BlackOptions {
line_length: NonZeroU16,
#[serde(alias = "skip-magic-trailing-comma")]
skip_magic_trailing_comma: bool,
preview: bool,
#[allow(unused)]
#[serde(alias = "force-exclude")]
force_exclude: Option<String>,
}
impl Default for BlackOptions {
@@ -879,7 +881,7 @@ impl Default for BlackOptions {
Self {
line_length: NonZeroU16::new(88).unwrap(),
skip_magic_trailing_comma: false,
preview: false,
force_exclude: None,
}
}
}
@@ -927,11 +929,6 @@ impl BlackOptions {
} else {
MagicTrailingComma::Respect
})
.with_preview(if self.preview {
PreviewMode::Enabled
} else {
PreviewMode::Disabled
})
}
}

View File

@@ -3,7 +3,6 @@
//! Used for <https://docs.astral.sh/ruff/settings/>.
use std::fmt::Write;
use ruff_python_trivia::textwrap;
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
@@ -126,57 +125,18 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab(
"pyproject.toml",
&format_header(parent_set, ConfigurationFile::PyprojectToml),
field.example,
));
output.push_str(&format_tab(
"ruff.toml",
&format_header(parent_set, ConfigurationFile::RuffToml),
field.example,
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}")
} else {
String::new()
},
field.example
));
output.push('\n');
}
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
format!(
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
tab_name,
header,
textwrap::indent(content, " ")
)
}
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
let fmt = if let Some(set_name) = parent_set.name() {
if set_name == "format" {
String::from(".format")
} else {
format!(".lint.{set_name}")
}
} else {
String::new()
};
match configuration {
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
ConfigurationFile::RuffToml => {
if fmt.is_empty() {
String::new()
} else {
format!("[{}]", fmt.strip_prefix('.').unwrap())
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum ConfigurationFile {
PyprojectToml,
RuffToml,
}
#[derive(Default)]
struct CollectOptionsVisitor {
groups: Vec<(String, OptionSet)>,

View File

@@ -22,16 +22,14 @@ 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 title='Automatic fix available'>{FIX_SYMBOL}</span>")
}
FixAvailability::None => {
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
format!("<span style='opacity: 1'>{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 title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
} else {
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")
};
let status_token = format!("{fix_token} {preview_token}");
@@ -64,7 +62,7 @@ pub(crate) fn generate() -> String {
table_out.push('\n');
table_out.push_str(&format!(
"The {PREVIEW_SYMBOL} emoji indicates that a rule is in [\"preview\"](faq.md#what-is-preview)."
"The {PREVIEW_SYMBOL} emoji indicates that a rule in [\"preview\"](faq.md#what-is-preview)."
));
table_out.push('\n');
table_out.push('\n');

View File

@@ -107,30 +107,6 @@ impl Fix {
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
Self {
edits: vec![edit],
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
pub fn applicable_edits(
edit: Edit,
rest: impl IntoIterator<Item = Edit>,
applicability: Applicability,
) -> Self {
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
edits.sort_by_key(|edit| (edit.start(), edit.end()));
Self {
edits,
applicability,
isolation_level: IsolationLevel::default(),
}
}
/// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
pub fn min_start(&self) -> Option<TextSize> {
self.edits.first().map(Edit::start)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.4"
version = "0.1.2"
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.8.0" }
pyproject-toml = { version = "0.7.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.8.1"
tempfile = "3.6.0"
[features]
default = []

View File

@@ -79,6 +79,3 @@ from ZeroDivisionError
raise IndexError() from ZeroDivisionError
raise IndexError();
# RSE102
raise Foo()

View File

@@ -1,5 +1,3 @@
obj = {}
key in obj.keys() # SIM118
key not in obj.keys() # SIM118

View File

@@ -1,18 +0,0 @@
import trio
async def foo():
with trio.fail_after():
...
async def foo():
with trio.fail_at():
await ...
async def foo():
with trio.move_on_after():
...
async def foo():
with trio.move_at():
await ...

View File

@@ -1,64 +0,0 @@
import trio
async def func() -> None:
trio.run(foo) # OK, not async
# OK
await trio.aclose_forcefully(foo)
await trio.open_file(foo)
await trio.open_ssl_over_tcp_listeners(foo, foo)
await trio.open_ssl_over_tcp_stream(foo, foo)
await trio.open_tcp_listeners(foo)
await trio.open_tcp_stream(foo, foo)
await trio.open_unix_socket(foo)
await trio.run_process(foo)
await trio.sleep(5)
await trio.sleep_until(5)
await trio.lowlevel.cancel_shielded_checkpoint()
await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint_if_cancelled()
await trio.lowlevel.open_process(foo)
await trio.lowlevel.permanently_detach_coroutine_object(foo)
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
await trio.lowlevel.wait_readable(foo)
await trio.lowlevel.wait_task_rescheduled(foo)
await trio.lowlevel.wait_writable(foo)
# TRIO105
trio.aclose_forcefully(foo)
trio.open_file(foo)
trio.open_ssl_over_tcp_listeners(foo, foo)
trio.open_ssl_over_tcp_stream(foo, foo)
trio.open_tcp_listeners(foo)
trio.open_tcp_stream(foo, foo)
trio.open_unix_socket(foo)
trio.run_process(foo)
trio.serve_listeners(foo, foo)
trio.serve_ssl_over_tcp(foo, foo, foo)
trio.serve_tcp(foo, foo)
trio.sleep(foo)
trio.sleep_forever()
trio.sleep_until(foo)
trio.lowlevel.cancel_shielded_checkpoint()
trio.lowlevel.checkpoint()
trio.lowlevel.checkpoint_if_cancelled()
trio.lowlevel.open_process()
trio.lowlevel.permanently_detach_coroutine_object(foo)
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
trio.lowlevel.temporarily_detach_coroutine_object(foo)
trio.lowlevel.wait_readable(foo)
trio.lowlevel.wait_task_rescheduled(foo)
trio.lowlevel.wait_writable(foo)
async with await trio.open_file(foo): # Ok
pass
async with trio.open_file(foo): # TRIO105
pass
def func() -> None:
# TRIO105 (without fix)
trio.open_file(foo)

View File

@@ -1,28 +0,0 @@
import trio
from trio import sleep
async def func():
await trio.sleep(0) # TRIO115
await trio.sleep(1) # OK
await trio.sleep(0, 1) # OK
await trio.sleep(...) # OK
await trio.sleep() # OK
trio.sleep(0) # TRIO115
foo = 0
trio.sleep(foo) # TRIO115
trio.sleep(1) # OK
time.sleep(0) # OK
sleep(0) # TRIO115
bar = "bar"
trio.sleep(bar)
trio.sleep(0) # TRIO115
def func():
trio.run(trio.sleep(0)) # TRIO115

View File

@@ -1,7 +1,7 @@
[tool.ruff]
line-length = 88
[tool.ruff.lint.isort]
[tool.ruff.isort]
lines-after-imports = 3
lines-between-types = 2
known-local-folder = ["ruff"]

View File

@@ -1,106 +0,0 @@
def func():
import numpy as np
np.add_docstring
np.add_newdoc
np.add_newdoc_ufunc
np.asfarray([1,2,3])
np.byte_bounds(np.array([1,2,3]))
np.cast
np.cfloat(12+34j)
np.clongfloat(12+34j)
np.compat
np.complex_(12+34j)
np.DataSource
np.deprecate
np.deprecate_with_doc
np.disp(10)
np.fastCopyAndTranspose
np.find_common_type
np.get_array_wrap
np.float_
np.geterrobj
np.Inf
np.Infinity
np.infty
np.issctype
np.issubclass_(np.int32, np.integer)
np.issubsctype
np.mat
np.maximum_sctype
np.NaN
np.nbytes[np.int64]
np.NINF
np.NZERO
np.longcomplex(12+34j)
np.longfloat(12+34j)
np.lookfor
np.obj2sctype(int)
np.PINF
np.PZERO
np.recfromcsv
np.recfromtxt
np.round_(12.34)
np.safe_eval
np.sctype2char
np.sctypes
np.seterrobj
np.set_numeric_ops
np.set_string_function
np.singlecomplex(12+1j)
np.string_("asdf")
np.source
np.tracemalloc_domain
np.unicode_("asf")
np.who()

View File

@@ -12,19 +12,3 @@ 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

View File

@@ -40,11 +40,5 @@ 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,

View File

@@ -69,5 +69,3 @@ while 1:
#: E701:2:3
a = \
5;
#:
with x(y) as z: ...

View File

@@ -4,9 +4,6 @@ if type(res) == type(42):
#: E721
if type(res) != type(""):
pass
#: E721
if type(res) == memoryview:
pass
#: Okay
import types
@@ -50,14 +47,6 @@ if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if isinstance(res, memoryview):
pass
#: Okay
if type(res) is type:
pass
#: E721
if type(res) == type:
pass
#: Okay
def func_histype(a, b, c):
pass

View File

@@ -8,19 +8,3 @@ def ends_in_quote():
def contains_quote():
'Sum"\\mary.'
# OK
def contains_triples(t):
"""('''|\""")"""
# OK
def contains_triples(t):
'''(\'''|""")'''
# TODO: here should raise D300 for using dobule quotes instead,
# because escaped double quote does allow us.
def contains_triples(t):
'''(\""")'''

View File

@@ -31,7 +31,3 @@ def make_unique_pod_id(pod_id: str) -> str | None:
:param pod_id: requested pod name
:return: ``str`` valid Pod name of appropriate length
"""
def shouldnt_add_raw_here2():
u"Sum\\mary."

View File

@@ -1,5 +1,5 @@
def f(tup):
x, y = tup
x, y = tup # this does NOT trigger F841
def f():
@@ -7,17 +7,17 @@ def f():
def f():
(x, y) = coords = 1, 2
(x, y) = coords = 1, 2 # this does NOT trigger F841
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2
(x, y) = coords = 1, 2 # this triggers F841 on coords
def f():
coords = (x, y) = 1, 2
coords = (x, y) = 1, 2 # this triggers F841 on coords
def f():

View File

@@ -1,32 +0,0 @@
"""Test fix for issue #8441.
Ref: https://github.com/astral-sh/ruff/issues/8441
"""
def foo():
...
def bar():
a = foo()
b, c = foo()
def baz():
d, _e = foo()
print(d)
def qux():
f, _ = foo()
print(f)
def quux():
g, h = foo()
print(g, h)
def quuz():
_i, _j = foo()

View File

@@ -49,13 +49,6 @@ 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
@@ -70,10 +63,6 @@ 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):

View File

@@ -1,34 +0,0 @@
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]

View File

@@ -57,9 +57,3 @@ r'\%03o' % (ord(c),)
'(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD')
'%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, )
"%d" % (1 if x > 0 else 2)
# Special cases for %c allowing single character strings
# https://github.com/astral-sh/ruff/issues/8406
"%c" % ("x",)
"%c" % "x"
"%c" % "œ"

View File

@@ -1,22 +0,0 @@
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]

View File

@@ -1,56 +0,0 @@
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
...

View File

@@ -5,6 +5,4 @@ extend-exclude = [
"migrations",
"with_excluded_file/other_excluded_file.py",
]
[tool.ruff.lint]
per-file-ignores = { "__init__.py" = ["F401"] }

View File

@@ -23,11 +23,3 @@ MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)
# Regression test for: https://github.com/astral-sh/ruff/issues/8402#issuecomment-1788787357
S3File = NamedTuple(
"S3File",
[
("dataHPK",* str),
],
)

View File

@@ -1,68 +0,0 @@
import asyncio, socket
# These should be fixed
try:
pass
except asyncio.TimeoutError:
pass
try:
pass
except socket.timeout:
pass
# Should NOT be in parentheses when replaced
try:
pass
except (asyncio.TimeoutError,):
pass
try:
pass
except (socket.timeout,):
pass
try:
pass
except (asyncio.TimeoutError, socket.timeout,):
pass
# Should be kept in parentheses (because multiple)
try:
pass
except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError):
pass
# First should change, second should not
from .mmap import error
try:
pass
except (asyncio.TimeoutError, error):
pass
# These should not change
from foo import error
try:
pass
except (TimeoutError, error):
pass
try:
pass
except:
pass
try:
pass
except TimeoutError:
pass
try:
pass
except (TimeoutError, KeyError):
pass

View File

@@ -43,12 +43,6 @@ def yes_four(x: Dict[int, str]):
del x[:]
def yes_five(x: Dict[int, str]):
# FURB131
del x[:]
x = 1
# these should not
del names["key"]

View File

@@ -1,51 +0,0 @@
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

View File

@@ -1,67 +0,0 @@
foo = None
# Error.
type(foo) is type(None)
type(None) is type(foo)
type(None) is type(None)
type(foo) is not type(None)
type(None) is not type(foo)
type(None) is not type(None)
type(foo) == type(None)
type(None) == type(foo)
type(None) == type(None)
type(foo) != type(None)
type(None) != type(foo)
type(None) != type(None)
# Ok.
foo is None
foo is not None
None is foo
None is not foo
None is None
None is not None
foo is type(None)
type(foo) is None
type(None) is None
foo is not type(None)
type(foo) is not None
type(None) is not None
foo == type(None)
type(foo) == None
type(None) == None
foo != type(None)
type(foo) != None
type(None) != None
type(foo) > type(None)

View File

@@ -43,6 +43,3 @@ if 1 is {1}:
if "a" == "a":
pass
if 1 in {*[1]}:
pass

View File

@@ -45,11 +45,3 @@ x = f"string { # And here's a comment with an unusual parenthesis:
# And here's a comment with a greek alpha:
foo # And here's a comment with an unusual punctuation mark:
}"
# At runtime the attribute will be stored as Greek small letter mu instead of
# micro sign because of PEP 3131's NFKC normalization
class Labware:
µL = 1.5
assert getattr(Labware(), "µL") == 1.5

View File

@@ -1,7 +1,5 @@
[tool.ruff]
src = [".", "python_modules/*"]
exclude = ["examples/excluded"]
[tool.ruff.lint]
extend-select = ["I001"]
extend-ignore = ["F841"]

View File

@@ -170,7 +170,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
expr.start(),
));
if expr.implicit_concatenated {
if pydocstyle::helpers::should_ignore_docstring(expr) {
#[allow(deprecated)]
let location = checker.locator.compute_source_location(expr.start());
warn_user!(

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Operator};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator};
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
use ruff_diagnostics::Diagnostic;
@@ -15,8 +15,8 @@ use crate::rules::{
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
};
use crate::settings::types::PythonVersion;
@@ -158,9 +158,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
@@ -317,9 +314,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::Numpy2Deprecation) {
numpy::rules::numpy_2_0_deprecation(checker, expr);
}
if checker.enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_attribute(checker, expr);
}
@@ -369,18 +363,20 @@ 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::StringLiteral(ast::ExprStringLiteral { value: string, .. }) =
value.as_ref()
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(val),
..
}) = value.as_ref()
{
if attr == "join" {
// "...".join(...) call
if checker.enabled(Rule::StaticJoinToFString) {
flynt::rules::static_join_to_fstring(checker, expr, string);
flynt::rules::static_join_to_fstring(checker, expr, val);
}
} else if attr == "format" {
// "...".format(...) call
let location = expr.range();
match pyflakes::format::FormatSummary::try_from(string.as_ref()) {
match pyflakes::format::FormatSummary::try_from(val.as_ref()) {
Err(e) => {
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
checker.diagnostics.push(Diagnostic::new(
@@ -425,7 +421,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::call(
checker, string, location,
checker, val, location,
);
}
}
@@ -466,11 +462,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_call(checker, func);
}
}
if checker.enabled(Rule::NonPEP604Isinstance) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::use_pep604_isinstance(checker, expr, func, args);
@@ -758,9 +749,6 @@ 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);
}
@@ -920,18 +908,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);
}
if checker.enabled(Rule::TrioSyncCall) {
flake8_trio::rules::sync_call(checker, call);
}
if checker.enabled(Rule::TrioZeroSleepCall) {
flake8_trio::rules::zero_sleep_call(checker, call);
}
}
Expr::Dict(
dict @ ast::ExprDict {
@@ -1008,7 +987,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
right,
range: _,
}) => {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = left.as_ref() {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(ast::StringConstant { value, .. }),
..
}) = left.as_ref()
{
if checker.any_enabled(&[
Rule::PercentFormatInvalidFormat,
Rule::PercentFormatExpectedMapping,
@@ -1241,36 +1224,42 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
comparators,
);
}
if checker.enabled(Rule::TypeNoneComparison) {
refurb::rules::type_none_comparison(checker, compare);
}
if checker.enabled(Rule::SingleItemMembershipTest) {
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
}
}
Expr::NumberLiteral(_) => {
Expr::Constant(ast::ExprConstant {
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
range: _,
}) => {
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
}
}
Expr::BytesLiteral(_) => {
Expr::Constant(ast::ExprConstant {
value: Constant::Bytes(_),
range: _,
}) => {
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
}
}
Expr::StringLiteral(string) => {
Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
range: _,
}) => {
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
if let Some(diagnostic) =
flake8_bandit::rules::hardcoded_bind_all_interfaces(string)
flake8_bandit::rules::hardcoded_bind_all_interfaces(value, expr.range())
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::HardcodedTempFile) {
flake8_bandit::rules::hardcoded_tmp_directory(checker, string);
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
}
if checker.enabled(Rule::UnicodeKindPrefix) {
pyupgrade::rules::unicode_kind_prefix(checker, string);
pyupgrade::rules::unicode_kind_prefix(checker, expr, value.unicode);
}
if checker.source_type.is_stub() {
if checker.enabled(Rule::StringOrBytesTooLong) {

View File

@@ -12,8 +12,8 @@ use crate::rules::{
airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger,
flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi,
flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots,
flake8_tidy_imports, flake8_trio, flake8_type_checking, mccabe, pandas_vet, pep8_naming,
perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint,
pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
@@ -530,9 +530,6 @@ 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() {
@@ -709,9 +706,6 @@ 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() {
@@ -1006,13 +1000,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pyupgrade::rules::os_error_alias_raise(checker, item);
}
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
if let Some(item) = exc {
pyupgrade::rules::timeout_error_alias_raise(checker, item);
}
}
}
if checker.enabled(Rule::RaiseVanillaClass) {
if let Some(expr) = exc {
tryceratops::rules::raise_vanilla_class(checker, expr);
@@ -1199,12 +1186,6 @@ 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);
}
if checker.enabled(Rule::TrioTimeoutWithoutAwait) {
flake8_trio::rules::timeout_without_await(checker, with_stmt, items);
}
}
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
if checker.enabled(Rule::FunctionUsesLoopVariable) {
@@ -1311,11 +1292,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_handlers(checker, handlers);
}
if checker.enabled(Rule::TimeoutErrorAlias) {
if checker.settings.target_version >= PythonVersion::Py310 {
pyupgrade::rules::timeout_error_alias_handlers(checker, handlers);
}
}
if checker.enabled(Rule::PytestAssertInExcept) {
flake8_pytest_style::rules::assert_in_exception_handler(checker, handlers);
}

View File

@@ -31,8 +31,9 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use ruff_python_ast::{
self as ast, Arguments, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, Suite, UnaryOp,
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt,
Suite, UnaryOp,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -786,7 +787,11 @@ where
&& self.semantic.in_type_definition()
&& self.semantic.future_annotations()
{
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
..
}) = expr
{
self.deferred.string_type_definitions.push((
expr.range(),
value,
@@ -1181,7 +1186,10 @@ where
}
}
}
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
range: _,
}) => {
if self.semantic.in_type_definition()
&& !self.semantic.in_literal()
&& !self.semantic.in_f_string()

View File

@@ -131,7 +131,7 @@ pub(crate) fn check_noqa(
|| settings
.external
.iter()
.any(|external| code.starts_with(external))
.any(|pattern| pattern.matches(code))
{
valid_codes.push(code);
} else {

View File

@@ -211,7 +211,6 @@ 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)]
@@ -271,14 +270,12 @@ 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)]
@@ -290,11 +287,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "101") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction),
(Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction),
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
// flake8-builtins
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
(Flake8Builtins, "002") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinArgumentShadowing),
@@ -502,7 +494,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "038") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
(Pyupgrade, "041") => (RuleGroup::Preview, rules::pyupgrade::rules::TimeoutErrorAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -862,7 +853,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Numpy, "001") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedTypeAlias),
(Numpy, "002") => (RuleGroup::Stable, rules::numpy::rules::NumpyLegacyRandom),
(Numpy, "003") => (RuleGroup::Stable, rules::numpy::rules::NumpyDeprecatedFunction),
(Numpy, "201") => (RuleGroup::Preview, rules::numpy::rules::Numpy2Deprecation),
// ruff
(Ruff, "001") => (RuleGroup::Stable, rules::ruff::rules::AmbiguousUnicodeCharacterString),
@@ -945,8 +935,6 @@ 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, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),

View File

@@ -3,7 +3,7 @@
use std::iter::FusedIterator;
use ruff_python_ast::{self as ast, Stmt, Suite};
use ruff_python_ast::{self as ast, Constant, Expr, 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: expr,
range: _,
}) = stmt
{
if expr.is_string_literal_expr() {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Constant(ast::ExprConstant {
value: Constant::Str(..),
..
}) = value.as_ref()
{
for line in UniversalNewlineIterator::with_offset(
self.locator.slice(expr.as_ref()),
expr.start(),
self.locator.slice(value.as_ref()),
value.start(),
) {
self.string_lines.push(line.start());
}

View File

@@ -1,23 +1,30 @@
//! Extract docstrings from an AST.
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_ast::{self as ast, Constant, Expr, 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<&ast::ExprStringLiteral> {
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
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.
value.as_string_literal_expr()
if !matches!(
value.as_ref(),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(_),
..
})
) {
return None;
}
Some(value)
}
/// Extract a docstring from a `Definition`.
pub(crate) fn extract_docstring<'a>(
definition: &'a Definition<'a>,
) -> Option<&'a ast::ExprStringLiteral> {
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
match definition {
Definition::Module(module) => docstring_from(module.python_ast),
Definition::Member(member) => docstring_from(member.body()),

View File

@@ -1,7 +1,7 @@
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use ruff_python_ast::ExprStringLiteral;
use ruff_python_ast::Expr;
use ruff_python_semantic::Definition;
use ruff_text_size::{Ranged, TextRange};
@@ -14,8 +14,7 @@ pub(crate) mod styles;
#[derive(Debug)]
pub(crate) struct Docstring<'a> {
pub(crate) definition: &'a Definition<'a>,
/// The literal AST node representing the docstring.
pub(crate) expr: &'a ExprStringLiteral,
pub(crate) expr: &'a Expr,
/// 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`].

View File

@@ -1,7 +1,8 @@
use colored::Colorize;
use log::warn;
use pyproject_toml::PyProjectToml;
use pyproject_toml::{BuildSystem, Project};
use ruff_text_size::{TextRange, TextSize};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::Diagnostic;
use ruff_source_file::SourceFile;
@@ -12,6 +13,16 @@ 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();

View File

@@ -64,9 +64,6 @@ pub enum Linter {
/// [flake8-async](https://pypi.org/project/flake8-async/)
#[prefix = "ASYNC"]
Flake8Async,
/// [flake8-trio](https://pypi.org/project/flake8-trio/)
#[prefix = "TRIO"]
Flake8Trio,
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
#[prefix = "S"]
Flake8Bandit,

View File

@@ -1,6 +1,7 @@
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;
@@ -78,7 +79,13 @@ 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 ast::ExprStringLiteral { value: task_id, .. } = keyword.value.as_string_literal_expr()?;
let task_id = match &keyword.value {
Expr::Constant(constant) => match &constant.value {
Constant::Str(ast::StringConstant { value, .. }) => value,
_ => return None,
},
_ => return None,
};
// If the target name is the same as the task_id, no violation.
if id == task_id {

View File

@@ -163,15 +163,11 @@ mod tests {
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
&[]
));
assert!(comment_contains_code("# )", &[]));
// This used to return true, but our parser has gotten a bit better
// at rejecting invalid Python syntax. And indeed, this is not valid
// Python code.
assert!(!comment_contains_code(
assert!(comment_contains_code(
"# app_label=\"core\", model=\"user\"",
&[]
));
assert!(comment_contains_code("# )", &[]));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():", &[]));

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, CmpOp, Expr};
use ruff_python_ast::{self as ast, CmpOp, Constant, 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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) = slice.as_ref()
{
if *i == 0 {
if let (
[CmpOp::Eq | CmpOp::NotEq],
[Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(n),
[Expr::Constant(ast::ExprConstant {
value: Constant::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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
[Expr::Constant(ast::ExprConstant {
value: Constant::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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
[Expr::Constant(ast::ExprConstant {
value: Constant::Int(_),
..
})],
) = (ops, comparators)
@@ -294,10 +294,13 @@ 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::StringLiteral(ast::ExprStringLiteral { value, .. })],
[Expr::Constant(ast::ExprConstant {
value: Constant::Str(s),
..
})],
) = (ops, comparators)
{
if value.len() == 1 {
if s.len() == 1 {
if checker.enabled(Rule::SysVersionCmpStr10) {
checker
.diagnostics

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Constant, 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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
if let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) = upper.as_ref()
{
@@ -194,8 +194,8 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
}
}
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) => {
if *i == 2 && checker.enabled(Rule::SysVersion2) {

View File

@@ -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, Expr, ParameterWithDefault, Stmt};
use ruff_python_ast::{self as ast, Constant, 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.lint.flake8-annotations]
/// [tool.ruff.flake8-annotations]
/// mypy-init-return = true
/// ```
///
@@ -431,7 +431,10 @@ 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 !value.is_none_literal_expr() {
if !matches!(
value,
Expr::Constant(constant) if constant.value.is_none()
) {
return false;
}
}
@@ -448,10 +451,9 @@ fn check_dynamically_typed<F>(
) where
F: FnOnce() -> String,
{
if let Expr::StringLiteral(ast::ExprStringLiteral {
if let Expr::Constant(ast::ExprConstant {
range,
value: string,
..
value: Constant::Str(string),
}) = annotation
{
// Quoted annotations

View File

@@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
use regex::Regex;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_python_semantic::SemanticModel;
@@ -10,7 +10,10 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
pub(super) fn string_literal(expr: &Expr) -> Option<&str> {
match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(string),
..
}) => Some(string),
_ => None,
}
}

View File

@@ -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, Expr, Operator};
use ruff_python_ast::{self as ast, Constant, 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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(int),
Expr::Constant(ast::ExprConstant {
value: Constant::Int(int),
..
}) => match int.as_u16() {
Some(value) => Ok(Some(value)),

View File

@@ -1,6 +1,7 @@
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`).
@@ -34,9 +35,9 @@ impl Violation for HardcodedBindAllInterfaces {
}
/// S104
pub(crate) fn hardcoded_bind_all_interfaces(string: &ExprStringLiteral) -> Option<Diagnostic> {
if string.value == "0.0.0.0" {
Some(Diagnostic::new(HardcodedBindAllInterfaces, string.range))
pub(crate) fn hardcoded_bind_all_interfaces(value: &str, range: TextRange) -> Option<Diagnostic> {
if value == "0.0.0.0" {
Some(Diagnostic::new(HardcodedBindAllInterfaces, range))
} else {
None
}

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -55,7 +55,10 @@ 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::StringLiteral(ast::ExprStringLiteral { value, .. }) => value,
Expr::Constant(ast::ExprConstant {
value: Constant::Str(string),
..
}) => string,
_ => return None,
},
// obj.password = "s3cr3t"

View File

@@ -2,6 +2,7 @@ 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;
@@ -51,13 +52,13 @@ impl Violation for HardcodedTempFile {
}
/// S108
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: &ast::ExprStringLiteral) {
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value: &str) {
if !checker
.settings
.flake8_bandit
.hardcoded_tmp_directory
.iter()
.any(|prefix| string.value.starts_with(prefix))
.any(|prefix| value.starts_with(prefix))
{
return;
}
@@ -76,8 +77,8 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: &ast::ExprS
checker.diagnostics.push(Diagnostic::new(
HardcodedTempFile {
string: string.value.clone(),
string: value.to_string(),
},
string.range,
expr.range(),
));
}

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -66,7 +66,10 @@ 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::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }) => (),
Expr::Constant(ast::ExprConstant {
value: Constant::Bool(true),
..
}) => (),
Expr::Call(ast::ExprCall { func, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if id != "select_autoescape" {

View File

@@ -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 keyword.value.is_none_literal_expr() {
if is_const_none(&keyword.value) {
checker.diagnostics.push(Diagnostic::new(
RequestWithoutTimeout { implicit: false },
keyword.range(),

View File

@@ -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, Expr, Keyword};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
@@ -38,22 +38,18 @@ use crate::{
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
#[violation]
pub struct SubprocessPopenWithShellEqualsTrue {
safety: Safety,
is_exact: bool,
seems_safe: bool,
}
impl Violation for SubprocessPopenWithShellEqualsTrue {
#[derive_message_formats]
fn message(&self) -> String {
match (self.safety, self.is_exact) {
(Safety::SeemsSafe, true) => format!(
if self.seems_safe {
format!(
"`subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`"
),
(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"),
)
} else {
format!("`subprocess` call with `shell=True` identified, security issue")
}
}
}
@@ -93,18 +89,16 @@ impl Violation for SubprocessWithoutShellEqualsTrue {
}
/// ## What it does
/// Checks for method calls that set the `shell` parameter to `true` or another
/// truthy value when invoking a subprocess.
/// Checks for method calls that set the `shell` parameter to `true` when
/// invoking a subprocess.
///
/// ## Why is this bad?
/// 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.
/// 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.
///
/// ## Known problems
/// Prone to false positives as it is triggered on any function call with a
@@ -121,18 +115,12 @@ impl Violation for SubprocessWithoutShellEqualsTrue {
/// ## References
/// - [Python documentation: Security Considerations](https://docs.python.org/3/library/subprocess.html#security-considerations)
#[violation]
pub struct CallWithShellEqualsTrue {
is_exact: bool,
}
pub struct CallWithShellEqualsTrue;
impl Violation for CallWithShellEqualsTrue {
#[derive_message_formats]
fn message(&self) -> String {
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")
}
format!("Function call with `shell=True` parameter identified, security issue")
}
}
@@ -174,15 +162,16 @@ impl Violation for CallWithShellEqualsTrue {
/// - [Python documentation: `subprocess`](https://docs.python.org/3/library/subprocess.html)
#[violation]
pub struct StartProcessWithAShell {
safety: Safety,
seems_safe: bool,
}
impl Violation for StartProcessWithAShell {
#[derive_message_formats]
fn message(&self) -> String {
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"),
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")
}
}
}
@@ -295,14 +284,13 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
match shell_keyword {
// S602
Some(ShellKeyword {
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
truthiness: Truthiness::Truthy,
keyword,
}) => {
if checker.enabled(Rule::SubprocessPopenWithShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
SubprocessPopenWithShellEqualsTrue {
safety: Safety::from(arg),
is_exact: matches!(truthiness, Truthiness::True),
seems_safe: shell_call_seems_safe(arg),
},
keyword.range(),
));
@@ -310,7 +298,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
}
// S603
Some(ShellKeyword {
truthiness: Truthiness::False | Truthiness::Falsey | Truthiness::Unknown,
truthiness: Truthiness::Falsey | Truthiness::Unknown,
keyword,
}) => {
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
@@ -332,18 +320,15 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
}
}
} else if let Some(ShellKeyword {
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
truthiness: Truthiness::Truthy,
keyword,
}) = shell_keyword
{
// S604
if checker.enabled(Rule::CallWithShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
CallWithShellEqualsTrue {
is_exact: matches!(truthiness, Truthiness::True),
},
keyword.range(),
));
checker
.diagnostics
.push(Diagnostic::new(CallWithShellEqualsTrue, keyword.range()));
}
}
@@ -353,7 +338,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 {
safety: Safety::from(arg),
seems_safe: shell_call_seems_safe(arg),
},
arg.range(),
));
@@ -391,7 +376,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
(
Some(CallKind::Subprocess),
Some(ShellKeyword {
truthiness: Truthiness::True | Truthiness::Truthy,
truthiness: Truthiness::Truthy,
keyword: _,
})
)
@@ -468,22 +453,16 @@ fn find_shell_keyword<'a>(
})
}
#[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 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(_),
..
})
)
}
/// Return `true` if the string appears to be a full file path.

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Int};
use ruff_python_ast::{self as ast, Constant, 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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(Int::ZERO | Int::ONE),
Expr::Constant(ast::ExprConstant {
value: Constant::Int(Int::ZERO | Int::ONE),
..
})
) {

View File

@@ -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::StringLiteral(ast::ExprStringLiteral { value: url, .. })) = &call.arguments.find_argument("url", 0) {
if let Some(Expr::Constant(ast::ExprConstant { value: ast::Constant::Str(url), .. })) = &call.arguments.find_argument("url", 0) {
let url = url.trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;

View File

@@ -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, Expr, ExprAttribute, ExprCall};
use ruff_python_ast::{self as ast, Constant, 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::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
let Expr::Constant(ast::ExprConstant {
value: Constant::Int(i),
..
}) = argument
else {

View File

@@ -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 truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
S602.py:11:15: S602 `subprocess` call with `shell=True` 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 truthy `shell` seems safe, but may be
13 | Popen("true", shell={1: 1})
|
S602.py:12:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
S602.py:12:15: S602 `subprocess` call with `shell=True` 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 truthy `shell` seems safe, but may be
14 | Popen("true", shell=(1,))
|
S602.py:13:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
S602.py:13:15: S602 `subprocess` call with `shell=True` 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 truthy `shell` seems safe, but may be
14 | Popen("true", shell=(1,))
|
S602.py:14:15: S602 `subprocess` call with truthy `shell` seems safe, but may be changed in the future; consider rewriting without `shell`
S602.py:14:15: S602 `subprocess` call with `shell=True` seems safe, but may be changed in the future; consider rewriting without `shell`
|
12 | Popen("true", shell=[1])
13 | Popen("true", shell={1: 1})

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{self as ast, Constant, 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,3 +62,14 @@ 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(_),
..
})
)
}

View File

@@ -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;
use crate::rules::flake8_boolean_trap::helpers::{is_allowed_func_def, is_boolean};
/// ## What it does
/// Checks for the use of boolean positional arguments in function definitions,
@@ -117,10 +117,7 @@ pub(crate) fn boolean_default_value_positional_argument(
range: _,
} in parameters.posonlyargs.iter().chain(&parameters.args)
{
if default
.as_ref()
.is_some_and(|default| default.is_boolean_literal_expr())
{
if default.as_ref().is_some_and(|default| is_boolean(default)) {
checker.diagnostics.push(Diagnostic::new(
BooleanDefaultValuePositionalArgument,
parameter.name.range(),

View File

@@ -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;
use crate::rules::flake8_boolean_trap::helpers::{allow_boolean_trap, is_boolean};
/// ## 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| arg.is_boolean_literal_expr()) {
for arg in args.iter().filter(|arg| is_boolean(arg)) {
checker
.diagnostics
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parameters};
use ruff_python_ast::{self as ast, Constant, Decorator, Expr, ParameterWithDefault, Parameters};
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
@@ -126,7 +126,10 @@ 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::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
Expr::Constant(ast::ExprConstant {
value: Constant::Str(ast::StringConstant { value, .. }),
..
}) => value == "bool",
_ => false,
};
if !hint || !checker.semantic().is_builtin("bool") {

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Stmt};
use ruff_python_ast::{self as ast, Arguments, Constant, 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: _ }) => {
matches!(
value.as_ref(),
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
)
}
Stmt::Expr(ast::StmtExpr { value, range: _ }) => match value.as_ref() {
Expr::Constant(ast::ExprConstant { value, .. }) => {
matches!(value, Constant::Str(..) | Constant::Ellipsis)
}
_ => false,
},
_ => false,
})
}

View File

@@ -27,11 +27,6 @@ use crate::checkers::ast::Checker;
/// raise AssertionError
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as changing an `assert` to a
/// `raise` will change the behavior of your program when running in
/// optimized mode (`python -O`).
///
/// ## References
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
#[violation]

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::Expr;
use ruff_python_ast::{self as 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 elt.is_literal_expr() {
if let Expr::Constant(ast::ExprConstant { value, .. }) = elt {
let comparable_value: ComparableExpr = elt.into();
if !seen_values.insert(comparable_value) {
checker.diagnostics.push(Diagnostic::new(
DuplicateValue {
value: checker.generator().expr(elt),
value: checker.generator().constant(value),
},
elt.range(),
));

View File

@@ -50,16 +50,6 @@ use crate::checkers::ast::Checker;
/// return arg
/// ```
///
/// If the use of a singleton is intentional, assign the result call to a
/// module-level variable, and use that variable in the default argument:
/// ```python
/// ERROR = ValueError("Hosts weren't successfully added")
///
///
/// def add_host(error: Exception = ERROR) -> None:
/// ...
/// ```
///
/// ## Options
/// - `flake8-bugbear.extend-immutable-calls`
#[violation]
@@ -72,9 +62,9 @@ impl Violation for FunctionCallInDefaultArgument {
fn message(&self) -> String {
let FunctionCallInDefaultArgument { name } = self;
if let Some(name) = name {
format!("Do not perform function call `{name}` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
format!("Do not perform function call `{name}` in argument defaults")
} else {
format!("Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
format!("Do not perform function call in argument defaults")
}
}
}

View File

@@ -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, Expr};
use ruff_python_ast::{self as ast, Constant, Expr};
use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use ruff_text_size::Ranged;
@@ -66,7 +66,11 @@ pub(crate) fn getattr_with_constant(
if obj.is_starred_expr() {
return;
}
let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = arg else {
let Expr::Constant(ast::ExprConstant {
value: Constant::Str(ast::StringConstant { value, .. }),
..
}) = arg
else {
return;
};
if !is_identifier(value) {

Some files were not shown because too many files have changed in this diff Show More