Compare commits
69 Commits
v0.0.273
...
charlie/ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fac6e83705 | ||
|
|
1cf307c34c | ||
|
|
7819b95d7f | ||
|
|
4a81cfc51a | ||
|
|
38e618cd18 | ||
|
|
50f0edd2cb | ||
|
|
e0e1d13d9f | ||
|
|
8bc7378002 | ||
|
|
cdbd0bd5cd | ||
|
|
5f88ff8a96 | ||
|
|
1c2be54b4a | ||
|
|
5dd00b19e6 | ||
|
|
c0f93fcf3e | ||
|
|
3238a6ef1f | ||
|
|
96ecfae1c5 | ||
|
|
03694ef649 | ||
|
|
f9f0cf7524 | ||
|
|
eaa10ad2d9 | ||
|
|
84259f5440 | ||
|
|
e8ebe0a425 | ||
|
|
d407165aa7 | ||
|
|
f7e1cf4b51 | ||
|
|
7d4f8e59da | ||
|
|
2c63f8cdea | ||
|
|
1c0a3a467f | ||
|
|
6b8b318d6b | ||
|
|
c0c59b82ec | ||
|
|
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 |
84
.github/workflows/release.yaml
vendored
84
.github/workflows/release.yaml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run skipping uploading artifact."
|
||||
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"
|
||||
@@ -392,28 +392,14 @@ jobs:
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
|
||||
release:
|
||||
name: Release
|
||||
validate-tag:
|
||||
name: Validate tag
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
# If you don't set an input it's a dry run skipping uploading artifact
|
||||
# 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:
|
||||
- name: Consistency check tag
|
||||
- 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
|
||||
@@ -424,7 +410,7 @@ jobs:
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
- name: Consistency check sha
|
||||
- name: Check SHA consistency
|
||||
if: ${{ inputs.sha }}
|
||||
run: |
|
||||
git_sha=$(git rev-parse HEAD)
|
||||
@@ -436,6 +422,27 @@ jobs:
|
||||
else
|
||||
echo "Releasing ${git_sha}"
|
||||
fi
|
||||
|
||||
upload-release:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
- musllinux
|
||||
- musllinux-cross
|
||||
- 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
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
@@ -446,10 +453,18 @@ jobs:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries
|
||||
|
||||
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"
|
||||
@@ -458,6 +473,21 @@ jobs:
|
||||
# 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
|
||||
path: binaries
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
@@ -468,9 +498,9 @@ jobs:
|
||||
# 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
|
||||
update-dependents:
|
||||
name: Release
|
||||
name: Update dependents
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
needs: publish-release
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v6
|
||||
|
||||
131
CONTRIBUTING.md
131
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
|
||||
|
||||
@@ -307,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.
|
||||
@@ -386,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:
|
||||
|
||||
@@ -429,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.273"
|
||||
version = "0.0.275"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1793,7 +1793,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.273"
|
||||
version = "0.0.275"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1889,7 +1889,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.273"
|
||||
version = "0.0.275"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
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=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
|
||||
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=f60e204b73b95bdb6ce87ccd0de34081b4a17c11#f60e204b73b95bdb6ce87ccd0de34081b4a17c11"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"memchr",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -50,15 +50,15 @@ 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 = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" }
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "f60e204b73b95bdb6ce87ccd0de34081b4a17c11" }
|
||||
# v0.0.3
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "f60e204b73b95bdb6ce87ccd0de34081b4a17c11" , 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 = "ed3b4eb72b6e497bbdb4d19dec6621074d724130", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "f60e204b73b95bdb6ce87ccd0de34081b4a17c11", default-features = false, features = ["num-bigint"] }
|
||||
# v0.0.3
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130", default-features = false }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "f60e204b73b95bdb6ce87ccd0de34081b4a17c11", default-features = false }
|
||||
# v0.0.3
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "f60e204b73b95bdb6ce87ccd0de34081b4a17c11" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
@@ -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.273
|
||||
rev: v0.0.275
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.273"
|
||||
version = "0.0.275"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.273"
|
||||
version = "0.0.275"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# T002 - accepted
|
||||
# TODO (evanrittenhouse): this has an author
|
||||
# TODO(evanrittenhouse): this also has an author
|
||||
# TODO(evanrittenhouse): this has an author
|
||||
# TODO (evanrittenhouse) and more: this has an author
|
||||
# TODO(evanrittenhouse) and more: this has an author
|
||||
# TODO@mayrholu: this has an author
|
||||
# TODO @mayrholu: this has an author
|
||||
# TODO@mayrholu and more: this has an author
|
||||
# TODO @mayrholu and more: this has an author
|
||||
# T002 - errors
|
||||
# TODO: this has no author
|
||||
# FIXME: neither does this
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import ABCMeta
|
||||
import abc
|
||||
|
||||
import pydantic
|
||||
|
||||
@@ -19,6 +19,10 @@ class Class:
|
||||
def class_method(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def abstract_class_method(cls):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def static_method(x):
|
||||
return x
|
||||
@@ -41,7 +45,7 @@ class Class:
|
||||
...
|
||||
|
||||
|
||||
class MetaClass(ABCMeta):
|
||||
class MetaClass(abc.ABCMeta):
|
||||
def bad_method(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import ABCMeta
|
||||
import abc
|
||||
|
||||
import pydantic
|
||||
|
||||
@@ -34,6 +34,23 @@ class Class:
|
||||
def stillBad(cls, my_field: str) -> str:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def badAllowed(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def stillBad(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def badAllowed(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def stillBad(cls):
|
||||
pass
|
||||
|
||||
|
||||
class PosOnlyClass:
|
||||
def badAllowed(this, blah, /, self, something: str):
|
||||
pass
|
||||
|
||||
52
crates/ruff/resources/test/fixtures/perflint/PERF101.py
vendored
Normal file
52
crates/ruff/resources/test/fixtures/perflint/PERF101.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
foo_tuple = (1, 2, 3)
|
||||
foo_list = [1, 2, 3]
|
||||
foo_set = {1, 2, 3}
|
||||
foo_dict = {1: 2, 3: 4}
|
||||
foo_int = 123
|
||||
|
||||
for i in list(foo_tuple): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_list): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_set): # PERF101
|
||||
pass
|
||||
|
||||
for i in list((1, 2, 3)): # PERF101
|
||||
pass
|
||||
|
||||
for i in list([1, 2, 3]): # PERF101
|
||||
pass
|
||||
|
||||
for i in list({1, 2, 3}): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
}
|
||||
):
|
||||
pass
|
||||
|
||||
for i in list( # Comment
|
||||
{1, 2, 3}
|
||||
): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_dict): # Ok
|
||||
pass
|
||||
|
||||
for i in list(1): # Ok
|
||||
pass
|
||||
|
||||
for i in list(foo_int): # Ok
|
||||
pass
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
for i in itertools.product(foo_int): # Ok
|
||||
pass
|
||||
@@ -37,7 +37,10 @@ f"{{test}}"
|
||||
f'{{ 40 }}'
|
||||
f"{{a {{x}}"
|
||||
f"{{{{x}}}}"
|
||||
""f""
|
||||
''f""
|
||||
(""f""r"")
|
||||
|
||||
# To be fixed
|
||||
# Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
# f"\{{x}}"
|
||||
# f"\{{x}}"
|
||||
|
||||
@@ -48,3 +48,12 @@ if True: from collections import (
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
# Ok: `typing_extensions` contains backported improvements.
|
||||
from typing_extensions import SupportsIndex
|
||||
|
||||
# Ok: `typing_extensions` contains backported improvements.
|
||||
from typing_extensions import NamedTuple
|
||||
|
||||
# Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@@ -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,23 +1,16 @@
|
||||
import typing
|
||||
from typing import ClassVar, Sequence
|
||||
|
||||
KNOWINGLY_MUTABLE_DEFAULT = []
|
||||
from typing import ClassVar, Sequence, Final
|
||||
|
||||
|
||||
class A:
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: typing.Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: typing.ClassVar[list[int]] = []
|
||||
__slots__ = {
|
||||
"mutable_default": "A mutable default value",
|
||||
}
|
||||
|
||||
|
||||
class B:
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
@@ -28,6 +21,17 @@ class C:
|
||||
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]] = []
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class D(BaseModel):
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
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
|
||||
|
||||
@@ -1481,6 +1481,9 @@ where
|
||||
if self.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(self, target, iter);
|
||||
}
|
||||
if self.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(self, iter);
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
@@ -3852,6 +3855,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,
|
||||
@@ -3862,14 +3868,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) {
|
||||
@@ -3889,6 +3887,13 @@ where
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
name,
|
||||
range,
|
||||
BindingKind::UnboundException(existing_id),
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
None => walk_except_handler(self, except_handler),
|
||||
}
|
||||
@@ -4236,7 +4241,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();
|
||||
|
||||
@@ -8,7 +8,7 @@ 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,
|
||||
|
||||
@@ -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),
|
||||
@@ -784,6 +784,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Airflow, "001") => (RuleGroup::Unspecified, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
|
||||
|
||||
// perflint
|
||||
(Perflint, "101") => (RuleGroup::Unspecified, rules::perflint::rules::UnnecessaryListCast),
|
||||
(Perflint, "102") => (RuleGroup::Unspecified, rules::perflint::rules::IncorrectDictIterator),
|
||||
|
||||
// flake8-fixme
|
||||
|
||||
@@ -445,7 +445,7 @@ impl Notebook {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -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
|
||||
---
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{self, Expr, Stmt};
|
||||
use rustpython_parser::ast::{self, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -50,9 +50,9 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
return;
|
||||
};
|
||||
let Expr::JoinedStr ( _) = value.as_ref() else {
|
||||
if !value.is_joined_str_expr() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FStringDocstring, stmt.identifier()));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of comparators other than `<` and `>=` for
|
||||
/// Checks for uses of comparators other than `<` and `>=` for
|
||||
/// `sys.version_info` checks in `.pyi` files. All other comparators, such
|
||||
/// as `>`, `<=`, and `==`, are banned.
|
||||
///
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_python_semantic::SemanticModel;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of the builtin `open()` function without an associated context
|
||||
/// Checks for uses of the builtin `open()` function without an associated context
|
||||
/// manager.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
|
||||
@@ -67,7 +67,7 @@ pub struct MissingTodoAuthor;
|
||||
impl Violation for MissingTodoAuthor {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Missing author in TODO; try: `# TODO(<author_name>): ...`")
|
||||
format!("Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ static ISSUE_LINK_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| {
|
||||
RegexSet::new([
|
||||
r#"^#\s*(http|https)://.*"#, // issue link
|
||||
r#"^#\s*\d+$"#, // issue code - like "003"
|
||||
r#"^#\s*[A-Z]{1,6}\-?\d+$"#, // issue code - like "TD003" or "TD-003"
|
||||
r#"^#\s*[A-Z]{1,6}\-?\d+$"#, // issue code - like "TD003"
|
||||
])
|
||||
.unwrap()
|
||||
});
|
||||
@@ -339,8 +339,7 @@ fn directive_errors(
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for "static" errors in the comment: missing colon, missing author, etc. This function
|
||||
/// modifies `diagnostics` in-place.
|
||||
/// Checks for "static" errors in the comment: missing colon, missing author, etc.
|
||||
fn static_errors(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
comment: &str,
|
||||
@@ -358,6 +357,15 @@ fn static_errors(
|
||||
} else {
|
||||
trimmed.text_len()
|
||||
}
|
||||
} else if trimmed.starts_with('@') {
|
||||
if let Some(end_index) = trimmed.find(|c: char| c.is_whitespace() || c == ':') {
|
||||
TextSize::try_from(end_index).unwrap()
|
||||
} else {
|
||||
// TD002
|
||||
diagnostics.push(Diagnostic::new(MissingTodoAuthor, directive.range));
|
||||
|
||||
TextSize::new(0)
|
||||
}
|
||||
} else {
|
||||
// TD002
|
||||
diagnostics.push(Diagnostic::new(MissingTodoAuthor, directive.range));
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_todos/mod.rs
|
||||
---
|
||||
TD002.py:5:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...`
|
||||
|
|
||||
3 | # TODO(evanrittenhouse): this also has an author
|
||||
4 | # T002 - errors
|
||||
5 | # TODO: this has no author
|
||||
| ^^^^ TD002
|
||||
6 | # FIXME: neither does this
|
||||
7 | # TODO : and neither does this
|
||||
|
|
||||
TD002.py:11:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`
|
||||
|
|
||||
9 | # TODO @mayrholu and more: this has an author
|
||||
10 | # T002 - errors
|
||||
11 | # TODO: this has no author
|
||||
| ^^^^ TD002
|
||||
12 | # FIXME: neither does this
|
||||
13 | # TODO : and neither does this
|
||||
|
|
||||
|
||||
TD002.py:6:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...`
|
||||
|
|
||||
4 | # T002 - errors
|
||||
5 | # TODO: this has no author
|
||||
6 | # FIXME: neither does this
|
||||
| ^^^^^ TD002
|
||||
7 | # TODO : and neither does this
|
||||
8 | # foo # TODO: this doesn't either
|
||||
|
|
||||
TD002.py:12:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`
|
||||
|
|
||||
10 | # T002 - errors
|
||||
11 | # TODO: this has no author
|
||||
12 | # FIXME: neither does this
|
||||
| ^^^^^ TD002
|
||||
13 | # TODO : and neither does this
|
||||
14 | # foo # TODO: this doesn't either
|
||||
|
|
||||
|
||||
TD002.py:7:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...`
|
||||
|
|
||||
5 | # TODO: this has no author
|
||||
6 | # FIXME: neither does this
|
||||
7 | # TODO : and neither does this
|
||||
| ^^^^ TD002
|
||||
8 | # foo # TODO: this doesn't either
|
||||
|
|
||||
TD002.py:13:3: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`
|
||||
|
|
||||
11 | # TODO: this has no author
|
||||
12 | # FIXME: neither does this
|
||||
13 | # TODO : and neither does this
|
||||
| ^^^^ TD002
|
||||
14 | # foo # TODO: this doesn't either
|
||||
|
|
||||
|
||||
TD002.py:8:9: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...`
|
||||
|
|
||||
6 | # FIXME: neither does this
|
||||
7 | # TODO : and neither does this
|
||||
8 | # foo # TODO: this doesn't either
|
||||
| ^^^^ TD002
|
||||
|
|
||||
TD002.py:14:9: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`
|
||||
|
|
||||
12 | # FIXME: neither does this
|
||||
13 | # TODO : and neither does this
|
||||
14 | # foo # TODO: this doesn't either
|
||||
| ^^^^ TD002
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(clippy::useless_format)]
|
||||
pub mod airflow;
|
||||
pub mod copyright;
|
||||
pub mod eradicate;
|
||||
pub mod flake8_2020;
|
||||
pub mod flake8_annotations;
|
||||
@@ -12,6 +11,7 @@ pub mod flake8_bugbear;
|
||||
pub mod flake8_builtins;
|
||||
pub mod flake8_commas;
|
||||
pub mod flake8_comprehensions;
|
||||
pub mod flake8_copyright;
|
||||
pub mod flake8_datetimez;
|
||||
pub mod flake8_debugger;
|
||||
pub mod flake8_django;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::registry::AsRule;
|
||||
/// ## Why is this bad?
|
||||
/// NumPy's `np.int` has long been an alias of the builtin `int`. The same
|
||||
/// goes for `np.float`, `np.bool`, and others. These aliases exist
|
||||
/// primarily primarily for historic reasons, and have been a cause of
|
||||
/// primarily for historic reasons, and have been a cause of
|
||||
/// frequent confusion for newcomers.
|
||||
///
|
||||
/// These aliases were been deprecated in 1.20, and removed in 1.24.
|
||||
|
||||
@@ -40,6 +40,5 @@ pub(super) fn convert_inplace_argument_to_assignment(
|
||||
false,
|
||||
)
|
||||
.ok()?;
|
||||
#[allow(deprecated)]
|
||||
Some(Fix::unspecified_edits(insert_assignment, [remove_argument]))
|
||||
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
|
||||
}
|
||||
|
||||
@@ -18,29 +18,29 @@ N805.py:12:30: N805 First argument of a method should be named `self`
|
||||
13 | pass
|
||||
|
|
||||
|
||||
N805.py:27:15: N805 First argument of a method should be named `self`
|
||||
|
|
||||
26 | @pydantic.validator
|
||||
27 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
28 | pass
|
||||
|
|
||||
|
||||
N805.py:31:15: N805 First argument of a method should be named `self`
|
||||
|
|
||||
30 | @pydantic.validator("my_field")
|
||||
30 | @pydantic.validator
|
||||
31 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
32 | pass
|
||||
|
|
||||
|
||||
N805.py:60:29: N805 First argument of a method should be named `self`
|
||||
N805.py:35:15: N805 First argument of a method should be named `self`
|
||||
|
|
||||
58 | pass
|
||||
59 |
|
||||
60 | def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
34 | @pydantic.validator("my_field")
|
||||
35 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
36 | pass
|
||||
|
|
||||
|
||||
N805.py:64:29: N805 First argument of a method should be named `self`
|
||||
|
|
||||
62 | pass
|
||||
63 |
|
||||
64 | def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
| ^^^^ N805
|
||||
61 | pass
|
||||
65 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ N805.py:12:30: N805 First argument of a method should be named `self`
|
||||
13 | pass
|
||||
|
|
||||
|
||||
N805.py:60:29: N805 First argument of a method should be named `self`
|
||||
N805.py:64:29: N805 First argument of a method should be named `self`
|
||||
|
|
||||
58 | pass
|
||||
59 |
|
||||
60 | def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
62 | pass
|
||||
63 |
|
||||
64 | def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
| ^^^^ N805
|
||||
61 | pass
|
||||
65 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ N805.py:34:18: N805 First argument of a method should be named `self`
|
||||
35 | pass
|
||||
|
|
||||
|
||||
N805.py:41:18: N805 First argument of a method should be named `self`
|
||||
N805.py:58:18: N805 First argument of a method should be named `self`
|
||||
|
|
||||
39 | pass
|
||||
40 |
|
||||
41 | def stillBad(this, blah, /, self, something: str):
|
||||
56 | pass
|
||||
57 |
|
||||
58 | def stillBad(this, blah, /, self, something: str):
|
||||
| ^^^^ N805
|
||||
42 | pass
|
||||
59 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ mod tests {
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::UnnecessaryListCast, Path::new("PERF101.py"))]
|
||||
#[test_case(Rule::IncorrectDictIterator, Path::new("PERF102.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub(crate) use incorrect_dict_iterator::{incorrect_dict_iterator, IncorrectDictIterator};
|
||||
pub(crate) use incorrect_dict_iterator::*;
|
||||
pub(crate) use unnecessary_list_cast::*;
|
||||
|
||||
mod incorrect_dict_iterator;
|
||||
mod unnecessary_list_cast;
|
||||
|
||||
129
crates/ruff/src/rules/perflint/rules/unnecessary_list_cast.rs
Normal file
129
crates/ruff/src/rules/perflint/rules/unnecessary_list_cast.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustpython_parser::ast::{self, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::prelude::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for explicit casts to `list` on for-loop iterables.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using a `list()` call to eagerly iterate over an already-iterable type
|
||||
/// (like a tuple, list, or set) is inefficient, as it forces Python to create
|
||||
/// a new list unnecessarily.
|
||||
///
|
||||
/// Removing the `list()` call will not change the behavior of the code, but
|
||||
/// may improve performance.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// items = (1, 2, 3)
|
||||
/// for i in list(items):
|
||||
/// print(i)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// items = (1, 2, 3)
|
||||
/// for i in items:
|
||||
/// print(i)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UnnecessaryListCast;
|
||||
|
||||
impl AlwaysAutofixableViolation for UnnecessaryListCast {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Do not cast an iterable to `list` before iterating over it")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
format!("Remove `list()` cast")
|
||||
}
|
||||
}
|
||||
|
||||
/// PERF101
|
||||
pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
|
||||
let Expr::Call(ast::ExprCall{ func, args, range: list_range, ..}) = iter else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else{
|
||||
return;
|
||||
};
|
||||
|
||||
if !(id == "list" && checker.semantic().is_builtin("list")) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &args[0] {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
range: iterable_range,
|
||||
..
|
||||
})
|
||||
| Expr::List(ast::ExprList {
|
||||
range: iterable_range,
|
||||
..
|
||||
})
|
||||
| Expr::Set(ast::ExprSet {
|
||||
range: iterable_range,
|
||||
..
|
||||
}) => {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
Expr::Name(ast::ExprName {
|
||||
id,
|
||||
range: iterable_range,
|
||||
..
|
||||
}) => {
|
||||
let scope = checker.semantic().scope();
|
||||
if let Some(binding_id) = scope.get(id) {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
|
||||
if let Some(parent_id) = binding.source {
|
||||
let parent = checker.semantic().stmts[parent_id];
|
||||
if let Stmt::Assign(ast::StmtAssign { value, .. })
|
||||
| Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value), ..
|
||||
})
|
||||
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
|
||||
{
|
||||
if matches!(
|
||||
value.as_ref(),
|
||||
Expr::Tuple(_) | Expr::List(_) | Expr::Set(_)
|
||||
) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(UnnecessaryListCast, *list_range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove a `list` cast from an expression.
|
||||
fn remove_cast(list_range: TextRange, iterable_range: TextRange) -> Fix {
|
||||
Fix::automatic_edits(
|
||||
Edit::deletion(list_range.start(), iterable_range.start()),
|
||||
[Edit::deletion(iterable_range.end(), list_range.end())],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/perflint/mod.rs
|
||||
---
|
||||
PERF101.py:7:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
5 | foo_int = 123
|
||||
6 |
|
||||
7 | for i in list(foo_tuple): # PERF101
|
||||
| ^^^^^^^^^^^^^^^ PERF101
|
||||
8 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
4 4 | foo_dict = {1: 2, 3: 4}
|
||||
5 5 | foo_int = 123
|
||||
6 6 |
|
||||
7 |-for i in list(foo_tuple): # PERF101
|
||||
7 |+for i in foo_tuple: # PERF101
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | for i in list(foo_list): # PERF101
|
||||
|
||||
PERF101.py:10:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
8 | pass
|
||||
9 |
|
||||
10 | for i in list(foo_list): # PERF101
|
||||
| ^^^^^^^^^^^^^^ PERF101
|
||||
11 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
7 7 | for i in list(foo_tuple): # PERF101
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 |-for i in list(foo_list): # PERF101
|
||||
10 |+for i in foo_list: # PERF101
|
||||
11 11 | pass
|
||||
12 12 |
|
||||
13 13 | for i in list(foo_set): # PERF101
|
||||
|
||||
PERF101.py:13:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
11 | pass
|
||||
12 |
|
||||
13 | for i in list(foo_set): # PERF101
|
||||
| ^^^^^^^^^^^^^ PERF101
|
||||
14 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
10 10 | for i in list(foo_list): # PERF101
|
||||
11 11 | pass
|
||||
12 12 |
|
||||
13 |-for i in list(foo_set): # PERF101
|
||||
13 |+for i in foo_set: # PERF101
|
||||
14 14 | pass
|
||||
15 15 |
|
||||
16 16 | for i in list((1, 2, 3)): # PERF101
|
||||
|
||||
PERF101.py:16:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
14 | pass
|
||||
15 |
|
||||
16 | for i in list((1, 2, 3)): # PERF101
|
||||
| ^^^^^^^^^^^^^^^ PERF101
|
||||
17 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
13 13 | for i in list(foo_set): # PERF101
|
||||
14 14 | pass
|
||||
15 15 |
|
||||
16 |-for i in list((1, 2, 3)): # PERF101
|
||||
16 |+for i in (1, 2, 3): # PERF101
|
||||
17 17 | pass
|
||||
18 18 |
|
||||
19 19 | for i in list([1, 2, 3]): # PERF101
|
||||
|
||||
PERF101.py:19:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
17 | pass
|
||||
18 |
|
||||
19 | for i in list([1, 2, 3]): # PERF101
|
||||
| ^^^^^^^^^^^^^^^ PERF101
|
||||
20 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
16 16 | for i in list((1, 2, 3)): # PERF101
|
||||
17 17 | pass
|
||||
18 18 |
|
||||
19 |-for i in list([1, 2, 3]): # PERF101
|
||||
19 |+for i in [1, 2, 3]: # PERF101
|
||||
20 20 | pass
|
||||
21 21 |
|
||||
22 22 | for i in list({1, 2, 3}): # PERF101
|
||||
|
||||
PERF101.py:22:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
20 | pass
|
||||
21 |
|
||||
22 | for i in list({1, 2, 3}): # PERF101
|
||||
| ^^^^^^^^^^^^^^^ PERF101
|
||||
23 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
19 19 | for i in list([1, 2, 3]): # PERF101
|
||||
20 20 | pass
|
||||
21 21 |
|
||||
22 |-for i in list({1, 2, 3}): # PERF101
|
||||
22 |+for i in {1, 2, 3}: # PERF101
|
||||
23 23 | pass
|
||||
24 24 |
|
||||
25 25 | for i in list(
|
||||
|
||||
PERF101.py:25:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
23 | pass
|
||||
24 |
|
||||
25 | for i in list(
|
||||
| __________^
|
||||
26 | | {
|
||||
27 | | 1,
|
||||
28 | | 2,
|
||||
29 | | 3,
|
||||
30 | | }
|
||||
31 | | ):
|
||||
| |_^ PERF101
|
||||
32 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
22 22 | for i in list({1, 2, 3}): # PERF101
|
||||
23 23 | pass
|
||||
24 24 |
|
||||
25 |-for i in list(
|
||||
26 |- {
|
||||
25 |+for i in {
|
||||
27 26 | 1,
|
||||
28 27 | 2,
|
||||
29 28 | 3,
|
||||
30 |- }
|
||||
31 |-):
|
||||
29 |+ }:
|
||||
32 30 | pass
|
||||
33 31 |
|
||||
34 32 | for i in list( # Comment
|
||||
|
||||
PERF101.py:34:10: PERF101 [*] Do not cast an iterable to `list` before iterating over it
|
||||
|
|
||||
32 | pass
|
||||
33 |
|
||||
34 | for i in list( # Comment
|
||||
| __________^
|
||||
35 | | {1, 2, 3}
|
||||
36 | | ): # PERF101
|
||||
| |_^ PERF101
|
||||
37 | pass
|
||||
|
|
||||
= help: Remove `list()` cast
|
||||
|
||||
ℹ Fix
|
||||
31 31 | ):
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 |-for i in list( # Comment
|
||||
35 |- {1, 2, 3}
|
||||
36 |-): # PERF101
|
||||
34 |+for i in {1, 2, 3}: # PERF101
|
||||
37 35 | pass
|
||||
38 36 |
|
||||
39 37 | for i in list(foo_dict): # Ok
|
||||
|
||||
|
||||
@@ -10,7 +10,21 @@ use crate::settings::Settings;
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// For flowing long blocks of text (docstrings or comments), overlong lines
|
||||
/// can hurt readability.
|
||||
/// can hurt readability. [PEP 8], for example, recommends that such lines be
|
||||
/// limited to 72 characters.
|
||||
///
|
||||
/// In the context of this rule, a "doc line" is defined as a line consisting
|
||||
/// of either a standalone comment or a standalone string, like a docstring.
|
||||
///
|
||||
/// In the interest of pragmatism, this rule makes a few exceptions when
|
||||
/// determining whether a line is overlong. Namely, it ignores lines that
|
||||
/// consist of a single "word" (i.e., without any whitespace between its
|
||||
/// characters), and lines that end with a URL (as long as the URL starts
|
||||
/// before the line-length threshold).
|
||||
///
|
||||
/// If `pycodestyle.ignore_overlong_task_comments` is `true`, this rule will
|
||||
/// also ignore comments that start with any of the specified `task-tags`
|
||||
/// (e.g., `# TODO:`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -26,6 +40,13 @@ use crate::settings::Settings;
|
||||
/// Duis auctor purus ut ex fermentum, at maximus est hendrerit.
|
||||
/// """
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Options
|
||||
/// - `task-tags`
|
||||
/// - `pycodestyle.ignore-overlong-task-comments`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
|
||||
#[violation]
|
||||
pub struct DocLineTooLong(pub usize, pub usize);
|
||||
|
||||
|
||||
@@ -108,8 +108,7 @@ pub(crate) fn invalid_escape_sequence(
|
||||
let range = TextRange::at(location, next_char.text_len() + TextSize::from(1));
|
||||
let mut diagnostic = Diagnostic::new(InvalidEscapeSequence(*next_char), range);
|
||||
if autofix {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::insertion(
|
||||
diagnostic.set_fix(Fix::automatic(Edit::insertion(
|
||||
r"\".to_string(),
|
||||
range.start() + TextSize::from(1),
|
||||
)));
|
||||
|
||||
@@ -9,7 +9,18 @@ use crate::settings::Settings;
|
||||
/// Checks for lines that exceed the specified maximum character length.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Overlong lines can hurt readability.
|
||||
/// Overlong lines can hurt readability. [PEP 8], for example, recommends
|
||||
/// limiting lines to 79 characters.
|
||||
///
|
||||
/// In the interest of pragmatism, this rule makes a few exceptions when
|
||||
/// determining whether a line is overlong. Namely, it ignores lines that
|
||||
/// consist of a single "word" (i.e., without any whitespace between its
|
||||
/// characters), and lines that end with a URL (as long as the URL starts
|
||||
/// before the line-length threshold).
|
||||
///
|
||||
/// If `pycodestyle.ignore_overlong_task_comments` is `true`, this rule will
|
||||
/// also ignore comments that start with any of the specified `task-tags`
|
||||
/// (e.g., `# TODO:`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -26,6 +37,9 @@ use crate::settings::Settings;
|
||||
///
|
||||
/// ## Options
|
||||
/// - `task-tags`
|
||||
/// - `pycodestyle.ignore-overlong-task-comments`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#maximum-line-length
|
||||
#[violation]
|
||||
pub struct LineTooLong(pub usize, pub usize);
|
||||
|
||||
|
||||
@@ -92,8 +92,7 @@ pub(crate) fn missing_whitespace(
|
||||
let mut diagnostic = Diagnostic::new(kind, token.range());
|
||||
|
||||
if autofix {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::insertion(
|
||||
diagnostic.set_fix(Fix::automatic(Edit::insertion(
|
||||
" ".to_string(),
|
||||
token.end(),
|
||||
)));
|
||||
|
||||
@@ -65,8 +65,7 @@ pub(crate) fn whitespace_before_parameters(
|
||||
let mut diagnostic = Diagnostic::new(kind, TextRange::new(start, end));
|
||||
|
||||
if autofix {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::deletion(start, end)));
|
||||
diagnostic.set_fix(Fix::automatic(Edit::deletion(start, end)));
|
||||
}
|
||||
context.push_diagnostic(diagnostic);
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ pub(crate) fn no_newline_at_end_of_file(
|
||||
|
||||
let mut diagnostic = Diagnostic::new(MissingNewlineAtEndOfFile, range);
|
||||
if autofix {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::insertion(
|
||||
diagnostic.set_fix(Fix::automatic(Edit::insertion(
|
||||
stylist.line_ending().to_string(),
|
||||
range.start(),
|
||||
)));
|
||||
|
||||
@@ -11,7 +11,7 @@ E21.py:2:5: E211 [*] Whitespace before '('
|
||||
|
|
||||
= help: Removed whitespace before '('
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: E211
|
||||
2 |-spam (1)
|
||||
2 |+spam(1)
|
||||
@@ -30,7 +30,7 @@ E21.py:4:5: E211 [*] Whitespace before '['
|
||||
|
|
||||
= help: Removed whitespace before '['
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: E211
|
||||
2 2 | spam (1)
|
||||
3 3 | #: E211 E211
|
||||
@@ -51,7 +51,7 @@ E21.py:4:20: E211 [*] Whitespace before '['
|
||||
|
|
||||
= help: Removed whitespace before '['
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: E211
|
||||
2 2 | spam (1)
|
||||
3 3 | #: E211 E211
|
||||
@@ -72,7 +72,7 @@ E21.py:6:12: E211 [*] Whitespace before '['
|
||||
|
|
||||
= help: Removed whitespace before '['
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 | #: E211 E211
|
||||
4 4 | dict ['key'] = list [index]
|
||||
5 5 | #: E211
|
||||
|
||||
@@ -11,7 +11,7 @@ E23.py:2:7: E231 [*] Missing whitespace after ','
|
||||
|
|
||||
= help: Added missing whitespace after ','
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: E231
|
||||
2 |-a = (1,2)
|
||||
2 |+a = (1, 2)
|
||||
@@ -30,7 +30,7 @@ E23.py:4:5: E231 [*] Missing whitespace after ','
|
||||
|
|
||||
= help: Added missing whitespace after ','
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: E231
|
||||
2 2 | a = (1,2)
|
||||
3 3 | #: E231
|
||||
@@ -51,7 +51,7 @@ E23.py:6:10: E231 [*] Missing whitespace after ':'
|
||||
|
|
||||
= help: Added missing whitespace after ':'
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 | #: E231
|
||||
4 4 | a[b1,:]
|
||||
5 5 | #: E231
|
||||
@@ -71,7 +71,7 @@ E23.py:19:10: E231 [*] Missing whitespace after ','
|
||||
|
|
||||
= help: Added missing whitespace after ','
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | def foo() -> None:
|
||||
18 18 | #: E231
|
||||
@@ -91,7 +91,7 @@ E23.py:29:20: E231 [*] Missing whitespace after ':'
|
||||
|
|
||||
= help: Added missing whitespace after ':'
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
26 26 | #: E231:2:20
|
||||
27 27 | mdtypes_template = {
|
||||
28 28 | 'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
|
||||
|
||||
@@ -9,7 +9,7 @@ W292_0.py:2:9: W292 [*] No newline at end of file
|
||||
|
|
||||
= help: Add trailing newline
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | def fn() -> None:
|
||||
2 |- pass
|
||||
2 |+ pass
|
||||
|
||||
@@ -11,7 +11,7 @@ W605_0.py:2:10: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: W605:1:10
|
||||
2 |-regex = '\.png$'
|
||||
2 |+regex = '\\.png$'
|
||||
@@ -29,7 +29,7 @@ W605_0.py:6:1: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 | #: W605:2:1
|
||||
5 5 | regex = '''
|
||||
@@ -49,7 +49,7 @@ W605_0.py:11:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | #: W605:2:6
|
||||
10 10 | f(
|
||||
@@ -70,7 +70,7 @@ W605_0.py:18:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
15 15 | """
|
||||
16 16 | multi-line
|
||||
17 17 | literal
|
||||
|
||||
@@ -11,7 +11,7 @@ W605_1.py:2:10: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 1 | #: W605:1:10
|
||||
2 |-regex = '\.png$'
|
||||
2 |+regex = '\\.png$'
|
||||
@@ -29,7 +29,7 @@ W605_1.py:6:1: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 | #: W605:2:1
|
||||
5 5 | regex = '''
|
||||
@@ -49,7 +49,7 @@ W605_1.py:11:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | #: W605:2:6
|
||||
10 10 | f(
|
||||
@@ -70,7 +70,7 @@ W605_1.py:18:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
15 15 | """
|
||||
16 16 | multi-line
|
||||
17 17 | literal
|
||||
|
||||
@@ -8,7 +8,7 @@ W292_4.py:1:2: W292 [*] No newline at end of file
|
||||
|
|
||||
= help: Add trailing newline
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
1 |-
|
||||
1 |+
|
||||
|
||||
|
||||
@@ -353,9 +353,59 @@ mod tests {
|
||||
except Exception as x:
|
||||
pass
|
||||
|
||||
# No error here, though it should arguably be an F821 error. `x` will
|
||||
# be unbound after the `except` block (assuming an exception is raised
|
||||
# and caught).
|
||||
print(x)
|
||||
"#,
|
||||
"print_after_shadowing_except"
|
||||
"print_in_body_after_shadowing_except"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as x:
|
||||
pass
|
||||
except ImportError as x:
|
||||
pass
|
||||
|
||||
# No error here, though it should arguably be an F821 error. `x` will
|
||||
# be unbound after the `except` block (assuming an exception is raised
|
||||
# and caught).
|
||||
print(x)
|
||||
"#,
|
||||
"print_in_body_after_double_shadowing_except"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
def f():
|
||||
try:
|
||||
x = 3
|
||||
except ImportError as x:
|
||||
print(x)
|
||||
else:
|
||||
print(x)
|
||||
"#,
|
||||
"print_in_try_else_after_shadowing_except"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
def f():
|
||||
list = [1, 2, 3]
|
||||
|
||||
for e in list:
|
||||
if e % 2 == 0:
|
||||
try:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(e)
|
||||
else:
|
||||
print(e)
|
||||
"#,
|
||||
"print_in_if_else_after_shadowing_except"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
@@ -366,6 +416,79 @@ mod tests {
|
||||
"#,
|
||||
"double_del"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
# This should resolve to the `x` in `x = 1`.
|
||||
print(x)
|
||||
"#,
|
||||
"load_after_unbind_from_module_scope"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
# This should resolve to the `x` in `x = 1`.
|
||||
print(x)
|
||||
"#,
|
||||
"load_after_multiple_unbinds_from_module_scope"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
def g():
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
# This should resolve to the `x` in `x = 1`.
|
||||
print(x)
|
||||
"#,
|
||||
"load_after_unbind_from_nested_module_scope"
|
||||
)]
|
||||
#[test_case(
|
||||
r#"
|
||||
class C:
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
except ValueError as x:
|
||||
pass
|
||||
|
||||
# This should raise an F821 error, rather than resolving to the
|
||||
# `x` in `x = 1`.
|
||||
print(x)
|
||||
"#,
|
||||
"load_after_unbind_from_class_scope"
|
||||
)]
|
||||
fn contents(contents: &str, snapshot: &str) {
|
||||
let diagnostics = test_snippet(contents, &Settings::for_rules(&Linter::Pyflakes));
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
|
||||
@@ -79,24 +79,6 @@ fn find_useless_f_strings<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
fn unescape_f_string(content: &str) -> String {
|
||||
content.replace("{{", "{").replace("}}", "}")
|
||||
}
|
||||
|
||||
fn fix_f_string_missing_placeholders(
|
||||
prefix_range: TextRange,
|
||||
tok_range: TextRange,
|
||||
checker: &mut Checker,
|
||||
) -> Fix {
|
||||
let content = &checker.locator.contents()[TextRange::new(prefix_range.end(), tok_range.end())];
|
||||
#[allow(deprecated)]
|
||||
Fix::unspecified(Edit::replacement(
|
||||
unescape_f_string(content),
|
||||
prefix_range.start(),
|
||||
tok_range.end(),
|
||||
))
|
||||
}
|
||||
|
||||
/// F541
|
||||
pub(crate) fn f_string_missing_placeholders(expr: &Expr, values: &[Expr], checker: &mut Checker) {
|
||||
if !values
|
||||
@@ -106,13 +88,51 @@ pub(crate) fn f_string_missing_placeholders(expr: &Expr, values: &[Expr], checke
|
||||
for (prefix_range, tok_range) in find_useless_f_strings(expr, checker.locator) {
|
||||
let mut diagnostic = Diagnostic::new(FStringMissingPlaceholders, tok_range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(fix_f_string_missing_placeholders(
|
||||
diagnostic.set_fix(convert_f_string_to_regular_string(
|
||||
prefix_range,
|
||||
tok_range,
|
||||
checker,
|
||||
checker.locator,
|
||||
));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unescape an f-string body by replacing `{{` with `{` and `}}` with `}`.
|
||||
///
|
||||
/// In Python, curly-brace literals within f-strings must be escaped by doubling the braces.
|
||||
/// When rewriting an f-string to a regular string, we need to unescape any curly-brace literals.
|
||||
/// For example, given `{{Hello, world!}}`, return `{Hello, world!}`.
|
||||
fn unescape_f_string(content: &str) -> String {
|
||||
content.replace("{{", "{").replace("}}", "}")
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to rewrite an f-string as a regular string.
|
||||
fn convert_f_string_to_regular_string(
|
||||
prefix_range: TextRange,
|
||||
tok_range: TextRange,
|
||||
locator: &Locator,
|
||||
) -> Fix {
|
||||
// Extract the f-string body.
|
||||
let mut content =
|
||||
unescape_f_string(locator.slice(TextRange::new(prefix_range.end(), tok_range.end())));
|
||||
|
||||
// If the preceding character is equivalent to the quote character, insert a space to avoid a
|
||||
// syntax error. For example, when removing the `f` prefix in `""f""`, rewrite to `"" ""`
|
||||
// instead of `""""`.
|
||||
if locator
|
||||
.slice(TextRange::up_to(prefix_range.start()))
|
||||
.chars()
|
||||
.last()
|
||||
.map_or(false, |char| content.starts_with(char))
|
||||
{
|
||||
content.insert(0, ' ');
|
||||
}
|
||||
|
||||
Fix::automatic(Edit::replacement(
|
||||
content,
|
||||
prefix_range.start(),
|
||||
tok_range.end(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ F541.py:6:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
3 3 | b = f"ghi{'jkl'}"
|
||||
4 4 |
|
||||
5 5 | # Errors
|
||||
@@ -32,7 +32,7 @@ F541.py:7:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
4 4 |
|
||||
5 5 | # Errors
|
||||
6 6 | c = f"def"
|
||||
@@ -53,7 +53,7 @@ F541.py:9:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
6 6 | c = f"def"
|
||||
7 7 | d = f"def" + "ghi"
|
||||
8 8 | e = (
|
||||
@@ -74,7 +74,7 @@ F541.py:13:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
10 10 | "ghi"
|
||||
11 11 | )
|
||||
12 12 | f = (
|
||||
@@ -95,7 +95,7 @@ F541.py:14:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
11 11 | )
|
||||
12 12 | f = (
|
||||
13 13 | f"a"
|
||||
@@ -116,7 +116,7 @@ F541.py:16:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
13 13 | f"a"
|
||||
14 14 | F"b"
|
||||
15 15 | "c"
|
||||
@@ -137,7 +137,7 @@ F541.py:17:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
14 14 | F"b"
|
||||
15 15 | "c"
|
||||
16 16 | rf"d"
|
||||
@@ -158,7 +158,7 @@ F541.py:19:5: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
16 16 | rf"d"
|
||||
17 17 | fr"e"
|
||||
18 18 | )
|
||||
@@ -178,7 +178,7 @@ F541.py:25:13: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
22 22 | g = f"ghi{123:{45}}"
|
||||
23 23 |
|
||||
24 24 | # Error
|
||||
@@ -198,7 +198,7 @@ F541.py:34:7: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
31 31 | f"{f'{v:0.2f}'}"
|
||||
32 32 |
|
||||
33 33 | # Errors
|
||||
@@ -219,7 +219,7 @@ F541.py:35:4: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
32 32 |
|
||||
33 33 | # Errors
|
||||
34 34 | f"{v:{f'0.2f'}}"
|
||||
@@ -240,7 +240,7 @@ F541.py:36:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
33 33 | # Errors
|
||||
34 34 | f"{v:{f'0.2f'}}"
|
||||
35 35 | f"{f''}"
|
||||
@@ -261,7 +261,7 @@ F541.py:37:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
34 34 | f"{v:{f'0.2f'}}"
|
||||
35 35 | f"{f''}"
|
||||
36 36 | f"{{test}}"
|
||||
@@ -269,7 +269,7 @@ F541.py:37:1: F541 [*] f-string without any placeholders
|
||||
37 |+'{ 40 }'
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 |
|
||||
40 40 | ""f""
|
||||
|
||||
F541.py:38:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
@@ -278,18 +278,19 @@ F541.py:38:1: F541 [*] f-string without any placeholders
|
||||
38 | f"{{a {{x}}"
|
||||
| ^^^^^^^^^^^^ F541
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
35 35 | f"{f''}"
|
||||
36 36 | f"{{test}}"
|
||||
37 37 | f'{{ 40 }}'
|
||||
38 |-f"{{a {{x}}"
|
||||
38 |+"{a {x}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 |
|
||||
41 41 | # To be fixed
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
|
||||
F541.py:39:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
@@ -297,19 +298,81 @@ F541.py:39:1: F541 [*] f-string without any placeholders
|
||||
38 | f"{{a {{x}}"
|
||||
39 | f"{{{{x}}}}"
|
||||
| ^^^^^^^^^^^^ F541
|
||||
40 |
|
||||
41 | # To be fixed
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
36 36 | f"{{test}}"
|
||||
37 37 | f'{{ 40 }}'
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 |-f"{{{{x}}}}"
|
||||
39 |+"{{x}}"
|
||||
40 40 |
|
||||
41 41 | # To be fixed
|
||||
42 42 | # Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
42 42 | (""f""r"")
|
||||
|
||||
F541.py:40:3: F541 [*] f-string without any placeholders
|
||||
|
|
||||
38 | f"{{a {{x}}"
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
| ^^^ F541
|
||||
41 | ''f""
|
||||
42 | (""f""r"")
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
37 37 | f'{{ 40 }}'
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 |-""f""
|
||||
40 |+"" ""
|
||||
41 41 | ''f""
|
||||
42 42 | (""f""r"")
|
||||
43 43 |
|
||||
|
||||
F541.py:41:3: F541 [*] f-string without any placeholders
|
||||
|
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
| ^^^ F541
|
||||
42 | (""f""r"")
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 | ""f""
|
||||
41 |-''f""
|
||||
41 |+''""
|
||||
42 42 | (""f""r"")
|
||||
43 43 |
|
||||
44 44 | # To be fixed
|
||||
|
||||
F541.py:42:4: F541 [*] f-string without any placeholders
|
||||
|
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
42 | (""f""r"")
|
||||
| ^^^ F541
|
||||
43 |
|
||||
44 | # To be fixed
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
42 |-(""f""r"")
|
||||
42 |+("" ""r"")
|
||||
43 43 |
|
||||
44 44 | # To be fixed
|
||||
45 45 | # Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
5 | try:
|
||||
6 | pass
|
||||
7 | except ValueError as x:
|
||||
| ^ F841
|
||||
8 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
4 4 | def f():
|
||||
5 5 | try:
|
||||
6 6 | pass
|
||||
7 |- except ValueError as x:
|
||||
7 |+ except ValueError:
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | try:
|
||||
|
||||
<filename>:12:26: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
10 | try:
|
||||
11 | pass
|
||||
12 | except ValueError as x:
|
||||
| ^ F841
|
||||
13 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
9 9 |
|
||||
10 10 | try:
|
||||
11 11 | pass
|
||||
12 |- except ValueError as x:
|
||||
12 |+ except ValueError:
|
||||
13 13 | pass
|
||||
14 14 |
|
||||
15 15 | # This should resolve to the `x` in `x = 1`.
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
<filename>:8:30: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
6 | try:
|
||||
7 | pass
|
||||
8 | except ValueError as x:
|
||||
| ^ F841
|
||||
9 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
5 5 | def f():
|
||||
6 6 | try:
|
||||
7 7 | pass
|
||||
8 |- except ValueError as x:
|
||||
8 |+ except ValueError:
|
||||
9 9 | pass
|
||||
10 10 |
|
||||
11 11 | # This should raise an F821 error, rather than resolving to the
|
||||
|
||||
<filename>:13:15: F821 Undefined name `x`
|
||||
|
|
||||
11 | # This should raise an F821 error, rather than resolving to the
|
||||
12 | # `x` in `x = 1`.
|
||||
13 | print(x)
|
||||
| ^ F821
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
5 | try:
|
||||
6 | pass
|
||||
7 | except ValueError as x:
|
||||
| ^ F841
|
||||
8 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
4 4 | def f():
|
||||
5 5 | try:
|
||||
6 6 | pass
|
||||
7 |- except ValueError as x:
|
||||
7 |+ except ValueError:
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | # This should resolve to the `x` in `x = 1`.
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
5 | try:
|
||||
6 | pass
|
||||
7 | except ValueError as x:
|
||||
| ^ F841
|
||||
8 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
4 4 | def f():
|
||||
5 5 | try:
|
||||
6 6 | pass
|
||||
7 |- except ValueError as x:
|
||||
7 |+ except ValueError:
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | def g():
|
||||
|
||||
<filename>:13:30: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
11 | try:
|
||||
12 | pass
|
||||
13 | except ValueError as x:
|
||||
| ^ F841
|
||||
14 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
10 10 | def g():
|
||||
11 11 | try:
|
||||
12 12 | pass
|
||||
13 |- except ValueError as x:
|
||||
13 |+ except ValueError:
|
||||
14 14 | pass
|
||||
15 15 |
|
||||
16 16 | # This should resolve to the `x` in `x = 1`.
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
<filename>:7:26: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
5 | try:
|
||||
6 | 1 / 0
|
||||
7 | except ValueError as x:
|
||||
| ^ F841
|
||||
8 | pass
|
||||
9 | except ImportError as x:
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
4 4 |
|
||||
5 5 | try:
|
||||
6 6 | 1 / 0
|
||||
7 |- except ValueError as x:
|
||||
7 |+ except ValueError:
|
||||
8 8 | pass
|
||||
9 9 | except ImportError as x:
|
||||
10 10 | pass
|
||||
|
||||
<filename>:9:27: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
7 | except ValueError as x:
|
||||
8 | pass
|
||||
9 | except ImportError as x:
|
||||
| ^ F841
|
||||
10 | pass
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Fix
|
||||
6 6 | 1 / 0
|
||||
7 7 | except ValueError as x:
|
||||
8 8 | pass
|
||||
9 |- except ImportError as x:
|
||||
9 |+ except ImportError:
|
||||
10 10 | pass
|
||||
11 11 |
|
||||
12 12 | # No error here, though it should arguably be an F821 error. `x` will
|
||||
|
||||
|
||||
@@ -19,14 +19,6 @@ source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
7 |+ except Exception:
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | print(x)
|
||||
|
||||
<filename>:10:11: F821 Undefined name `x`
|
||||
|
|
||||
8 | pass
|
||||
9 |
|
||||
10 | print(x)
|
||||
| ^ F821
|
||||
|
|
||||
10 10 | # No error here, though it should arguably be an F821 error. `x` will
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of the builtin `eval()` function.
|
||||
/// Checks for uses of the builtin `eval()` function.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `eval()` function is insecure as it enables arbitrary code execution.
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of `await` outside of `async` functions.
|
||||
/// Checks for uses of `await` outside of `async` functions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `await` outside of an `async` function is a syntax error.
|
||||
|
||||
@@ -7,49 +7,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum EmptyStringCmpOp {
|
||||
Is,
|
||||
IsNot,
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
impl TryFrom<&CmpOp> for EmptyStringCmpOp {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &CmpOp) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
CmpOp::Is => Ok(Self::Is),
|
||||
CmpOp::IsNot => Ok(Self::IsNot),
|
||||
CmpOp::Eq => Ok(Self::Eq),
|
||||
CmpOp::NotEq => Ok(Self::NotEq),
|
||||
_ => bail!("{value:?} cannot be converted to EmptyStringCmpOp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmptyStringCmpOp {
|
||||
pub(crate) fn into_unary(self) -> &'static str {
|
||||
match self {
|
||||
Self::Is | Self::Eq => "not ",
|
||||
Self::IsNot | Self::NotEq => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EmptyStringCmpOp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let repr = match self {
|
||||
Self::Is => "is",
|
||||
Self::IsNot => "is not",
|
||||
Self::Eq => "==",
|
||||
Self::NotEq => "!=",
|
||||
};
|
||||
write!(f, "{repr}")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons to empty strings.
|
||||
///
|
||||
@@ -83,13 +40,15 @@ pub struct CompareToEmptyString {
|
||||
impl Violation for CompareToEmptyString {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"`{}` can be simplified to `{}` as an empty string is falsey",
|
||||
self.existing, self.replacement,
|
||||
)
|
||||
let CompareToEmptyString {
|
||||
existing,
|
||||
replacement,
|
||||
} = self;
|
||||
format!("`{existing}` can be simplified to `{replacement}` as an empty string is falsey",)
|
||||
}
|
||||
}
|
||||
|
||||
/// PLC1901
|
||||
pub(crate) fn compare_to_empty_string(
|
||||
checker: &mut Checker,
|
||||
left: &Expr,
|
||||
@@ -98,10 +57,12 @@ pub(crate) fn compare_to_empty_string(
|
||||
) {
|
||||
// Omit string comparison rules within subscripts. This is mostly commonly used within
|
||||
// DataFrame and np.ndarray indexing.
|
||||
for parent in checker.semantic().expr_ancestors() {
|
||||
if matches!(parent, Expr::Subscript(_)) {
|
||||
return;
|
||||
}
|
||||
if checker
|
||||
.semantic()
|
||||
.expr_ancestors()
|
||||
.any(|parent| parent.is_subscript_expr())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut first = true;
|
||||
@@ -153,3 +114,46 @@ pub(crate) fn compare_to_empty_string(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum EmptyStringCmpOp {
|
||||
Is,
|
||||
IsNot,
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
impl TryFrom<&CmpOp> for EmptyStringCmpOp {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &CmpOp) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
CmpOp::Is => Ok(Self::Is),
|
||||
CmpOp::IsNot => Ok(Self::IsNot),
|
||||
CmpOp::Eq => Ok(Self::Eq),
|
||||
CmpOp::NotEq => Ok(Self::NotEq),
|
||||
_ => bail!("{value:?} cannot be converted to EmptyStringCmpOp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmptyStringCmpOp {
|
||||
fn into_unary(self) -> &'static str {
|
||||
match self {
|
||||
Self::Is | Self::Eq => "not ",
|
||||
Self::IsNot | Self::NotEq => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EmptyStringCmpOp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let repr = match self {
|
||||
Self::Is => "is",
|
||||
Self::IsNot => "is not",
|
||||
Self::Eq => "==",
|
||||
Self::NotEq => "!=",
|
||||
};
|
||||
write!(f, "{repr}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +191,7 @@ pub(crate) fn invalid_string_characters(locator: &Locator, range: TextRange) ->
|
||||
let location = range.start() + TextSize::try_from(column).unwrap();
|
||||
let range = TextRange::at(location, c.text_len());
|
||||
|
||||
#[allow(deprecated)]
|
||||
diagnostics.push(Diagnostic::new(rule, range).with_fix(Fix::unspecified(
|
||||
diagnostics.push(Diagnostic::new(rule, range).with_fix(Fix::automatic(
|
||||
Edit::range_replacement(replacement.to_string(), range),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_python_ast::source_code::OneIndexed;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of names that are declared as `global` prior to the
|
||||
/// Checks for uses of names that are declared as `global` prior to the
|
||||
/// relevant `global` declaration.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usages of named expressions (e.g., `a := 42`) that can be
|
||||
/// Checks for uses of named expressions (e.g., `a := 42`) that can be
|
||||
/// replaced by regular assignment statements (e.g., `a = 42`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
|
||||
@@ -157,8 +157,7 @@ pub(crate) fn nested_min_max(
|
||||
keywords: keywords.to_owned(),
|
||||
range: TextRange::default(),
|
||||
});
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
checker.generator().expr(&flattened_expr),
|
||||
expr.range(),
|
||||
)));
|
||||
|
||||
@@ -82,8 +82,7 @@ pub(crate) fn sys_exit_alias(checker: &mut Checker, func: &Expr) {
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let reference_edit = Edit::range_replacement(binding, func.range());
|
||||
#[allow(deprecated)]
|
||||
Ok(Fix::unspecified_edits(import_edit, [reference_edit]))
|
||||
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -49,8 +49,7 @@ pub(crate) fn useless_import_alias(checker: &mut Checker, alias: &Alias) {
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UselessImportAlias, alias.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
asname.to_string(),
|
||||
alias.range(),
|
||||
)));
|
||||
|
||||
@@ -12,7 +12,7 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
12 12 | # (Pylint, "C0414") => Rule::UselessImportAlias,
|
||||
13 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
|
||||
14 14 | #foo = 'hi'
|
||||
|
||||
@@ -12,7 +12,7 @@ invalid_characters.py:21:12: PLE2512 [*] Invalid unescaped character SUB, use "\
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 | cr_ok = '\\r'
|
||||
20 20 |
|
||||
|
||||
@@ -12,7 +12,7 @@ invalid_characters.py:25:16: PLE2513 [*] Invalid unescaped character ESC, use "\
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
22 22 |
|
||||
23 23 | sub_ok = '\x1a'
|
||||
24 24 |
|
||||
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@ invalid_characters.py:34:13: PLE2515 [*] Invalid unescaped character zero-width-
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
31 31 |
|
||||
32 32 | nul_ok = '\0'
|
||||
33 33 |
|
||||
@@ -32,7 +32,7 @@ invalid_characters.py:38:36: PLE2515 [*] Invalid unescaped character zero-width-
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
35 35 |
|
||||
36 36 | zwsp_ok = '\u200b'
|
||||
37 37 |
|
||||
@@ -48,7 +48,7 @@ invalid_characters.py:39:60: PLE2515 [*] Invalid unescaped character zero-width-
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
36 36 | zwsp_ok = '\u200b'
|
||||
37 37 |
|
||||
38 38 | zwsp_after_multibyte_character = "ಫ"
|
||||
@@ -63,7 +63,7 @@ invalid_characters.py:39:61: PLE2515 [*] Invalid unescaped character zero-width-
|
||||
|
|
||||
= help: Replace with escape sequence
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Fix
|
||||
36 36 | zwsp_ok = '\u200b'
|
||||
37 37 |
|
||||
38 38 | zwsp_after_multibyte_character = "ಫ"
|
||||
|
||||
@@ -126,7 +126,7 @@ pub(crate) fn remove_import_members(contents: &str, members: &[&str]) -> String
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use crate::rules::pyupgrade::fixes::remove_import_members;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -43,6 +43,12 @@ enum Deprecation {
|
||||
/// Deprecated imports may be removed in future versions of Python, and
|
||||
/// should be replaced with their new equivalents.
|
||||
///
|
||||
/// Note that, in some cases, it may be preferable to continue importing
|
||||
/// members from `typing_extensions` even after they're added to the Python
|
||||
/// standard library, as `typing_extensions` can backport bugfixes and
|
||||
/// optimizations from later Python versions. This rule thus avoids flagging
|
||||
/// imports from `typing_extensions` in such cases.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from collections import Sequence
|
||||
@@ -139,10 +145,12 @@ const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
|
||||
"ContextManager",
|
||||
"Coroutine",
|
||||
"DefaultDict",
|
||||
"NewType",
|
||||
"TYPE_CHECKING",
|
||||
"Text",
|
||||
"Type",
|
||||
// Introduced in Python 3.5.2, but `typing_extensions` contains backported bugfixes and
|
||||
// optimizations,
|
||||
// "NewType",
|
||||
];
|
||||
|
||||
// Python 3.7+
|
||||
@@ -168,11 +176,13 @@ const MYPY_EXTENSIONS_TO_TYPING_38: &[&str] = &["TypedDict"];
|
||||
// Members of `typing_extensions` that were moved to `typing`.
|
||||
const TYPING_EXTENSIONS_TO_TYPING_38: &[&str] = &[
|
||||
"Final",
|
||||
"Literal",
|
||||
"OrderedDict",
|
||||
"Protocol",
|
||||
"SupportsIndex",
|
||||
"runtime_checkable",
|
||||
// Introduced in Python 3.8, but `typing_extensions` contains backported bugfixes and
|
||||
// optimizations.
|
||||
// "Literal",
|
||||
// "Protocol",
|
||||
// "SupportsIndex",
|
||||
];
|
||||
|
||||
// Python 3.9+
|
||||
@@ -243,6 +253,8 @@ const TYPING_TO_COLLECTIONS_ABC_310: &[&str] = &["Callable"];
|
||||
// Members of `typing_extensions` that were moved to `typing`.
|
||||
const TYPING_EXTENSIONS_TO_TYPING_310: &[&str] = &[
|
||||
"Concatenate",
|
||||
"Literal",
|
||||
"NewType",
|
||||
"ParamSpecArgs",
|
||||
"ParamSpecKwargs",
|
||||
"TypeAlias",
|
||||
@@ -258,23 +270,28 @@ const TYPING_EXTENSIONS_TO_TYPING_310: &[&str] = &[
|
||||
const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
|
||||
"Any",
|
||||
"LiteralString",
|
||||
"NamedTuple",
|
||||
"Never",
|
||||
"NotRequired",
|
||||
"Required",
|
||||
"Self",
|
||||
"TypedDict",
|
||||
"Unpack",
|
||||
"assert_never",
|
||||
"assert_type",
|
||||
"clear_overloads",
|
||||
"dataclass_transform",
|
||||
"final",
|
||||
"get_overloads",
|
||||
"overload",
|
||||
"reveal_type",
|
||||
];
|
||||
|
||||
// Python 3.12+
|
||||
|
||||
// Members of `typing_extensions` that were moved to `typing`.
|
||||
const TYPING_EXTENSIONS_TO_TYPING_312: &[&str] = &[
|
||||
// Introduced in Python 3.11, but `typing_extensions` backports the `frozen_default` argument,
|
||||
// which was introduced in Python 3.12.
|
||||
"dataclass_transform",
|
||||
];
|
||||
|
||||
struct ImportReplacer<'a> {
|
||||
stmt: &'a Stmt,
|
||||
module: &'a str,
|
||||
@@ -359,6 +376,9 @@ impl<'a> ImportReplacer<'a> {
|
||||
if self.version >= PythonVersion::Py311 {
|
||||
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_311);
|
||||
}
|
||||
if self.version >= PythonVersion::Py312 {
|
||||
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_312);
|
||||
}
|
||||
if let Some(operation) = self.try_replace(&typing_extensions_to_typing, "typing") {
|
||||
operations.push(operation);
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ pub(crate) fn printf_string_formatting(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -11,6 +11,22 @@ use crate::rules::ruff::rules::confusables::CONFUSABLES;
|
||||
use crate::rules::ruff::rules::Context;
|
||||
use crate::settings::Settings;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for ambiguous unicode characters in strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The use of ambiguous unicode characters can confuse readers and cause
|
||||
/// subtle bugs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// print("Ηello, world!") # "Η" is the Greek eta (`U+0397`).
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// print("Hello, world!") # "H" is the Latin capital H (`U+0048`).
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AmbiguousUnicodeCharacterString {
|
||||
confusable: char,
|
||||
@@ -44,6 +60,22 @@ impl AlwaysAutofixableViolation for AmbiguousUnicodeCharacterString {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for ambiguous unicode characters in docstrings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The use of ambiguous unicode characters can confuse readers and cause
|
||||
/// subtle bugs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// """A lovely docstring (with a `U+FF09` parenthesis)."""
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// """A lovely docstring (with no strange parentheses)."""
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AmbiguousUnicodeCharacterDocstring {
|
||||
confusable: char,
|
||||
@@ -77,6 +109,22 @@ impl AlwaysAutofixableViolation for AmbiguousUnicodeCharacterDocstring {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for ambiguous unicode characters in comments.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The use of ambiguous unicode characters can confuse readers and cause
|
||||
/// subtle bugs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo() # nоqa # "о" is Cyrillic (`U+043E`)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// foo() # noqa # "o" is Latin (`U+006F`)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AmbiguousUnicodeCharacterComment {
|
||||
confusable: char,
|
||||
|
||||
@@ -8,6 +8,36 @@ use ruff_python_ast::helpers::has_comments;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `+` operator to concatenate collections.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In Python, the `+` operator can be used to concatenate collections (e.g.,
|
||||
/// `x + y` to concatenate the lists `x` and `y`).
|
||||
///
|
||||
/// However, collections can be concatenated more efficiently using the
|
||||
/// unpacking operator (e.g., `[*x, *y]` to concatenate `x` and `y`).
|
||||
///
|
||||
/// Prefer the unpacking operator to concatenate collections, as it is more
|
||||
/// readable and flexible. The `*` operator can unpack any iterable, whereas
|
||||
/// `+` operates only on particular sequences which, in many cases, must be of
|
||||
/// the same type.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = [2, 3, 4]
|
||||
/// bar = [1] + foo + [5, 6]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// foo = [2, 3, 4]
|
||||
/// bar = [1, *foo, 5, 6]
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 448 – Additional Unpacking Generalizations](https://peps.python.org/pep-0448/)
|
||||
/// - [Python docs: Sequence Types — `list`, `tuple`, `range`](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range)
|
||||
#[violation]
|
||||
pub struct CollectionLiteralConcatenation {
|
||||
expr: String,
|
||||
|
||||
@@ -6,7 +6,6 @@ use rustpython_parser::ast::{self, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::prelude::ConversionFlag;
|
||||
use ruff_python_ast::source_code::{Locator, Stylist};
|
||||
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
@@ -22,62 +21,36 @@ use crate::registry::AsRule;
|
||||
/// f-strings support dedicated conversion flags for these types, which are
|
||||
/// more succinct and idiomatic.
|
||||
///
|
||||
/// In the case of `str()`, it's also redundant, since `str()` is the default
|
||||
/// conversion.
|
||||
/// Note that, in many cases, calling `str()` within an f-string is
|
||||
/// unnecessary and can be removed entirely, as the value will be converted
|
||||
/// to a string automatically, the notable exception being for classes that
|
||||
/// implement a custom `__format__` method.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// a = "some string"
|
||||
/// f"{str(a)}"
|
||||
/// f"{repr(a)}"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// a = "some string"
|
||||
/// f"{a}"
|
||||
/// f"{a!r}"
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct ExplicitFStringTypeConversion {
|
||||
operation: Operation,
|
||||
}
|
||||
pub struct ExplicitFStringTypeConversion;
|
||||
|
||||
impl AlwaysAutofixableViolation for ExplicitFStringTypeConversion {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ExplicitFStringTypeConversion { operation } = self;
|
||||
match operation {
|
||||
Operation::ConvertCallToConversionFlag => {
|
||||
format!("Use explicit conversion flag")
|
||||
}
|
||||
Operation::RemoveCall => format!("Remove unnecessary `str` conversion"),
|
||||
Operation::RemoveConversionFlag => format!("Remove unnecessary conversion flag"),
|
||||
}
|
||||
format!("Use explicit conversion flag")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let ExplicitFStringTypeConversion { operation } = self;
|
||||
match operation {
|
||||
Operation::ConvertCallToConversionFlag => {
|
||||
format!("Replace with conversion flag")
|
||||
}
|
||||
Operation::RemoveCall => format!("Remove `str` call"),
|
||||
Operation::RemoveConversionFlag => format!("Remove conversion flag"),
|
||||
}
|
||||
"Replace with conversion flag".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Operation {
|
||||
/// Ex) Convert `f"{repr(bla)}"` to `f"{bla!r}"`
|
||||
ConvertCallToConversionFlag,
|
||||
/// Ex) Convert `f"{bla!s}"` to `f"{bla}"`
|
||||
RemoveConversionFlag,
|
||||
/// Ex) Convert `f"{str(bla)}"` to `f"{bla}"`
|
||||
RemoveCall,
|
||||
}
|
||||
|
||||
/// RUF010
|
||||
pub(crate) fn explicit_f_string_type_conversion(
|
||||
checker: &mut Checker,
|
||||
@@ -96,156 +69,50 @@ pub(crate) fn explicit_f_string_type_conversion(
|
||||
.enumerate()
|
||||
{
|
||||
let ast::ExprFormattedValue {
|
||||
value,
|
||||
conversion,
|
||||
format_spec,
|
||||
range: _,
|
||||
value, conversion, ..
|
||||
} = formatted_value;
|
||||
|
||||
match conversion {
|
||||
ConversionFlag::Ascii | ConversionFlag::Repr => {
|
||||
// Nothing to do.
|
||||
continue;
|
||||
}
|
||||
ConversionFlag::Str => {
|
||||
// Skip if there's a format spec.
|
||||
if format_spec.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the conversion flag entirely.
|
||||
// Ex) `f"{bla!s}"`
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ExplicitFStringTypeConversion {
|
||||
operation: Operation::RemoveConversionFlag,
|
||||
},
|
||||
value.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
remove_conversion_flag(expr, index, checker.locator, checker.stylist)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
ConversionFlag::None => {
|
||||
// Replace with the appropriate conversion flag.
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
}) = value.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Can't be a conversion otherwise.
|
||||
if args.len() != 1 || !keywords.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !matches!(id.as_str(), "str" | "repr" | "ascii") {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if id == "str" && format_spec.is_none() {
|
||||
// Ex) `f"{str(bla)}"`
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ExplicitFStringTypeConversion {
|
||||
operation: Operation::RemoveCall,
|
||||
},
|
||||
value.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
remove_conversion_call(expr, index, checker.locator, checker.stylist)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
} else {
|
||||
// Ex) `f"{repr(bla)}"`
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ExplicitFStringTypeConversion {
|
||||
operation: Operation::ConvertCallToConversionFlag,
|
||||
},
|
||||
value.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
convert_call_to_conversion_flag(
|
||||
expr,
|
||||
index,
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
// Skip if there's already a conversion flag.
|
||||
if !conversion.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
}) = value.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Can't be a conversion otherwise.
|
||||
if args.len() != 1 || !keywords.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !matches!(id.as_str(), "str" | "repr" | "ascii") {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !checker.semantic().is_builtin(id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(ExplicitFStringTypeConversion, value.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
convert_call_to_conversion_flag(expr, index, checker.locator, checker.stylist)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove a conversion flag from a formatted expression.
|
||||
fn remove_conversion_flag(
|
||||
expr: &Expr,
|
||||
index: usize,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Fix> {
|
||||
// Parenthesize the expression, to support implicit concatenation.
|
||||
let range = expr.range();
|
||||
let content = locator.slice(range);
|
||||
let parenthesized_content = format!("({content})");
|
||||
let mut expression = match_expression(&parenthesized_content)?;
|
||||
|
||||
// Replace the formatted call expression at `index` with a conversion flag.
|
||||
let formatted_string_expression = match_part(index, &mut expression)?;
|
||||
formatted_string_expression.conversion = None;
|
||||
|
||||
// Remove the parentheses (first and last characters).
|
||||
let mut content = expression.codegen_stylist(stylist);
|
||||
content.remove(0);
|
||||
content.pop();
|
||||
|
||||
Ok(Fix::automatic(Edit::range_replacement(content, range)))
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove a call from a formatted expression.
|
||||
fn remove_conversion_call(
|
||||
expr: &Expr,
|
||||
index: usize,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
) -> Result<Fix> {
|
||||
// Parenthesize the expression, to support implicit concatenation.
|
||||
let range = expr.range();
|
||||
let content = locator.slice(range);
|
||||
let parenthesized_content = format!("({content})");
|
||||
let mut expression = match_expression(&parenthesized_content)?;
|
||||
|
||||
// Replace the formatted call expression at `index` with a conversion flag.
|
||||
let formatted_string_expression = match_part(index, &mut expression)?;
|
||||
let call = match_call_mut(&mut formatted_string_expression.expression)?;
|
||||
formatted_string_expression.expression = call.args[0].value.clone();
|
||||
|
||||
// Remove the parentheses (first and last characters).
|
||||
let mut content = expression.codegen_stylist(stylist);
|
||||
content.remove(0);
|
||||
content.pop();
|
||||
|
||||
Ok(Fix::automatic(Edit::range_replacement(content, range)))
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to replace an explicit type conversion with a conversion flag.
|
||||
fn convert_call_to_conversion_flag(
|
||||
expr: &Expr,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user