Compare commits
64 Commits
4404_fix_e
...
charlie/do
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fbec8e6a2 | ||
|
|
ac146e11f0 | ||
|
|
1229600e1d | ||
|
|
ccf34aae8c | ||
|
|
341b12d918 | ||
|
|
3d7411bfaf | ||
|
|
1eccbbb60e | ||
|
|
e71f044f0d | ||
|
|
f194572be8 | ||
|
|
62e2c46f98 | ||
|
|
9419d3f9c8 | ||
|
|
9b5fb8f38f | ||
|
|
d7c7484618 | ||
|
|
bc63cc9b3c | ||
|
|
bf1a94ee54 | ||
|
|
c792c10eaa | ||
|
|
f9ffb3d50d | ||
|
|
2b76d88bd3 | ||
|
|
41ef17b007 | ||
|
|
0aa21277c6 | ||
|
|
ecf61d49fa | ||
|
|
d99b3bf661 | ||
|
|
e47aa468d5 | ||
|
|
6155fd647d | ||
|
|
4634560c80 | ||
|
|
10885d09a1 | ||
|
|
44156f6962 | ||
|
|
f551c9aad2 | ||
|
|
653dbb6d17 | ||
|
|
db301c14bd | ||
|
|
1336ca601b | ||
|
|
3973836420 | ||
|
|
a332f078db | ||
|
|
e0339b538b | ||
|
|
07b6b7401f | ||
|
|
1db7d9e759 | ||
|
|
621e9ace88 | ||
|
|
f9f77cf617 | ||
|
|
1a2bd984f2 | ||
|
|
4717d0779f | ||
|
|
07409ce201 | ||
|
|
2c0ec97782 | ||
|
|
e520a3a721 | ||
|
|
fde5dbc9aa | ||
|
|
b4bd5a5acb | ||
|
|
acb23dce3c | ||
|
|
30734f06fd | ||
|
|
4547002eb7 | ||
|
|
310abc769d | ||
|
|
b369288833 | ||
|
|
6f7d3cc798 | ||
|
|
d9e59b21cd | ||
|
|
6929fcc55f | ||
|
|
7bc33a8d5f | ||
|
|
6331598511 | ||
|
|
17f1ecd56e | ||
|
|
062b6e5c2b | ||
|
|
773e79b481 | ||
|
|
dfb04e679e | ||
|
|
5c5d2815af | ||
|
|
4cc3cdba16 | ||
|
|
a797e05602 | ||
|
|
62aa77df31 | ||
|
|
64bd955c58 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -76,6 +76,9 @@ jobs:
|
||||
cargo insta test --all --all-features
|
||||
git diff --exit-code
|
||||
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
|
||||
# Skipped as it's currently broken. The resource were moved from the
|
||||
# ruff_cli to ruff crate, but this test was not updated.
|
||||
if: false
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
@@ -217,11 +220,10 @@ jobs:
|
||||
- name: "Build wheels"
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
manylinux: auto
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
pip install --force-reinstall --find-links dist ${{ env.PACKAGE_NAME }}
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Remove wheels from cache"
|
||||
|
||||
89
.github/workflows/release.yaml
vendored
89
.github/workflows/release.yaml
vendored
@@ -2,8 +2,17 @@ name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
inputs:
|
||||
tag:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
|
||||
type: string
|
||||
sha:
|
||||
description: "Optionally, the full sha of the commit to be released"
|
||||
type: string
|
||||
push:
|
||||
paths:
|
||||
# When we change pyproject.toml, we want to ensure that the maturin builds still work
|
||||
- pyproject.toml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -383,8 +392,39 @@ jobs:
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
release:
|
||||
name: Release
|
||||
validate-tag:
|
||||
name: Validate tag
|
||||
runs-on: ubuntu-latest
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "${{ inputs.tag }}" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "${{ inputs.tag }}" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
- name: Check SHA consistency
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
git_sha=$(git rev-parse HEAD)
|
||||
if [ "${{ inputs.sha }}" != "${git_sha}" ]; then
|
||||
echo "The specified sha does not match the git checkout" >&2
|
||||
echo "${{ inputs.sha }}" >&2
|
||||
echo "${git_sha}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${git_sha}"
|
||||
fi
|
||||
|
||||
upload-release:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
@@ -394,25 +434,56 @@ jobs:
|
||||
- linux-cross
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
- validate-tag
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
# For pypi trusted publishing
|
||||
id-token: write
|
||||
# For GitHub release publishing
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: wheels
|
||||
- name: "Publish to PyPi"
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
|
||||
tag-release:
|
||||
name: Tag release
|
||||
runs-on: ubuntu-latest
|
||||
needs: upload-release
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
permissions:
|
||||
# For git tag
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: git tag
|
||||
run: |
|
||||
git config user.email "hey@astral.sh"
|
||||
git config user.name "Ruff Release CI"
|
||||
git tag -m "v${{ inputs.tag }}" "v${{ inputs.tag }}"
|
||||
# If there is duplicate tag, this will fail. The publish to pypi action will have been a noop (due to skip
|
||||
# existing), so we make a non-destructive exit here
|
||||
git push --tags
|
||||
|
||||
publish-release:
|
||||
name: Publish to GitHub
|
||||
runs-on: ubuntu-latest
|
||||
needs: tag-release
|
||||
# If you don't set an input tag, it's a dry run (no uploads).
|
||||
if: ${{ inputs.tag }}
|
||||
permissions:
|
||||
# For GitHub release publishing
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
@@ -420,7 +491,9 @@ jobs:
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: false
|
||||
files: binaries/*
|
||||
tag_name: v${{ inputs.tag }}
|
||||
|
||||
# After the release has been published, we update downstream repositories
|
||||
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,15 +1,25 @@
|
||||
# Benchmarking cpython (CONTRIBUTING.md)
|
||||
crates/ruff/resources/test/cpython
|
||||
# generate_mkdocs.py
|
||||
mkdocs.yml
|
||||
.overrides
|
||||
# check_ecosystem.py
|
||||
ruff-old
|
||||
github_search*.jsonl
|
||||
# update_schemastore.py
|
||||
schemastore
|
||||
# `maturin develop` and ecosystem_all_check.sh
|
||||
.venv*
|
||||
# Formatter debugging (crates/ruff_python_formatter/README.md)
|
||||
scratch.py
|
||||
# Created by `perf` (CONTRIBUTING.md)
|
||||
perf.data
|
||||
perf.data.old
|
||||
# Created by `flamegraph` (CONTRIBUTING.md)
|
||||
flamegraph.svg
|
||||
# Additional target directories that don't invalidate the main compile cache when changing linker settings
|
||||
# Additional target directories that don't invalidate the main compile cache when changing linker settings,
|
||||
# e.g. `CARGO_TARGET_DIR=target-maturin maturin build --release --strip` or
|
||||
# `CARGO_TARGET_DIR=target-llvm-lines RUSTFLAGS="-Csymbol-mangling-version=v0" cargo llvm-lines -p ruff --lib`
|
||||
/target*
|
||||
|
||||
###
|
||||
|
||||
@@ -4,6 +4,7 @@ exclude: |
|
||||
(?x)^(
|
||||
crates/ruff/resources/.*|
|
||||
crates/ruff/src/rules/.*/snapshots/.*|
|
||||
crates/ruff_cli/resources/.*|
|
||||
crates/ruff_python_formatter/resources/.*|
|
||||
crates/ruff_python_formatter/src/snapshots/.*
|
||||
)$
|
||||
|
||||
153
CONTRIBUTING.md
153
CONTRIBUTING.md
@@ -12,7 +12,7 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
||||
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
|
||||
- [MkDocs](#mkdocs)
|
||||
- [Release Process](#release-process)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Benchmarks](#benchmarking-and-profiling)
|
||||
|
||||
## The Basics
|
||||
|
||||
@@ -271,6 +271,28 @@ them to [PyPI](https://pypi.org/project/ruff/).
|
||||
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
|
||||
### Creating a new release
|
||||
|
||||
1. Update the version with `rg 0.0.269 --files-with-matches | xargs sed -i 's/0.0.269/0.0.270/g'`
|
||||
1. Update `BREAKING_CHANGES.md`
|
||||
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
|
||||
1. Merge the PR
|
||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||
main has your merged PR as last commit
|
||||
1. The release workflow will do the following:
|
||||
1. Build all the assets. If this fails (even though we tested in step 4), we haven’t tagged or
|
||||
uploaded anything, you can restart after pushing a fix
|
||||
1. Upload to pypi
|
||||
1. Create and push the git tag (from pyproject.toml). We create the git tag only here
|
||||
because we can't change it ([#4468](https://github.com/charliermarsh/ruff/issues/4468)), so
|
||||
we want to make sure everything up to and including publishing to pypi worked.
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail without causing fallout, it is possible (if
|
||||
inconvenient) to trigger the downstream jobs manually
|
||||
1. Create release notes in GitHub UI and promote from draft to proper release(<https://github.com/charliermarsh/ruff/releases/new>)
|
||||
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
||||
1. If needed, update ruff-lsp and ruff-vscode
|
||||
|
||||
## Ecosystem CI
|
||||
|
||||
GitHub Actions will run your changes against a number of real-world projects from GitHub and
|
||||
@@ -285,7 +307,15 @@ downloading the [`known-github-tomls.json`](https://github.com/akx/ruff-usage-ag
|
||||
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.
|
||||
|
||||
## Benchmarks
|
||||
## Benchmarking and Profiling
|
||||
|
||||
We have several ways of benchmarking and profiling Ruff:
|
||||
|
||||
- Our main performance benchmark comparing Ruff with other tools on the CPython codebase
|
||||
- Microbenchmarks which the linter or the formatter on individual files. There run on pull requests.
|
||||
- Profiling the linter on either the microbenchmarks or entire projects
|
||||
|
||||
### CPython Benchmark
|
||||
|
||||
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
|
||||
which makes it a good target for benchmarking.
|
||||
@@ -364,9 +394,9 @@ Summary
|
||||
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
|
||||
```
|
||||
|
||||
You can run `poetry install` from `./scripts` to create a working environment for the above. All
|
||||
reported benchmarks were computed using the versions specified by `./scripts/pyproject.toml`
|
||||
on Python 3.11.
|
||||
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
|
||||
above. All reported benchmarks were computed using the versions specified by
|
||||
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
|
||||
|
||||
To benchmark Pylint, remove the following files from the CPython repository:
|
||||
|
||||
@@ -407,3 +437,116 @@ Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
|
||||
Time (mean ± σ): 30.119 s ± 0.195 s [User: 28.638 s, System: 0.390 s]
|
||||
Range (min … max): 29.813 s … 30.356 s 10 runs
|
||||
```
|
||||
|
||||
## Microbenchmarks
|
||||
|
||||
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
|
||||
|
||||
You can run the benchmarks with
|
||||
|
||||
```shell
|
||||
cargo benchmark
|
||||
```
|
||||
|
||||
### Benchmark driven Development
|
||||
|
||||
Ruff uses [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for benchmarks. You can use
|
||||
`--save-baseline=<name>` to store an initial baseline benchmark (e.g. on `main`) and then use
|
||||
`--benchmark=<name>` to compare against that benchmark. Criterion will print a message telling you
|
||||
if the benchmark improved/regressed compared to that baseline.
|
||||
|
||||
```shell
|
||||
# Run once on your "baseline" code
|
||||
cargo benchmark --save-baseline=main
|
||||
|
||||
# Then iterate with
|
||||
cargo benchmark --baseline=main
|
||||
```
|
||||
|
||||
### PR Summary
|
||||
|
||||
You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings.
|
||||
This is useful to illustrate the improvements of a PR.
|
||||
|
||||
```shell
|
||||
# On main
|
||||
cargo benchmark --save-baseline=main
|
||||
|
||||
# After applying your changes
|
||||
cargo benchmark --save-baseline=pr
|
||||
|
||||
critcmp main pr
|
||||
```
|
||||
|
||||
You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comparison.
|
||||
|
||||
```bash
|
||||
cargo install critcmp
|
||||
```
|
||||
|
||||
### Tips
|
||||
|
||||
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
|
||||
to only run the pydantic tests.
|
||||
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
|
||||
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
|
||||
|
||||
## Profiling Projects
|
||||
|
||||
You can either use the microbenchmarks from above or a project directory for benchmarking. There
|
||||
are a lot of profiling tools out there,
|
||||
[The Rust Performance Book](https://nnethercote.github.io/perf-book/profiling.html) lists some
|
||||
examples.
|
||||
|
||||
### Linux
|
||||
|
||||
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
|
||||
|
||||
```shell
|
||||
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
|
||||
```
|
||||
|
||||
You can also use the `ruff_dev` launcher to run `ruff check` multiple times on a repository to
|
||||
gather enough samples for a good flamegraph (change the 999, the sample rate, and the 30, the number
|
||||
of checks, to your liking)
|
||||
|
||||
```shell
|
||||
cargo build --bin ruff_dev --profile=release-debug
|
||||
perf record -g -F 999 target/release-debug/ruff_dev repeat --repeat 30 --exit-zero --no-cache path/to/cpython > /dev/null
|
||||
```
|
||||
|
||||
Then convert the recorded profile
|
||||
|
||||
```shell
|
||||
perf script -F +pid > /tmp/test.perf
|
||||
```
|
||||
|
||||
You can now view the converted file with [firefox profiler](https://profiler.firefox.com/), with a
|
||||
more in-depth guide [here](https://profiler.firefox.com/docs/#/./guide-perf-profiling)
|
||||
|
||||
An alternative is to convert the perf data to `flamegraph.svg` using
|
||||
[flamegraph](https://github.com/flamegraph-rs/flamegraph) (`cargo install flamegraph`):
|
||||
|
||||
```shell
|
||||
flamegraph --perfdata perf.data
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments):
|
||||
|
||||
```shell
|
||||
cargo install cargo-instruments
|
||||
```
|
||||
|
||||
Then run the profiler with
|
||||
|
||||
```shell
|
||||
cargo instruments -t time --bench linter --profile release-debug -p ruff_benchmark -- --profile-time=1
|
||||
```
|
||||
|
||||
- `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc`
|
||||
for profiling the allocations.
|
||||
- You may want to pass an additional filter to run a single test file
|
||||
|
||||
Otherwise, follow the instructions from the linux section.
|
||||
|
||||
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -733,7 +733,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.272"
|
||||
version = "0.0.274"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1793,7 +1793,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.272"
|
||||
version = "0.0.274"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1889,7 +1889,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.272"
|
||||
version = "0.0.274"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2105,7 +2105,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2183,7 +2183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
@@ -2194,7 +2194,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-format"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"bitflags 2.3.1",
|
||||
"itertools",
|
||||
@@ -2206,7 +2206,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-literal"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
@@ -2218,7 +2218,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
@@ -2241,7 +2241,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=8d74eee75031b68d2204219963fae54a3f31a394#8d74eee75031b68d2204219963fae54a3f31a394"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"memchr",
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -24,7 +24,6 @@ ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.28.0" }
|
||||
is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.5.0"
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
@@ -36,11 +35,6 @@ proc-macro2 = { version = "1.0.51" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "8d74eee75031b68d2204219963fae54a3f31a394" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "8d74eee75031b68d2204219963fae54a3f31a394" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "8d74eee75031b68d2204219963fae54a3f31a394", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "8d74eee75031b68d2204219963fae54a3f31a394" }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "8d74eee75031b68d2204219963fae54a3f31a394" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93" }
|
||||
@@ -53,8 +47,22 @@ syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
toml = { version = "0.7.2" }
|
||||
|
||||
# v0.0.1
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
|
||||
# v0.0.3
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" }
|
||||
# v0.0.3
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
|
||||
# v0.0.3
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false, features = ["num-bigint"] }
|
||||
# v0.0.3
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false }
|
||||
# v0.0.3
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
opt-level = 3
|
||||
|
||||
@@ -14,9 +14,9 @@ An extremely fast Python linter, written in Rust.
|
||||
|
||||
<p align="center">
|
||||
<picture align="center">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/212613422-7faaf278-706b-4294-ad92-236ffcab3430.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
|
||||
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/232603514-c95e9b0f-6b31-43de-9a80-9e844173fd6a.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
|
||||
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
@@ -139,7 +139,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.272
|
||||
rev: v0.0.274
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.272"
|
||||
version = "0.0.274"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.272"
|
||||
version = "0.0.274"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -21,6 +21,13 @@ def test_error():
|
||||
assert something and something_else == """error
|
||||
message
|
||||
"""
|
||||
assert (
|
||||
something
|
||||
and something_else
|
||||
== """error
|
||||
message
|
||||
"""
|
||||
)
|
||||
|
||||
# recursive case
|
||||
assert not (a or not (b or c))
|
||||
@@ -31,14 +38,6 @@ def test_error():
|
||||
assert not (something or something_else and something_third), "with message"
|
||||
# detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
assert not (something or something_else and something_third)
|
||||
# detected, but no autofix for parenthesized conditions
|
||||
assert (
|
||||
something
|
||||
and something_else
|
||||
== """error
|
||||
message
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
assert something # OK
|
||||
|
||||
@@ -34,4 +34,4 @@
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
}
|
||||
|
||||
38
crates/ruff/resources/test/fixtures/jupyter/no_trailing_newline.ipynb
vendored
Normal file
38
crates/ruff/resources/test/fixtures/jupyter/no_trailing_newline.ipynb
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4cec6161-f594-446c-ab65-37395bbb3127",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"_ = math.pi"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -63,3 +63,6 @@ class Foo:
|
||||
#: E702:2:4
|
||||
while 1:
|
||||
1;...
|
||||
#: E703:2:1
|
||||
0\
|
||||
;
|
||||
|
||||
@@ -1,53 +1,8 @@
|
||||
[tool.ruff]
|
||||
allowed-confusables = ["−", "ρ", "∗"]
|
||||
line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded_file.py",
|
||||
"migrations",
|
||||
"with_excluded_file/other_excluded_file.py",
|
||||
]
|
||||
external = ["V101"]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
[tool.ruff.flake8-bugbear]
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
|
||||
[tool.ruff.flake8-builtins]
|
||||
builtins-ignorelist = ["id", "dir"]
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
multiline-quotes = "double"
|
||||
docstring-quotes = "double"
|
||||
avoid-escape = true
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
classmethod-decorators = ["pydantic.validator"]
|
||||
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
ban-relative-imports = "parents"
|
||||
|
||||
[tool.ruff.flake8-tidy-imports.banned-api]
|
||||
"cgi".msg = "The cgi module is deprecated."
|
||||
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
|
||||
|
||||
[tool.ruff.flake8-errmsg]
|
||||
max-string-length = 20
|
||||
|
||||
[tool.ruff.flake8-import-conventions.aliases]
|
||||
pandas = "pd"
|
||||
|
||||
[tool.ruff.flake8-import-conventions.extend-aliases]
|
||||
"dask.dataframe" = "dd"
|
||||
|
||||
[tool.ruff.flake8-pytest-style]
|
||||
fixture-parentheses = false
|
||||
parametrize-names-type = "csv"
|
||||
parametrize-values-type = "tuple"
|
||||
parametrize-values-row-type = "list"
|
||||
raises-require-match-for = ["Exception", "TypeError", "KeyError"]
|
||||
raises-extend-require-match-for = ["requests.RequestException"]
|
||||
mark-parentheses = false
|
||||
|
||||
@@ -34,12 +34,3 @@ f"{ascii(bla)}" # OK
|
||||
" intermediary content "
|
||||
f" that flows {repr(obj)} of type {type(obj)}.{additional_message}" # RUF010
|
||||
)
|
||||
|
||||
|
||||
f"{str(bla)}" # RUF010
|
||||
|
||||
f"{str(bla):20}" # RUF010
|
||||
|
||||
f"{bla!s}" # RUF010
|
||||
|
||||
f"{bla!s:20}" # OK
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import typing
|
||||
from typing import ClassVar, Sequence
|
||||
from typing import ClassVar, Sequence, Final
|
||||
|
||||
KNOWINGLY_MUTABLE_DEFAULT = []
|
||||
|
||||
@@ -10,6 +10,7 @@ class A:
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: typing.ClassVar[list[int]] = []
|
||||
final_variable: typing.Final[list[int]] = []
|
||||
|
||||
|
||||
class B:
|
||||
@@ -18,6 +19,7 @@ class B:
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
@@ -31,3 +33,17 @@ class C:
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class D(BaseModel):
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
@@ -18,19 +18,19 @@ def f(arg: object = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: int = None): # RUF011
|
||||
def f(arg: int = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: str = None): # RUF011
|
||||
def f(arg: str = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.List[str] = None): # RUF011
|
||||
def f(arg: typing.List[str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Tuple[str] = None): # RUF011
|
||||
def f(arg: Tuple[str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
@@ -64,15 +64,15 @@ def f(arg: Union[int, str, Any] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union = None): # RUF011
|
||||
def f(arg: Union = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[int, str] = None): # RUF011
|
||||
def f(arg: Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Union[int, str] = None): # RUF011
|
||||
def f(arg: typing.Union[int, str] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
@@ -91,11 +91,11 @@ def f(arg: int | float | str | None = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: int | float = None): # RUF011
|
||||
def f(arg: int | float = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: int | float | str | bytes = None): # RUF011
|
||||
def f(arg: int | float | str | bytes = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
@@ -110,11 +110,11 @@ def f(arg: Literal[1, 2, None, 3] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Literal[1, "foo"] = None): # RUF011
|
||||
def f(arg: Literal[1, "foo"] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: typing.Literal[1, "foo", True] = None): # RUF011
|
||||
def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
@@ -133,11 +133,11 @@ def f(arg: Annotated[Any, ...] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Annotated[int, ...] = None): # RUF011
|
||||
def f(arg: Annotated[int, ...] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF011
|
||||
def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
@@ -153,9 +153,9 @@ def f(
|
||||
|
||||
|
||||
def f(
|
||||
arg1: int = None, # RUF011
|
||||
arg2: Union[int, float] = None, # RUF011
|
||||
arg3: Literal[1, 2, 3] = None, # RUF011
|
||||
arg1: int = None, # RUF013
|
||||
arg2: Union[int, float] = None, # RUF013
|
||||
arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -183,5 +183,41 @@ def f(arg: Union[Annotated[int, ...], Annotated[Optional[float], ...]] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
|
||||
def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
# Quoted
|
||||
|
||||
|
||||
def f(arg: "int" = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: "str" = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: "st" "r" = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: "Optional[int]" = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union["int", "str"] = None): # RUF013
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union["int", "None"] = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: Union["No" "ne", "int"] = None):
|
||||
pass
|
||||
|
||||
|
||||
# Avoid flagging when there's a parse error in the forward reference
|
||||
def f(arg: Union["<>", "int"] = None):
|
||||
pass
|
||||
|
||||
@@ -60,8 +60,8 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
) -> Result<Edit> {
|
||||
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
|
||||
None => Ok(delete_stmt(stmt, parent, locator, indexer)),
|
||||
|
||||
@@ -366,9 +366,7 @@ where
|
||||
}
|
||||
if self.enabled(Rule::AmbiguousFunctionName) {
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_function_name(name, || {
|
||||
stmt.identifier(self.locator)
|
||||
})
|
||||
pycodestyle::rules::ambiguous_function_name(name, || stmt.identifier())
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -383,7 +381,6 @@ where
|
||||
decorator_list,
|
||||
&self.settings.pep8_naming.ignore_names,
|
||||
&self.semantic,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -452,7 +449,6 @@ where
|
||||
stmt,
|
||||
name,
|
||||
&self.settings.pep8_naming.ignore_names,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -503,7 +499,6 @@ where
|
||||
name,
|
||||
body,
|
||||
self.settings.mccabe.max_complexity,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -523,7 +518,6 @@ where
|
||||
stmt,
|
||||
body,
|
||||
self.settings.pylint.max_returns,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -533,7 +527,6 @@ where
|
||||
stmt,
|
||||
body,
|
||||
self.settings.pylint.max_branches,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -543,7 +536,6 @@ where
|
||||
stmt,
|
||||
body,
|
||||
self.settings.pylint.max_statements,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -605,7 +597,6 @@ where
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
self.locator,
|
||||
);
|
||||
}
|
||||
if self.enabled(Rule::FStringDocstring) {
|
||||
@@ -693,9 +684,9 @@ where
|
||||
pyupgrade::rules::unnecessary_class_parentheses(self, class_def, stmt);
|
||||
}
|
||||
if self.enabled(Rule::AmbiguousClassName) {
|
||||
if let Some(diagnostic) = pycodestyle::rules::ambiguous_class_name(name, || {
|
||||
stmt.identifier(self.locator)
|
||||
}) {
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_class_name(name, || stmt.identifier())
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -704,7 +695,6 @@ where
|
||||
stmt,
|
||||
name,
|
||||
&self.settings.pep8_naming.ignore_names,
|
||||
self.locator,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -714,7 +704,6 @@ where
|
||||
stmt,
|
||||
bases,
|
||||
name,
|
||||
self.locator,
|
||||
&self.settings.pep8_naming.ignore_names,
|
||||
) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
@@ -813,7 +802,7 @@ where
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(self.locator),
|
||||
alias.identifier(),
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
|
||||
BindingFlags::EXTERNAL,
|
||||
);
|
||||
@@ -825,7 +814,7 @@ where
|
||||
if alias
|
||||
.asname
|
||||
.as_ref()
|
||||
.map_or(false, |asname| asname == &alias.name)
|
||||
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
|
||||
{
|
||||
flags |= BindingFlags::EXPLICIT_EXPORT;
|
||||
}
|
||||
@@ -834,7 +823,7 @@ where
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(self.locator),
|
||||
alias.identifier(),
|
||||
BindingKind::Import(Import { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
@@ -1052,7 +1041,7 @@ where
|
||||
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(self.locator),
|
||||
alias.identifier(),
|
||||
BindingKind::FutureImport,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
@@ -1110,7 +1099,7 @@ where
|
||||
if alias
|
||||
.asname
|
||||
.as_ref()
|
||||
.map_or(false, |asname| asname == &alias.name)
|
||||
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
|
||||
{
|
||||
flags |= BindingFlags::EXPLICIT_EXPORT;
|
||||
}
|
||||
@@ -1123,7 +1112,7 @@ where
|
||||
helpers::format_import_from_member(level, module, &alias.name);
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(self.locator),
|
||||
alias.identifier(),
|
||||
BindingKind::FromImport(FromImport { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
@@ -1804,7 +1793,7 @@ where
|
||||
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(self.locator),
|
||||
stmt.identifier(),
|
||||
BindingKind::FunctionDefinition,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
@@ -2029,7 +2018,7 @@ where
|
||||
self.semantic.pop_definition();
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(self.locator),
|
||||
stmt.identifier(),
|
||||
BindingKind::ClassDefinition,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
@@ -3846,7 +3835,7 @@ where
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
let range = except_handler.try_identifier(self.locator).unwrap();
|
||||
let range = except_handler.try_identifier().unwrap();
|
||||
|
||||
if self.enabled(Rule::AmbiguousVariableName) {
|
||||
if let Some(diagnostic) =
|
||||
@@ -3863,6 +3852,9 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
// Store the existing binding, if any.
|
||||
let existing_id = self.semantic.lookup(name);
|
||||
|
||||
// Add the bound exception name to the scope.
|
||||
let binding_id = self.add_binding(
|
||||
name,
|
||||
@@ -3873,14 +3865,6 @@ where
|
||||
|
||||
walk_except_handler(self, except_handler);
|
||||
|
||||
// Remove it from the scope immediately after.
|
||||
self.add_binding(
|
||||
name,
|
||||
range,
|
||||
BindingKind::UnboundException,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
|
||||
// If the exception name wasn't used in the scope, emit a diagnostic.
|
||||
if !self.semantic.is_used(binding_id) {
|
||||
if self.enabled(Rule::UnusedVariable) {
|
||||
@@ -3900,6 +3884,13 @@ where
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
name,
|
||||
range,
|
||||
BindingKind::UnboundException(existing_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
None => walk_except_handler(self, except_handler),
|
||||
}
|
||||
@@ -3961,7 +3952,7 @@ where
|
||||
// upstream.
|
||||
self.add_binding(
|
||||
&arg.arg,
|
||||
arg.identifier(self.locator),
|
||||
arg.identifier(),
|
||||
BindingKind::Argument,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
@@ -4001,7 +3992,7 @@ where
|
||||
{
|
||||
self.add_binding(
|
||||
name,
|
||||
pattern.try_identifier(self.locator).unwrap(),
|
||||
pattern.try_identifier().unwrap(),
|
||||
BindingKind::Assignment,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
@@ -4247,7 +4238,7 @@ impl<'a> Checker<'a> {
|
||||
let shadowed = &self.semantic.bindings[shadowed_id];
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Builtin | BindingKind::Deletion | BindingKind::UnboundException,
|
||||
BindingKind::Builtin | BindingKind::Deletion | BindingKind::UnboundException(_),
|
||||
) {
|
||||
let references = shadowed.references.clone();
|
||||
let is_global = shadowed.is_global();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//! Lint rules based on checking physical lines.
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_text_size::TextSize;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
use ruff_python_whitespace::UniversalNewlines;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::copyright::rules::missing_copyright_notice;
|
||||
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
||||
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
|
||||
use crate::rules::flake8_executable::rules::{
|
||||
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
|
||||
@@ -146,7 +146,7 @@ pub(crate) fn check_physical_lines(
|
||||
}
|
||||
|
||||
if enforce_trailing_whitespace || enforce_blank_line_contains_whitespace {
|
||||
if let Some(diagnostic) = trailing_whitespace(&line, settings) {
|
||||
if let Some(diagnostic) = trailing_whitespace(&line, locator, indexer, settings) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -185,9 +185,10 @@ pub(crate) fn check_physical_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::lexer::lex;
|
||||
use rustpython_parser::Mode;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ pub(crate) fn check_tokens(
|
||||
// ERA001
|
||||
if enforce_commented_out_code {
|
||||
diagnostics.extend(eradicate::rules::commented_out_code(
|
||||
indexer, locator, settings,
|
||||
locator, indexer, settings,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ pub(crate) fn check_tokens(
|
||||
// E701, E702, E703
|
||||
if enforce_compound_statements {
|
||||
diagnostics.extend(
|
||||
pycodestyle::rules::compound_statements(tokens, settings)
|
||||
pycodestyle::rules::compound_statements(tokens, locator, indexer, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
@@ -187,7 +187,7 @@ pub(crate) fn check_tokens(
|
||||
|
||||
// PYI033
|
||||
if enforce_type_comment_in_stub && is_stub {
|
||||
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(indexer, locator));
|
||||
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(locator, indexer));
|
||||
}
|
||||
|
||||
// TD001, TD002, TD003, TD004, TD005, TD006, TD007
|
||||
@@ -204,7 +204,7 @@ pub(crate) fn check_tokens(
|
||||
.collect();
|
||||
|
||||
diagnostics.extend(
|
||||
flake8_todos::rules::todos(&todo_comments, indexer, locator, settings)
|
||||
flake8_todos::rules::todos(&todo_comments, locator, indexer, settings)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
|
||||
@@ -157,7 +157,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// pylint
|
||||
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C1901") => (RuleGroup::Unspecified, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
(Pylint, "C0208") => (RuleGroup::Unspecified, rules::pylint::rules::IterationOverSet),
|
||||
(Pylint, "E0100") => (RuleGroup::Unspecified, rules::pylint::rules::YieldInInit),
|
||||
@@ -375,7 +375,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
|
||||
|
||||
// copyright
|
||||
(Copyright, "001") => (RuleGroup::Nursery, rules::copyright::rules::MissingCopyrightNotice),
|
||||
(Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
|
||||
|
||||
// pyupgrade
|
||||
(Pyupgrade, "001") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessMetaclassType),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Write};
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -34,9 +34,9 @@ pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
})?;
|
||||
let code = notebook.content().to_string();
|
||||
notebook.update_cell_content(&code);
|
||||
let mut buffer = BufWriter::new(Vec::new());
|
||||
notebook.write_inner(&mut buffer)?;
|
||||
Ok(String::from_utf8(buffer.into_inner()?)?)
|
||||
let mut writer = Vec::new();
|
||||
notebook.write_inner(&mut writer)?;
|
||||
Ok(String::from_utf8(writer)?)
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Path`] appears to be that of a jupyter notebook file (`.ipynb`).
|
||||
@@ -113,13 +113,17 @@ pub struct Notebook {
|
||||
cell_offsets: Vec<TextSize>,
|
||||
/// The cell index of all valid code cells in the notebook.
|
||||
valid_code_cells: Vec<u32>,
|
||||
/// Flag to indicate if the JSON string of the notebook has a trailing newline.
|
||||
trailing_newline: bool,
|
||||
}
|
||||
|
||||
impl Notebook {
|
||||
/// Read the Jupyter Notebook from the given [`Path`].
|
||||
///
|
||||
/// See also the black implementation
|
||||
/// <https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#L1017-L1046>
|
||||
pub fn read(path: &Path) -> Result<Self, Box<Diagnostic>> {
|
||||
let reader = BufReader::new(File::open(path).map_err(|err| {
|
||||
let mut reader = BufReader::new(File::open(path).map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
@@ -127,6 +131,18 @@ impl Notebook {
|
||||
TextRange::default(),
|
||||
)
|
||||
})?);
|
||||
let trailing_newline = reader.seek(SeekFrom::End(-1)).is_ok_and(|_| {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf).is_ok_and(|_| buf[0] == b'\n')
|
||||
});
|
||||
reader.rewind().map_err(|err| {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: format!("{err}"),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
})?;
|
||||
let raw_notebook: RawNotebook = match serde_json::from_reader(reader) {
|
||||
Ok(notebook) => notebook,
|
||||
Err(err) => {
|
||||
@@ -240,6 +256,7 @@ impl Notebook {
|
||||
content: contents.join("\n") + "\n",
|
||||
cell_offsets,
|
||||
valid_code_cells,
|
||||
trailing_newline,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,8 +428,11 @@ impl Notebook {
|
||||
fn write_inner(&self, writer: &mut impl Write) -> anyhow::Result<()> {
|
||||
// https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#LL1041
|
||||
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
||||
let mut ser = serde_json::Serializer::with_formatter(writer, formatter);
|
||||
SortAlphabetically(&self.raw).serialize(&mut ser)?;
|
||||
let mut serializer = serde_json::Serializer::with_formatter(writer, formatter);
|
||||
SortAlphabetically(&self.raw).serialize(&mut serializer)?;
|
||||
if self.trailing_newline {
|
||||
writeln!(serializer.into_inner())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -426,7 +446,6 @@ impl Notebook {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::io::BufWriter;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -438,7 +457,7 @@ mod test {
|
||||
use crate::jupyter::schema::Cell;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::registry::Rule;
|
||||
use crate::test::{test_notebook_path, test_resource_path};
|
||||
use crate::test::{read_jupyter_notebook, test_notebook_path, test_resource_path};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Read a Jupyter cell from the `resources/test/fixtures/jupyter/cell` directory.
|
||||
@@ -450,15 +469,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_valid() {
|
||||
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
|
||||
assert!(Notebook::read(path).is_ok());
|
||||
assert!(read_jupyter_notebook(Path::new("valid.ipynb")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r() {
|
||||
// We can load this, it will be filtered out later
|
||||
let path = Path::new("resources/test/fixtures/jupyter/R.ipynb");
|
||||
assert!(Notebook::read(path).is_ok());
|
||||
assert!(read_jupyter_notebook(Path::new("R.ipynb")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -506,9 +523,8 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_notebook() {
|
||||
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
|
||||
let notebook = Notebook::read(path).unwrap();
|
||||
fn test_concat_notebook() -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(Path::new("valid.ipynb"))?;
|
||||
assert_eq!(
|
||||
notebook.content,
|
||||
r#"def unused_variable():
|
||||
@@ -546,6 +562,7 @@ print("after empty cells")
|
||||
198.into()
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -568,12 +585,26 @@ print("after empty cells")
|
||||
Path::new("after_fix.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
let mut writer = BufWriter::new(Vec::new());
|
||||
let mut writer = Vec::new();
|
||||
source_kind.expect_jupyter().write_inner(&mut writer)?;
|
||||
let actual = String::from_utf8(writer.into_inner()?)?;
|
||||
let actual = String::from_utf8(writer)?;
|
||||
let expected =
|
||||
std::fs::read_to_string(test_resource_path("fixtures/jupyter/after_fix.ipynb"))?;
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("before_fix.ipynb"), true; "trailing_newline")]
|
||||
#[test_case(Path::new("no_trailing_newline.ipynb"), false; "no_trailing_newline")]
|
||||
fn test_trailing_newline(path: &Path, trailing_newline: bool) -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(path)?;
|
||||
assert_eq!(notebook.trailing_newline, trailing_newline);
|
||||
|
||||
let mut writer = Vec::new();
|
||||
notebook.write_inner(&mut writer)?;
|
||||
let string = String::from_utf8(writer)?;
|
||||
assert_eq!(string.ends_with('\n'), trailing_newline);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ fn detect_package_root_with_cache<'a>(
|
||||
current
|
||||
}
|
||||
|
||||
/// Return a mapping from Python file to its package root.
|
||||
/// Return a mapping from Python package to its package root.
|
||||
pub fn detect_package_roots<'a>(
|
||||
files: &[&'a Path],
|
||||
resolver: &'a Resolver,
|
||||
|
||||
@@ -251,7 +251,7 @@ impl Renamer {
|
||||
| BindingKind::ClassDefinition
|
||||
| BindingKind::FunctionDefinition
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::UnboundException => {
|
||||
| BindingKind::UnboundException(_) => {
|
||||
Some(Edit::range_replacement(target.to_string(), binding.range))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -48,8 +48,8 @@ fn is_standalone_comment(line: &str) -> bool {
|
||||
|
||||
/// ERA001
|
||||
pub(crate) fn commented_out_code(
|
||||
indexer: &Indexer,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &Settings,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
|
||||
@@ -653,7 +653,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method
|
||||
@@ -664,7 +664,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
@@ -676,7 +676,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
@@ -693,7 +693,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(return_type) = simple_magic_return_type(name) {
|
||||
@@ -713,7 +713,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -723,7 +723,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,40 @@ use ruff_python_semantic::SemanticModel;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for abstract classes without abstract methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Abstract base classes are used to define interfaces. If they have no abstract
|
||||
/// methods, they are not useful.
|
||||
///
|
||||
/// If the class is not meant to be used as an interface, it should not be an
|
||||
/// abstract base class. Remove the `ABC` base class from the class definition,
|
||||
/// or add an abstract method to the class.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from abc import ABC
|
||||
///
|
||||
///
|
||||
/// class Foo(ABC):
|
||||
/// def method(self):
|
||||
/// bar()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from abc import ABC, abstractmethod
|
||||
///
|
||||
///
|
||||
/// class Foo(ABC):
|
||||
/// @abstractmethod
|
||||
/// def method(self):
|
||||
/// bar()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `abc`](https://docs.python.org/3/library/abc.html)
|
||||
#[violation]
|
||||
pub struct AbstractBaseClassWithoutAbstractMethod {
|
||||
name: String,
|
||||
@@ -21,6 +55,40 @@ impl Violation for AbstractBaseClassWithoutAbstractMethod {
|
||||
format!("`{name}` is an abstract base class, but it has no abstract methods")
|
||||
}
|
||||
}
|
||||
/// ## What it does
|
||||
/// Checks for empty methods in abstract base classes without an abstract
|
||||
/// decorator.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Empty methods in abstract base classes without an abstract decorator are
|
||||
/// indicative of unfinished code or a mistake.
|
||||
///
|
||||
/// Instead, add an abstract method decorated to indicate that it is abstract,
|
||||
/// or implement the method.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from abc import ABC
|
||||
///
|
||||
///
|
||||
/// class Foo(ABC):
|
||||
/// def method(self):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from abc import ABC, abstractmethod
|
||||
///
|
||||
///
|
||||
/// class Foo(ABC):
|
||||
/// @abstractmethod
|
||||
/// def method(self):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: abc](https://docs.python.org/3/library/abc.html)
|
||||
#[violation]
|
||||
pub struct EmptyMethodWithoutAbstractDecorator {
|
||||
name: String,
|
||||
@@ -134,7 +202,7 @@ pub(crate) fn abstract_base_class(
|
||||
AbstractBaseClassWithoutAbstractMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,28 @@ use ruff_python_ast::helpers::is_const_false;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `assert False`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python removes `assert` statements when running in optimized mode
|
||||
/// (`python -O`), making `assert False` an unreliable means of
|
||||
/// raising an `AssertionError`.
|
||||
///
|
||||
/// Instead, raise an `AssertionError` directly.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// assert False
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// raise AssertionError
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
|
||||
#[violation]
|
||||
pub struct AssertFalse;
|
||||
|
||||
|
||||
@@ -5,6 +5,39 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for assignments to `os.environ`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, `os.environ` is a mapping that represents the environment of the
|
||||
/// current process.
|
||||
///
|
||||
/// However, reassigning to `os.environ` does not clear the environment. Instead,
|
||||
/// it merely updates the `os.environ` for the current process. This can lead to
|
||||
/// unexpected behavior, especially when running the program in a subprocess.
|
||||
///
|
||||
/// Instead, use `os.environ.clear()` to clear the environment, or use the
|
||||
/// `env` argument of `subprocess.Popen` to pass a custom environment to
|
||||
/// a subprocess.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.environ = {"foo": "bar"}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.environ.clear()
|
||||
/// os.environ["foo"] = "bar"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `os.environ`](https://docs.python.org/3/library/os.html#os.environ)
|
||||
/// - [Python documentation: `subprocess.Popen`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
#[violation]
|
||||
pub struct AssignmentToOsEnviron;
|
||||
|
||||
|
||||
@@ -6,6 +6,57 @@ use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `functools.lru_cache` and `functools.cache`
|
||||
/// decorators on methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using the `functools.lru_cache` and `functools.cache` decorators on methods
|
||||
/// can lead to memory leaks, as the global cache will retain a reference to
|
||||
/// the instance, preventing it from being garbage collected.
|
||||
///
|
||||
/// Instead, refactor the method to depend only on its arguments and not on the
|
||||
/// instance of the class, or use the `@lru_cache` decorator on a function
|
||||
/// outside of the class.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from functools import lru_cache
|
||||
///
|
||||
///
|
||||
/// def square(x: int) -> int:
|
||||
/// return x * x
|
||||
///
|
||||
///
|
||||
/// class Number:
|
||||
/// value: int
|
||||
///
|
||||
/// @lru_cache
|
||||
/// def squared(self):
|
||||
/// return square(self.value)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from functools import lru_cache
|
||||
///
|
||||
///
|
||||
/// @lru_cache
|
||||
/// def square(x: int) -> int:
|
||||
/// return x * x
|
||||
///
|
||||
///
|
||||
/// class Number:
|
||||
/// value: int
|
||||
///
|
||||
/// def squared(self):
|
||||
/// return square(self.value)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)
|
||||
/// - [Python documentation: `functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache)
|
||||
/// - [don't lru_cache methods!](https://www.youtube.com/watch?v=sVjtp6tGo0g)
|
||||
#[violation]
|
||||
pub struct CachedInstanceMethod;
|
||||
|
||||
|
||||
@@ -12,6 +12,33 @@ use ruff_python_ast::call_path::CallPath;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `try-except` blocks with duplicate exception handlers.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Duplicate exception handlers are redundant, as the first handler will catch
|
||||
/// the exception, making the second handler unreachable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except ValueError:
|
||||
/// ...
|
||||
/// except ValueError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except ValueError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
#[violation]
|
||||
pub struct DuplicateTryBlockException {
|
||||
name: String,
|
||||
@@ -24,6 +51,36 @@ impl Violation for DuplicateTryBlockException {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for exception handlers that catch duplicate exceptions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Including the same exception multiple times in the same handler is redundant,
|
||||
/// as the first exception will catch the exception, making the second exception
|
||||
/// unreachable. The same applies to exception hierarchies, as a handler for a
|
||||
/// parent exception (like `Exception`) will also catch child exceptions (like
|
||||
/// `ValueError`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except (Exception, ValueError): # `Exception` includes `ValueError`.
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except Exception:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
/// - [Python documentation: Exception hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
|
||||
#[violation]
|
||||
pub struct DuplicateHandlerException {
|
||||
pub names: Vec<String>,
|
||||
|
||||
@@ -6,6 +6,32 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for exception handlers that catch an empty tuple.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// An exception handler that catches an empty tuple will not catch anything,
|
||||
/// and is indicative of a mistake. Instead, add exceptions to the `except`
|
||||
/// clause.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except ():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except ZeroDivisionError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
#[violation]
|
||||
pub struct ExceptWithEmptyTuple;
|
||||
|
||||
|
||||
@@ -7,6 +7,32 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for exception handlers that catch non-exception classes.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Catching classes that do not inherit from `BaseException` will raise a
|
||||
/// `TypeError`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except 1:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except ZeroDivisionError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
/// - [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions)
|
||||
#[violation]
|
||||
pub struct ExceptWithNonExceptionClasses;
|
||||
|
||||
|
||||
@@ -6,6 +6,30 @@ use ruff_python_ast::identifier::Identifier;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for docstrings that are written via f-strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python will interpret the f-string as a joined string, rather than as a
|
||||
/// docstring. As such, the "docstring" will not be accessible via the
|
||||
/// `__doc__` attribute, nor will it be picked up by any automated
|
||||
/// documentation tooling.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// f"""Not a docstring."""
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// """A docstring."""
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 257](https://peps.python.org/pep-0257/)
|
||||
/// - [Python documentation: Formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
|
||||
#[violation]
|
||||
pub struct FStringDocstring;
|
||||
|
||||
@@ -29,8 +53,7 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||
let Expr::JoinedStr ( _) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
FStringDocstring,
|
||||
stmt.identifier(checker.locator),
|
||||
));
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FStringDocstring, stmt.identifier()));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,39 @@ use ruff_python_ast::visitor::Visitor;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for function definitions that use a loop variable.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The loop variable is not bound in the function definition, so it will always
|
||||
/// have the value it had in the last iteration when the function is called.
|
||||
///
|
||||
/// Instead, consider using a default argument to bind the loop variable at
|
||||
/// function definition time. Or, use `functools.partial`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// adders = [lambda x: x + i for i in range(3)]
|
||||
/// values = [adder(1) for adder in adders] # [3, 3, 3]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// adders = [lambda x, i=i: x + i for i in range(3)]
|
||||
/// values = [adder(1) for adder in adders] # [1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
/// Or:
|
||||
/// ```python
|
||||
/// from functools import partial
|
||||
///
|
||||
/// adders = [partial(lambda x, i: x + i, i) for i in range(3)]
|
||||
/// values = [adder(1) for adder in adders] # [1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [The Hitchhiker's Guide to Python: Late Binding Closures](https://docs.python-guide.org/writing/gotchas/#late-binding-closures)
|
||||
/// - [Python documentation: functools.partial](https://docs.python.org/3/library/functools.html#functools.partial)
|
||||
#[violation]
|
||||
pub struct FunctionUsesLoopVariable {
|
||||
name: String,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Identifier, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -8,6 +8,29 @@ use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `getattr` that take a constant attribute value as an
|
||||
/// argument (e.g., `getattr(obj, "foo")`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `getattr` is used to access attributes dynamically. If the attribute is
|
||||
/// defined as a constant, it is no safer than a typical property access. When
|
||||
/// possible, prefer property access over `getattr` calls, as the former is
|
||||
/// more concise and idiomatic.
|
||||
///
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// getattr(obj, "foo")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// obj.foo
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `getattr`](https://docs.python.org/3/library/functions.html#getattr)
|
||||
#[violation]
|
||||
pub struct GetAttrWithConstant;
|
||||
|
||||
@@ -27,7 +50,7 @@ impl AlwaysAutofixableViolation for GetAttrWithConstant {
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
ast::ExprAttribute {
|
||||
value: Box::new(value.clone()),
|
||||
attr: attr.into(),
|
||||
attr: Identifier::new(attr.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
}
|
||||
|
||||
@@ -5,6 +5,40 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `break`, `continue`, and `return` statements in `finally`
|
||||
/// blocks.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The use of `break`, `continue`, and `return` statements in `finally` blocks
|
||||
/// can cause exceptions to be silenced.
|
||||
///
|
||||
/// `finally` blocks execute regardless of whether an exception is raised. If a
|
||||
/// `break`, `continue`, or `return` statement is reached in a `finally` block,
|
||||
/// any exception raised in the `try` or `except` blocks will be silenced.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def speed(distance, time):
|
||||
/// try:
|
||||
/// return distance / time
|
||||
/// except ZeroDivisionError:
|
||||
/// raise ValueError("Time cannot be zero")
|
||||
/// finally:
|
||||
/// return 299792458 # `ValueError` is silenced
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def speed(distance, time):
|
||||
/// try:
|
||||
/// return distance / time
|
||||
/// except ZeroDivisionError:
|
||||
/// raise ValueError("Time cannot be zero")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The `try` statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)
|
||||
#[violation]
|
||||
pub struct JumpStatementInFinally {
|
||||
name: String,
|
||||
|
||||
@@ -8,6 +8,33 @@ use ruff_python_ast::visitor::Visitor;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for loop control variables that override the loop iterable.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Loop control variables should not override the loop iterable, as this can
|
||||
/// lead to confusing behavior.
|
||||
///
|
||||
/// Instead, use a distinct variable name for any loop control variables.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// items = [1, 2, 3]
|
||||
///
|
||||
/// for items in items:
|
||||
/// print(items)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// items = [1, 2, 3]
|
||||
///
|
||||
/// for item in items:
|
||||
/// print(item)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The `for` statement](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement)
|
||||
#[violation]
|
||||
pub struct LoopVariableOverridesIterator {
|
||||
name: String,
|
||||
|
||||
@@ -6,6 +6,46 @@ use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of mutable objects as function argument defaults.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Function defaults are evaluated once, when the function is defined.
|
||||
///
|
||||
/// The same mutable object is then shared across all calls to the function.
|
||||
/// If the object is modified, those modifications will persist across calls,
|
||||
/// which can lead to unexpected behavior.
|
||||
///
|
||||
/// Instead, prefer to use immutable data structures, or take `None` as a
|
||||
/// default, and initialize a new mutable object inside the function body
|
||||
/// for each call.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def add_to_list(item, some_list=[]):
|
||||
/// some_list.append(item)
|
||||
/// return some_list
|
||||
///
|
||||
///
|
||||
/// l1 = add_to_list(0) # [0]
|
||||
/// l2 = add_to_list(1) # [0, 1]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def add_to_list(item, some_list=None):
|
||||
/// if some_list is None:
|
||||
/// some_list = []
|
||||
/// some_list.append(item)
|
||||
/// return some_list
|
||||
///
|
||||
///
|
||||
/// l1 = add_to_list(0) # [0]
|
||||
/// l2 = add_to_list(1) # [1]
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Default Argument Values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values)
|
||||
#[violation]
|
||||
pub struct MutableArgumentDefault;
|
||||
|
||||
|
||||
@@ -5,6 +5,26 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `raise` statements that raise a literal value.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `raise` must be followed by an exception instance or an exception class,
|
||||
/// and exceptions must be instances of `BaseException` or a subclass thereof.
|
||||
/// Raising a literal will raise a `TypeError` at runtime.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// raise "foo"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// raise Exception("foo")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)
|
||||
#[violation]
|
||||
pub struct RaiseLiteral;
|
||||
|
||||
|
||||
@@ -8,6 +8,44 @@ use ruff_python_stdlib::str::is_cased_lowercase;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `raise` statements in exception handlers that lack a `from`
|
||||
/// clause.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, `raise` can be used with or without an exception from which the
|
||||
/// current exception is derived. This is known as exception chaining. When
|
||||
/// printing the stack trace, chained exceptions are displayed in such a way
|
||||
/// so as make it easier to trace the exception back to its root cause.
|
||||
///
|
||||
/// When raising an exception from within an `except` clause, always include a
|
||||
/// `from` clause to facilitate exception chaining. If the exception is not
|
||||
/// chained, it will be difficult to trace the exception back to its root cause.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except FileNotFoundError:
|
||||
/// if ...:
|
||||
/// raise RuntimeError("...")
|
||||
/// else:
|
||||
/// raise UserWarning("...")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except FileNotFoundError as exc:
|
||||
/// if ...:
|
||||
/// raise RuntimeError("...") from None
|
||||
/// else:
|
||||
/// raise UserWarning("...") from exc
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)
|
||||
#[violation]
|
||||
pub struct RaiseWithoutFromInsideExcept;
|
||||
|
||||
|
||||
@@ -6,6 +6,32 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for single-element tuples in exception handlers (e.g.,
|
||||
/// `except (ValueError,):`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A tuple with a single element can be more concisely and idiomatically
|
||||
/// expressed as a single value.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except (ValueError,):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// ...
|
||||
/// except ValueError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
#[violation]
|
||||
pub struct RedundantTupleInExceptionHandler {
|
||||
name: String,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Identifier, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -9,6 +9,28 @@ use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `setattr` that take a constant attribute value as an
|
||||
/// argument (e.g., `setattr(obj, "foo", 42)`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `setattr` is used to set attributes dynamically. If the attribute is
|
||||
/// defined as a constant, it is no safer than a typical property access. When
|
||||
/// possible, prefer property access over `setattr` calls, as the former is
|
||||
/// more concise and idiomatic.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// setattr(obj, "foo", 42)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// obj.foo = 42
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `setattr`](https://docs.python.org/3/library/functions.html#setattr)
|
||||
#[violation]
|
||||
pub struct SetAttrWithConstant;
|
||||
|
||||
@@ -30,7 +52,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
|
||||
let stmt = Stmt::Assign(ast::StmtAssign {
|
||||
targets: vec![Expr::Attribute(ast::ExprAttribute {
|
||||
value: Box::new(obj.clone()),
|
||||
attr: name.into(),
|
||||
attr: Identifier::new(name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
})],
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
//! Checks for `f(x=0, *(1, 2))`.
|
||||
//!
|
||||
//! ## Why is this bad?
|
||||
//!
|
||||
//! Star-arg unpacking after a keyword argument is strongly discouraged. It only
|
||||
//! works when the keyword parameter is declared after all parameters supplied
|
||||
//! by the unpacked sequence, and this change of ordering can surprise and
|
||||
//! mislead readers.
|
||||
|
||||
use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -14,6 +5,45 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for function calls that use star-argument unpacking after providing a
|
||||
/// keyword argument
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, you can use star-argument unpacking to pass a list or tuple of
|
||||
/// arguments to a function.
|
||||
///
|
||||
/// Providing a star-argument after a keyword argument can lead to confusing
|
||||
/// behavior, and is only supported for backwards compatibility.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(x, y, z):
|
||||
/// return x, y, z
|
||||
///
|
||||
///
|
||||
/// foo(1, 2, 3) # (1, 2, 3)
|
||||
/// foo(1, *[2, 3]) # (1, 2, 3)
|
||||
/// # foo(x=1, *[2, 3]) # TypeError
|
||||
/// # foo(y=2, *[1, 3]) # TypeError
|
||||
/// foo(z=3, *[1, 2]) # (1, 2, 3) # No error, but confusing!
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def foo(x, y, z):
|
||||
/// return x, y, z
|
||||
///
|
||||
///
|
||||
/// foo(1, 2, 3) # (1, 2, 3)
|
||||
/// foo(x=1, y=2, z=3) # (1, 2, 3)
|
||||
/// foo(*[1, 2, 3]) # (1, 2, 3)
|
||||
/// foo(*[1, 2], 3) # (1, 2, 3)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [Disallow iterable argument unpacking after a keyword argument?](https://github.com/python/cpython/issues/82741)
|
||||
#[violation]
|
||||
pub struct StarArgUnpackingAfterKeywordArg;
|
||||
|
||||
|
||||
@@ -6,6 +6,32 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of multi-character strings in `.strip()`, `.lstrip()`, and
|
||||
/// `.rstrip()` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// All characters in the call to `.strip()`, `.lstrip()`, or `.rstrip()` are
|
||||
/// removed from the leading and trailing ends of the string. If the string
|
||||
/// contains multiple characters, the reader may be misled into thinking that
|
||||
/// a prefix or suffix is being removed, rather than a set of characters.
|
||||
///
|
||||
/// In Python 3.9 and later, you can use `str#removeprefix` and
|
||||
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// respectively, which should be preferred when possible.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// "abcba".strip("ab") # "c"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// "abcba".removeprefix("ab").removesuffix("ba") # "c"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `str.strip`](https://docs.python.org/3/library/stdtypes.html#str.strip)
|
||||
#[violation]
|
||||
pub struct StripWithMultiCharacters;
|
||||
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
//! Checks for `++n`.
|
||||
//!
|
||||
//! ## Why is this bad?
|
||||
//!
|
||||
//! Python does not support the unary prefix increment. Writing `++n` is
|
||||
//! equivalent to `+(+(n))`, which equals `n`.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```python
|
||||
//! ++n;
|
||||
//! ```
|
||||
//!
|
||||
//! Use instead:
|
||||
//!
|
||||
//! ```python
|
||||
//! n += 1
|
||||
//! ```
|
||||
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, UnaryOp};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -24,6 +5,26 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the unary prefix increment operator (e.g., `++n`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python does not support the unary prefix increment operator. Writing `++n`
|
||||
/// is equivalent to `+(+(n))`, which is equivalent to `n`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// ++n
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// n += 1
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Unary arithmetic and bitwise operations](https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations)
|
||||
/// - [Python documentation: Augmented assignment statements](https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements)
|
||||
#[violation]
|
||||
pub struct UnaryPrefixIncrement;
|
||||
|
||||
|
||||
@@ -5,6 +5,32 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `hasattr` to test if an object is callable (e.g.,
|
||||
/// `hasattr(obj, "__call__")`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `hasattr` is an unreliable mechanism for testing if an object is
|
||||
/// callable. If `obj` implements a custom `__getattr__`, or if its `__call__`
|
||||
/// is itself not callable, you may get misleading results.
|
||||
///
|
||||
/// Instead, use `callable(obj)` to test if `obj` is callable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// hasattr(obj, "__call__")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// callable(obj)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `callable`](https://docs.python.org/3/library/functions.html#callable)
|
||||
/// - [Python documentation: `hasattr`](https://docs.python.org/3/library/functions.html#hasattr)
|
||||
/// - [Python documentation: `__getattr__`](https://docs.python.org/3/reference/datamodel.html#object.__getattr__)
|
||||
/// - [Python documentation: `__call__`](https://docs.python.org/3/reference/datamodel.html#object.__call__)
|
||||
#[violation]
|
||||
pub struct UnreliableCallableCheck;
|
||||
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
//! Checks for unused loop variables.
|
||||
//!
|
||||
//! ## Why is this bad?
|
||||
//!
|
||||
//! Unused variables may signal a mistake or unfinished code.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```python
|
||||
//! for x in range(10):
|
||||
//! method()
|
||||
//! ```
|
||||
//!
|
||||
//! Prefix the variable with an underscore:
|
||||
//!
|
||||
//! ```python
|
||||
//! for _x in range(10):
|
||||
//! method()
|
||||
//! ```
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt};
|
||||
|
||||
@@ -35,6 +15,31 @@ enum Certainty {
|
||||
Uncertain,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for unused variables in loops (e.g., `for` and `while` statements).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Defining a variable in a loop statement that is never used can confuse
|
||||
/// readers.
|
||||
///
|
||||
/// If the variable is intended to be unused (e.g., to facilitate
|
||||
/// destructuring of a tuple or other object), prefix it with an underscore
|
||||
/// to indicate the intent. Otherwise, remove the variable entirely.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// for i, j in foo:
|
||||
/// bar(i)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// for i, _j in foo:
|
||||
/// bar(i)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Naming Conventions](https://peps.python.org/pep-0008/#naming-conventions)
|
||||
#[violation]
|
||||
pub struct UnusedLoopControlVariable {
|
||||
/// The name of the loop control variable.
|
||||
|
||||
@@ -5,6 +5,26 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for useless comparisons.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Useless comparisons have no effect on the program, and are often included
|
||||
/// by mistake. If the comparison is intended to enforce an invariant, prepend
|
||||
/// the comparison with an `assert`. Otherwise, remove it entirely.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo == bar
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// assert foo == bar, "`foo` and `bar` should be equal."
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `assert` statement](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
|
||||
#[violation]
|
||||
pub struct UselessComparison;
|
||||
|
||||
|
||||
@@ -5,6 +5,36 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `contextlib.suppress` without arguments.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `contextlib.suppress` is a context manager that suppresses exceptions. It takes,
|
||||
/// as arguments, the exceptions to suppress within the enclosed block. If no
|
||||
/// exceptions are specified, then the context manager won't suppress any
|
||||
/// exceptions, and is thus redundant.
|
||||
///
|
||||
/// Consider adding exceptions to the `contextlib.suppress` call, or removing the
|
||||
/// context manager entirely.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import contextlib
|
||||
///
|
||||
/// with contextlib.suppress():
|
||||
/// foo()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import contextlib
|
||||
///
|
||||
/// with contextlib.suppress(Exception):
|
||||
/// foo()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: contextlib.suppress](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)
|
||||
#[violation]
|
||||
pub struct UselessContextlibSuppress;
|
||||
|
||||
|
||||
@@ -6,12 +6,23 @@ use ruff_python_ast::helpers::contains_effect;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum Kind {
|
||||
Expression,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for useless expressions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Useless expressions have no effect on the program, and are often included
|
||||
/// by mistake. Assign a useless expression to a variable, or remove it
|
||||
/// entirely.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// 1 + 1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// foo = 1 + 1
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UselessExpression {
|
||||
kind: Kind,
|
||||
@@ -74,3 +85,9 @@ pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
|
||||
value.range(),
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum Kind {
|
||||
Expression,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,29 @@ use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `zip` calls without an explicit `strict` parameter.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By default, if the iterables passed to `zip` are of different lengths, the
|
||||
/// resulting iterator will be silently truncated to the length of the shortest
|
||||
/// iterable. This can lead to subtle bugs.
|
||||
///
|
||||
/// Use the `strict` parameter to raise a `ValueError` if the iterables are of
|
||||
/// non-uniform length.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// zip(a, b)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// zip(a, b, strict=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip)
|
||||
#[violation]
|
||||
pub struct ZipWithoutExplicitStrict;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{ExceptHandler, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
use ruff_python_ast::identifier::{Identifier, TryIdentifier};
|
||||
use ruff_python_stdlib::builtins::BUILTINS;
|
||||
|
||||
pub(super) fn shadows_builtin(name: &str, ignorelist: &[String]) -> bool {
|
||||
@@ -16,12 +15,12 @@ pub(crate) enum AnyShadowing<'a> {
|
||||
ExceptHandler(&'a ExceptHandler),
|
||||
}
|
||||
|
||||
impl AnyShadowing<'_> {
|
||||
pub(crate) fn range(self, locator: &Locator) -> TextRange {
|
||||
impl Identifier for AnyShadowing<'_> {
|
||||
fn identifier(&self) -> TextRange {
|
||||
match self {
|
||||
AnyShadowing::Expression(expr) => expr.range(),
|
||||
AnyShadowing::Statement(stmt) => stmt.identifier(locator),
|
||||
AnyShadowing::ExceptHandler(handler) => handler.range(),
|
||||
AnyShadowing::Statement(stmt) => stmt.identifier(),
|
||||
AnyShadowing::ExceptHandler(handler) => handler.try_identifier().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -83,7 +84,7 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
BuiltinAttributeShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.range(checker.locator),
|
||||
shadowing.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -68,7 +69,7 @@ pub(crate) fn builtin_variable_shadowing(
|
||||
BuiltinVariableShadowing {
|
||||
name: name.to_string(),
|
||||
},
|
||||
shadowing.range(checker.locator),
|
||||
shadowing.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,15 +122,13 @@ A001.py:16:7: A001 Variable `slice` is shadowing a Python builtin
|
||||
17 | pass
|
||||
|
|
||||
|
||||
A001.py:21:1: A001 Variable `ValueError` is shadowing a Python builtin
|
||||
A001.py:21:23: A001 Variable `ValueError` is shadowing a Python builtin
|
||||
|
|
||||
19 | try:
|
||||
20 | ...
|
||||
21 | / except ImportError as ValueError:
|
||||
22 | | ...
|
||||
| |_______^ A001
|
||||
23 |
|
||||
24 | for memoryview, *bytearray in []:
|
||||
19 | try:
|
||||
20 | ...
|
||||
21 | except ImportError as ValueError:
|
||||
| ^^^^^^^^^^ A001
|
||||
22 | ...
|
||||
|
|
||||
|
||||
A001.py:24:5: A001 Variable `memoryview` is shadowing a Python builtin
|
||||
|
||||
@@ -102,15 +102,13 @@ A001.py:16:7: A001 Variable `slice` is shadowing a Python builtin
|
||||
17 | pass
|
||||
|
|
||||
|
||||
A001.py:21:1: A001 Variable `ValueError` is shadowing a Python builtin
|
||||
A001.py:21:23: A001 Variable `ValueError` is shadowing a Python builtin
|
||||
|
|
||||
19 | try:
|
||||
20 | ...
|
||||
21 | / except ImportError as ValueError:
|
||||
22 | | ...
|
||||
| |_______^ A001
|
||||
23 |
|
||||
24 | for memoryview, *bytearray in []:
|
||||
19 | try:
|
||||
20 | ...
|
||||
21 | except ImportError as ValueError:
|
||||
| ^^^^^^^^^^ A001
|
||||
22 | ...
|
||||
|
|
||||
|
||||
A001.py:24:5: A001 Variable `memoryview` is shadowing a Python builtin
|
||||
|
||||
@@ -75,7 +75,7 @@ import os
|
||||
"#
|
||||
.trim(),
|
||||
&settings::Settings {
|
||||
copyright: super::settings::Settings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -95,7 +95,7 @@ import os
|
||||
"#
|
||||
.trim(),
|
||||
&settings::Settings {
|
||||
copyright: super::settings::Settings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
author: Some("Ruff".to_string()),
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -113,7 +113,7 @@ import os
|
||||
"#
|
||||
.trim(),
|
||||
&settings::Settings {
|
||||
copyright: super::settings::Settings {
|
||||
flake8_copyright: super::settings::Settings {
|
||||
min_file_size: 256,
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
@@ -28,7 +28,7 @@ pub(crate) fn missing_copyright_notice(
|
||||
settings: &Settings,
|
||||
) -> Option<Diagnostic> {
|
||||
// Ignore files that are too small to contain a copyright notice.
|
||||
if locator.len() < settings.copyright.min_file_size {
|
||||
if locator.len() < settings.flake8_copyright.min_file_size {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ pub(crate) fn missing_copyright_notice(
|
||||
};
|
||||
|
||||
// Locate the copyright notice.
|
||||
if let Some(match_) = settings.copyright.notice_rgx.find(contents) {
|
||||
match settings.copyright.author {
|
||||
if let Some(match_) = settings.flake8_copyright.notice_rgx.find(contents) {
|
||||
match settings.flake8_copyright.author {
|
||||
Some(ref author) => {
|
||||
// Ensure that it's immediately followed by the author.
|
||||
if contents[match_.end()..].trim_start().starts_with(author) {
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Settings for the `copyright` plugin.
|
||||
//! Settings for the `flake8-copyright` plugin.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
@@ -12,7 +12,7 @@ use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "CopyrightOptions"
|
||||
rename = "Flake8CopyrightOptions"
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Options {
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
<filename>:1:1: CPY001 Missing copyright notice at top of file
|
||||
|
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/copyright/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
<filename>:1:1: CPY001 Missing copyright notice at top of file
|
||||
|
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -7,6 +7,28 @@ use ruff_python_ast::call_path::{format_call_path, from_unqualified_name, CallPa
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_debugger::types::DebuggerUsingType;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of debugger calls and imports.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Debugger calls and imports should be used for debugging purposes only. The
|
||||
/// presence of a debugger call or import in production code is likely a
|
||||
/// mistake and may cause unintended behavior, such as exposing sensitive
|
||||
/// information or causing the program to hang.
|
||||
///
|
||||
/// Instead, consider using a logging library to log information about the
|
||||
/// program's state, and writing tests to verify that the program behaves
|
||||
/// as expected.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// breakpoint()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `pdb` — The Python Debugger](https://docs.python.org/3/library/pdb.html)
|
||||
/// - [Python documentation: `logging` — Logging facility for Python](https://docs.python.org/3/library/logging.html)
|
||||
#[violation]
|
||||
pub struct Debugger {
|
||||
using_type: DebuggerUsingType,
|
||||
|
||||
@@ -3,7 +3,7 @@ use rustpython_parser::ast::{self, Expr};
|
||||
/// Returns true if the [`Expr`] is an internationalization function call.
|
||||
pub(crate) fn is_gettext_func_call(func: &Expr, functions_names: &[String]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
functions_names.contains(id.as_ref())
|
||||
functions_names.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub(crate) use f_string_in_gettext_func_call::*;
|
||||
pub(crate) use format_in_gettext_func_call::*;
|
||||
pub(crate) use is_gettext_func_call::is_gettext_func_call;
|
||||
pub(crate) use is_gettext_func_call::*;
|
||||
pub(crate) use printf_in_gettext_func_call::*;
|
||||
|
||||
mod f_string_in_gettext_func_call;
|
||||
|
||||
@@ -16,7 +16,7 @@ custom.py:4:8: ICN001 `dask.array` should be imported as `da`
|
||||
|
|
||||
3 | import altair # unconventional
|
||||
4 | import dask.array # unconventional
|
||||
| ^^^^ ICN001
|
||||
| ^^^^^^^^^^ ICN001
|
||||
5 | import dask.dataframe # unconventional
|
||||
6 | import matplotlib.pyplot # unconventional
|
||||
|
|
||||
@@ -27,7 +27,7 @@ custom.py:5:8: ICN001 `dask.dataframe` should be imported as `dd`
|
||||
3 | import altair # unconventional
|
||||
4 | import dask.array # unconventional
|
||||
5 | import dask.dataframe # unconventional
|
||||
| ^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^ ICN001
|
||||
6 | import matplotlib.pyplot # unconventional
|
||||
7 | import numpy # unconventional
|
||||
|
|
||||
@@ -38,7 +38,7 @@ custom.py:6:8: ICN001 `matplotlib.pyplot` should be imported as `plt`
|
||||
4 | import dask.array # unconventional
|
||||
5 | import dask.dataframe # unconventional
|
||||
6 | import matplotlib.pyplot # unconventional
|
||||
| ^^^^^^^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^^^^ ICN001
|
||||
7 | import numpy # unconventional
|
||||
8 | import pandas # unconventional
|
||||
|
|
||||
@@ -115,7 +115,7 @@ custom.py:13:8: ICN001 `plotly.express` should be imported as `px`
|
||||
11 | import holoviews # unconventional
|
||||
12 | import panel # unconventional
|
||||
13 | import plotly.express # unconventional
|
||||
| ^^^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^ ICN001
|
||||
14 | import matplotlib # unconventional
|
||||
15 | import polars # unconventional
|
||||
|
|
||||
|
||||
@@ -16,7 +16,7 @@ defaults.py:4:8: ICN001 `matplotlib.pyplot` should be imported as `plt`
|
||||
|
|
||||
3 | import altair # unconventional
|
||||
4 | import matplotlib.pyplot # unconventional
|
||||
| ^^^^^^^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^^^^ ICN001
|
||||
5 | import numpy # unconventional
|
||||
6 | import pandas # unconventional
|
||||
|
|
||||
|
||||
@@ -6,7 +6,7 @@ from_imports.py:3:8: ICN001 `xml.dom.minidom` should be imported as `md`
|
||||
1 | # Test absolute imports
|
||||
2 | # Violation cases
|
||||
3 | import xml.dom.minidom
|
||||
| ^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^^ ICN001
|
||||
4 | import xml.dom.minidom as wrong
|
||||
5 | from xml.dom import minidom as wrong
|
||||
|
|
||||
|
||||
@@ -16,7 +16,7 @@ override_default.py:4:8: ICN001 `matplotlib.pyplot` should be imported as `plt`
|
||||
|
|
||||
3 | import altair # unconventional
|
||||
4 | import matplotlib.pyplot # unconventional
|
||||
| ^^^^^^^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^^^^ ICN001
|
||||
5 | import numpy # unconventional
|
||||
6 | import pandas # unconventional
|
||||
|
|
||||
|
||||
@@ -16,7 +16,7 @@ remove_default.py:4:8: ICN001 `matplotlib.pyplot` should be imported as `plt`
|
||||
|
|
||||
3 | import altair # unconventional
|
||||
4 | import matplotlib.pyplot # unconventional
|
||||
| ^^^^^^^^^^ ICN001
|
||||
| ^^^^^^^^^^^^^^^^^ ICN001
|
||||
5 | import numpy # not checked
|
||||
6 | import pandas # unconventional
|
||||
|
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub(crate) use logging_call::logging_call;
|
||||
pub(crate) use logging_call::*;
|
||||
|
||||
mod logging_call;
|
||||
|
||||
@@ -5,7 +5,7 @@ use itertools::Either::{Left, Right};
|
||||
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use rustpython_parser::ast::{self, BoolOp, Expr, ExprContext, Ranged};
|
||||
use rustpython_parser::ast::{self, BoolOp, Expr, ExprContext, Identifier, Ranged};
|
||||
|
||||
use ruff_diagnostics::AlwaysAutofixableViolation;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
@@ -140,7 +140,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
});
|
||||
let node2 = Expr::Attribute(ast::ExprAttribute {
|
||||
value: Box::new(node1),
|
||||
attr: attr_name.into(),
|
||||
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
});
|
||||
|
||||
@@ -148,7 +148,7 @@ pub(crate) fn non_self_return_type(
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
@@ -162,7 +162,7 @@ pub(crate) fn non_self_return_type(
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
@@ -177,7 +177,7 @@ pub(crate) fn non_self_return_type(
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
@@ -193,7 +193,7 @@ pub(crate) fn non_self_return_type(
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ pub(crate) fn non_self_return_type(
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||
StrOrReprDefinedInStub {
|
||||
name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let stmt = checker.semantic().stmt();
|
||||
|
||||
@@ -32,6 +32,6 @@ pub(crate) fn stub_body_multiple_statements(checker: &mut Checker, stmt: &Stmt,
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
StubBodyMultipleStatements,
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl Violation for TypeCommentInStub {
|
||||
}
|
||||
|
||||
/// PYI033
|
||||
pub(crate) fn type_comment_in_stub(indexer: &Indexer, locator: &Locator) -> Vec<Diagnostic> {
|
||||
pub(crate) fn type_comment_in_stub(locator: &Locator, indexer: &Indexer) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
|
||||
for range in indexer.comment_ranges() {
|
||||
|
||||
@@ -9,7 +9,6 @@ use libcst_native::{
|
||||
};
|
||||
use rustpython_parser::ast::{self, BoolOp, ExceptHandler, Expr, Keyword, Ranged, Stmt, UnaryOp};
|
||||
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{has_comments_in, Truthiness};
|
||||
@@ -17,6 +16,7 @@ use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{visitor, whitespace};
|
||||
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::cst::matchers::match_indented_block;
|
||||
use crate::cst::matchers::match_module;
|
||||
@@ -315,6 +315,54 @@ fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Propagate parentheses from a parent to a child expression, if necessary.
|
||||
///
|
||||
/// For example, when splitting:
|
||||
/// ```python
|
||||
/// assert (a and b ==
|
||||
/// """)
|
||||
/// ```
|
||||
///
|
||||
/// The parentheses need to be propagated to the right-most expression:
|
||||
/// ```python
|
||||
/// assert a
|
||||
/// assert (b ==
|
||||
/// "")
|
||||
/// ```
|
||||
fn parenthesize<'a>(expression: Expression<'a>, parent: &Expression<'a>) -> Expression<'a> {
|
||||
if matches!(
|
||||
expression,
|
||||
Expression::Comparison(_)
|
||||
| Expression::UnaryOperation(_)
|
||||
| Expression::BinaryOperation(_)
|
||||
| Expression::BooleanOperation(_)
|
||||
| Expression::Attribute(_)
|
||||
| Expression::Tuple(_)
|
||||
| Expression::Call(_)
|
||||
| Expression::GeneratorExp(_)
|
||||
| Expression::ListComp(_)
|
||||
| Expression::SetComp(_)
|
||||
| Expression::DictComp(_)
|
||||
| Expression::List(_)
|
||||
| Expression::Set(_)
|
||||
| Expression::Dict(_)
|
||||
| Expression::Subscript(_)
|
||||
| Expression::StarredElement(_)
|
||||
| Expression::IfExp(_)
|
||||
| Expression::Lambda(_)
|
||||
| Expression::Yield(_)
|
||||
| Expression::Await(_)
|
||||
| Expression::ConcatenatedString(_)
|
||||
| Expression::FormattedString(_)
|
||||
| Expression::NamedExpr(_)
|
||||
) {
|
||||
if let (Some(left), Some(right)) = (parent.lpar().first(), parent.rpar().first()) {
|
||||
return expression.with_parens(left.clone(), right.clone());
|
||||
}
|
||||
}
|
||||
expression
|
||||
}
|
||||
|
||||
/// Replace composite condition `assert a == "hello" and b == "world"` with two statements
|
||||
/// `assert a == "hello"` and `assert b == "world"`.
|
||||
fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Result<Edit> {
|
||||
@@ -363,10 +411,6 @@ fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) ->
|
||||
bail!("Expected simple statement to be an assert")
|
||||
};
|
||||
|
||||
if !(assert_statement.test.lpar().is_empty() && assert_statement.test.rpar().is_empty()) {
|
||||
bail!("Unable to split parenthesized condition");
|
||||
}
|
||||
|
||||
// Extract the individual conditions.
|
||||
let mut conditions: Vec<Expression> = Vec::with_capacity(2);
|
||||
match &assert_statement.test {
|
||||
@@ -374,8 +418,8 @@ fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) ->
|
||||
if matches!(op.operator, libcst_native::UnaryOp::Not { .. }) {
|
||||
if let Expression::BooleanOperation(op) = &*op.expression {
|
||||
if matches!(op.operator, BooleanOp::Or { .. }) {
|
||||
conditions.push(negate(&op.left));
|
||||
conditions.push(negate(&op.right));
|
||||
conditions.push(parenthesize(negate(&op.left), &assert_statement.test));
|
||||
conditions.push(parenthesize(negate(&op.right), &assert_statement.test));
|
||||
} else {
|
||||
bail!("Expected assert statement to be a composite condition");
|
||||
}
|
||||
@@ -386,8 +430,8 @@ fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) ->
|
||||
}
|
||||
Expression::BooleanOperation(op) => {
|
||||
if matches!(op.operator, BooleanOp::And { .. }) {
|
||||
conditions.push(*op.left.clone());
|
||||
conditions.push(*op.right.clone());
|
||||
conditions.push(parenthesize(*op.left.clone(), &assert_statement.test));
|
||||
conditions.push(parenthesize(*op.right.clone(), &assert_statement.test));
|
||||
} else {
|
||||
bail!("Expected assert statement to be a composite condition");
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ fn check_fixture_returns(checker: &mut Checker, stmt: &Stmt, name: &str, body: &
|
||||
PytestIncorrectFixtureNameUnderscore {
|
||||
function: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
} else if checker.enabled(Rule::PytestMissingFixtureNameUnderscore)
|
||||
&& !visitor.has_return_with_value
|
||||
@@ -390,7 +390,7 @@ fn check_fixture_returns(checker: &mut Checker, stmt: &Stmt, name: &str, body: &
|
||||
PytestMissingFixtureNameUnderscore {
|
||||
function: name.to_string(),
|
||||
},
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{self, Expr, Identifier, Keyword, Ranged, Stmt, WithItem};
|
||||
use rustpython_parser::ast::{self, Expr, Keyword, Ranged, Stmt, WithItem};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -74,9 +74,12 @@ pub(crate) fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], key
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::PytestRaisesTooBroad) {
|
||||
let match_keyword = keywords
|
||||
.iter()
|
||||
.find(|kw| kw.arg == Some(Identifier::new("match")));
|
||||
let match_keyword = keywords.iter().find(|keyword| {
|
||||
keyword
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |arg| arg.as_str() == "match")
|
||||
});
|
||||
|
||||
if let Some(exception) = args.first() {
|
||||
if let Some(match_keyword) = match_keyword {
|
||||
|
||||
@@ -3,7 +3,9 @@ use std::hash::BuildHasherDefault;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, ExprContext, Keyword, Stmt, UnaryOp};
|
||||
use rustpython_parser::ast::{
|
||||
self, CmpOp, Constant, Expr, ExprContext, Identifier, Keyword, Stmt, UnaryOp,
|
||||
};
|
||||
|
||||
/// An enum to represent the different types of assertions present in the
|
||||
/// `unittest` module. Note: any variants that can't be replaced with plain
|
||||
@@ -388,7 +390,7 @@ impl UnittestAssert {
|
||||
};
|
||||
let node1 = ast::ExprAttribute {
|
||||
value: Box::new(node.into()),
|
||||
attr: "search".into(),
|
||||
attr: Identifier::new("search".to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
|
||||
@@ -163,8 +163,8 @@ PT018.py:21:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
22 | | message
|
||||
23 | | """
|
||||
| |_______^ PT018
|
||||
24 |
|
||||
25 | # recursive case
|
||||
24 | assert (
|
||||
25 | something
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
@@ -177,131 +177,144 @@ PT018.py:21:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
22 |+ assert something_else == """error
|
||||
22 23 | message
|
||||
23 24 | """
|
||||
24 25 |
|
||||
24 25 | assert (
|
||||
|
||||
PT018.py:26:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
PT018.py:24:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
25 | # recursive case
|
||||
26 | assert not (a or not (b or c))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
27 | assert not (a or not (b and c))
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
23 23 | """
|
||||
24 24 |
|
||||
25 25 | # recursive case
|
||||
26 |- assert not (a or not (b or c))
|
||||
26 |+ assert not a
|
||||
27 |+ assert (b or c)
|
||||
27 28 | assert not (a or not (b and c))
|
||||
28 29 |
|
||||
29 30 | # detected, but no autofix for messages
|
||||
|
||||
PT018.py:27:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
25 | # recursive case
|
||||
26 | assert not (a or not (b or c))
|
||||
27 | assert not (a or not (b and c))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
28 |
|
||||
29 | # detected, but no autofix for messages
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
24 24 |
|
||||
25 25 | # recursive case
|
||||
26 26 | assert not (a or not (b or c))
|
||||
27 |- assert not (a or not (b and c))
|
||||
27 |+ assert not a
|
||||
28 |+ assert (b and c)
|
||||
28 29 |
|
||||
29 30 | # detected, but no autofix for messages
|
||||
30 31 | assert something and something_else, "error message"
|
||||
|
||||
PT018.py:30:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
29 | # detected, but no autofix for messages
|
||||
30 | assert something and something_else, "error message"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
31 | assert not (something or something_else and something_third), "with message"
|
||||
32 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:31:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
29 | # detected, but no autofix for messages
|
||||
30 | assert something and something_else, "error message"
|
||||
31 | assert not (something or something_else and something_third), "with message"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
32 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
33 | assert not (something or something_else and something_third)
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:33:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
31 | assert not (something or something_else and something_third), "with message"
|
||||
32 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
33 | assert not (something or something_else and something_third)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
34 | # detected, but no autofix for parenthesized conditions
|
||||
35 | assert (
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:35:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
33 | assert not (something or something_else and something_third)
|
||||
34 | # detected, but no autofix for parenthesized conditions
|
||||
35 | assert (
|
||||
22 | message
|
||||
23 | """
|
||||
24 | assert (
|
||||
| _____^
|
||||
36 | | something
|
||||
37 | | and something_else
|
||||
38 | | == """error
|
||||
39 | | message
|
||||
40 | | """
|
||||
41 | | )
|
||||
25 | | something
|
||||
26 | | and something_else
|
||||
27 | | == """error
|
||||
28 | | message
|
||||
29 | | """
|
||||
30 | | )
|
||||
| |_____^ PT018
|
||||
31 |
|
||||
32 | # recursive case
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
21 21 | assert something and something_else == """error
|
||||
22 22 | message
|
||||
23 23 | """
|
||||
24 |+ assert something
|
||||
24 25 | assert (
|
||||
25 |- something
|
||||
26 |- and something_else
|
||||
26 |+ something_else
|
||||
27 27 | == """error
|
||||
28 28 | message
|
||||
29 29 | """
|
||||
|
||||
PT018.py:33:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
32 | # recursive case
|
||||
33 | assert not (a or not (b or c))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
34 | assert not (a or not (b and c))
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
30 30 | )
|
||||
31 31 |
|
||||
32 32 | # recursive case
|
||||
33 |- assert not (a or not (b or c))
|
||||
33 |+ assert not a
|
||||
34 |+ assert (b or c)
|
||||
34 35 | assert not (a or not (b and c))
|
||||
35 36 |
|
||||
36 37 | # detected, but no autofix for messages
|
||||
|
||||
PT018.py:34:5: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
32 | # recursive case
|
||||
33 | assert not (a or not (b or c))
|
||||
34 | assert not (a or not (b and c))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
35 |
|
||||
36 | # detected, but no autofix for messages
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
31 31 |
|
||||
32 32 | # recursive case
|
||||
33 33 | assert not (a or not (b or c))
|
||||
34 |- assert not (a or not (b and c))
|
||||
34 |+ assert not a
|
||||
35 |+ assert (b and c)
|
||||
35 36 |
|
||||
36 37 | # detected, but no autofix for messages
|
||||
37 38 | assert something and something_else, "error message"
|
||||
|
||||
PT018.py:37:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
36 | # detected, but no autofix for messages
|
||||
37 | assert something and something_else, "error message"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
38 | assert not (something or something_else and something_third), "with message"
|
||||
39 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:38:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
36 | # detected, but no autofix for messages
|
||||
37 | assert something and something_else, "error message"
|
||||
38 | assert not (something or something_else and something_third), "with message"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
39 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
40 | assert not (something or something_else and something_third)
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:40:5: PT018 Assertion should be broken down into multiple parts
|
||||
|
|
||||
38 | assert not (something or something_else and something_third), "with message"
|
||||
39 | # detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
40 | assert not (something or something_else and something_third)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
PT018.py:44:1: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
43 | assert something # OK
|
||||
44 | assert something and something_else # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
45 | assert something and something_else and something_third # Error
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
41 41 |
|
||||
42 42 |
|
||||
43 43 | assert something # OK
|
||||
44 |-assert something and something_else # Error
|
||||
44 |+assert something
|
||||
45 |+assert something_else
|
||||
45 46 | assert something and something_else and something_third # Error
|
||||
|
||||
PT018.py:45:1: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
44 | assert something # OK
|
||||
45 | assert something and something_else # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
46 | assert something and something_else and something_third # Error
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
42 42 |
|
||||
43 43 |
|
||||
44 44 | assert something # OK
|
||||
45 |-assert something and something_else # Error
|
||||
45 |+assert something
|
||||
46 |+assert something_else
|
||||
46 47 | assert something and something_else and something_third # Error
|
||||
|
||||
PT018.py:46:1: PT018 [*] Assertion should be broken down into multiple parts
|
||||
|
|
||||
44 | assert something # OK
|
||||
45 | assert something and something_else # Error
|
||||
46 | assert something and something_else and something_third # Error
|
||||
43 | assert something # OK
|
||||
44 | assert something and something_else # Error
|
||||
45 | assert something and something_else and something_third # Error
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT018
|
||||
|
|
||||
= help: Break down assertion into multiple parts
|
||||
|
||||
ℹ Suggested fix
|
||||
43 43 |
|
||||
44 44 | assert something # OK
|
||||
45 45 | assert something and something_else # Error
|
||||
46 |-assert something and something_else and something_third # Error
|
||||
46 |+assert something and something_else
|
||||
47 |+assert something_third
|
||||
42 42 |
|
||||
43 43 | assert something # OK
|
||||
44 44 | assert something and something_else # Error
|
||||
45 |-assert something and something_else and something_third # Error
|
||||
45 |+assert something and something_else
|
||||
46 |+assert something_third
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use log::error;
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, ExprContext, Ranged, Stmt};
|
||||
use rustpython_parser::ast::{self, CmpOp, Constant, Expr, ExprContext, Identifier, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -941,7 +941,7 @@ pub(crate) fn use_dict_get_with_default(
|
||||
let node1 = *test_key.clone();
|
||||
let node2 = ast::ExprAttribute {
|
||||
value: expected_subscript.clone(),
|
||||
attr: "get".into(),
|
||||
attr: Identifier::new("get".to_string(), TextRange::default()),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
|
||||
if !has_slots(&class.body) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoSlotsInNamedtupleSubclass,
|
||||
stmt.identifier(checker.locator),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,9 @@ pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class
|
||||
})
|
||||
}) {
|
||||
if !has_slots(&class.body) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoSlotsInStrSubclass,
|
||||
stmt.identifier(checker.locator),
|
||||
));
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(NoSlotsInStrSubclass, stmt.identifier()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +63,9 @@ pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, cla
|
||||
})
|
||||
}) {
|
||||
if !has_slots(&class.body) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoSlotsInTupleSubclass,
|
||||
stmt.identifier(checker.locator),
|
||||
));
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(NoSlotsInTupleSubclass, stmt.identifier()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user