Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9acda732 | ||
|
|
6c8021e970 | ||
|
|
61b6ad46ea | ||
|
|
041d8108e6 | ||
|
|
e2c4a098de | ||
|
|
e865f58426 | ||
|
|
23b4e16b1d | ||
|
|
ae2ac905dc | ||
|
|
55619b321a | ||
|
|
6f31b002f8 | ||
|
|
1a79965aa0 | ||
|
|
16da183f8e | ||
|
|
3f689917cb | ||
|
|
a4a215e8a3 | ||
|
|
aa1c884910 | ||
|
|
7fb55c6d99 | ||
|
|
04ea523ad8 | ||
|
|
9897f81cf3 | ||
|
|
1a2559b001 | ||
|
|
721a1e9443 | ||
|
|
f38bba18ee | ||
|
|
14cf36f922 | ||
|
|
d28e026525 | ||
|
|
d19a8aa54d | ||
|
|
f299940452 | ||
|
|
e1ab7163ac | ||
|
|
9edc479c6c | ||
|
|
560558b814 | ||
|
|
bef601b994 | ||
|
|
7445d00b88 | ||
|
|
7c78d4e103 | ||
|
|
8b14f1b8cc | ||
|
|
5a6b51e623 | ||
|
|
0b60242fb7 | ||
|
|
65b77feeb8 | ||
|
|
04b9c0a31d | ||
|
|
49dc8231be | ||
|
|
92ca114882 | ||
|
|
553bc7443a | ||
|
|
a3af6c1ea5 | ||
|
|
b50016fe89 | ||
|
|
2cf2805848 | ||
|
|
33fbef7700 | ||
|
|
68668a584b | ||
|
|
6cd8655d29 | ||
|
|
72a9bd3cfb |
6
.github/workflows/ruff.yaml
vendored
6
.github/workflows/ruff.yaml
vendored
@@ -287,14 +287,10 @@ jobs:
|
||||
with:
|
||||
name: wheels
|
||||
- uses: actions/setup-python@v4
|
||||
- name: Publish to PyPI
|
||||
- name: Publish to PyPi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
|
||||
run: |
|
||||
pip install --upgrade twine
|
||||
twine upload --skip-existing *
|
||||
- name: Publish to GitHub Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: *
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.136
|
||||
rev: v0.0.143
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
56
Cargo.lock
generated
56
Cargo.lock
generated
@@ -290,6 +290,36 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
|
||||
dependencies = [
|
||||
"clap 4.0.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_command"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
|
||||
dependencies = [
|
||||
"clap 4.0.22",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_fig"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
|
||||
dependencies = [
|
||||
"clap 4.0.22",
|
||||
"clap_complete",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.0.21"
|
||||
@@ -670,7 +700,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.136-dev.0"
|
||||
version = "0.0.143-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -769,10 +799,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
name = "globset"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr 0.2.17",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
@@ -1029,7 +1066,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"itertools",
|
||||
@@ -1044,7 +1081,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1768,7 +1805,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1779,6 +1816,7 @@ dependencies = [
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap 4.0.22",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"common-path",
|
||||
@@ -1787,7 +1825,7 @@ dependencies = [
|
||||
"fern",
|
||||
"filetime",
|
||||
"getrandom 0.2.8",
|
||||
"glob",
|
||||
"globset",
|
||||
"insta",
|
||||
"itertools",
|
||||
"libcst",
|
||||
@@ -1818,7 +1856,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -22,14 +22,15 @@ bitflags = { version = "1.3.2" }
|
||||
cachedir = { version = "0.3.0" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
clap_complete_command = "0.4.0"
|
||||
colored = { version = "2.0.0" }
|
||||
common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
globset = {version = "0.4.9" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
log = { version = "0.4.17" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
notify = { version = "4.0.17" }
|
||||
|
||||
50
LICENSE
50
LICENSE
@@ -242,6 +242,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-debugger, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Joseph Kahn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-tidy-imports, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
@@ -443,3 +468,28 @@ are:
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- RustPython, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 RustPython Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
729
README.md
729
README.md
@@ -20,10 +20,9 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 `--fix` support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 👀 `--watch` support, for continuous file monitoring
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
|
||||
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
|
||||
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
|
||||
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
|
||||
@@ -32,7 +31,7 @@ of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://
|
||||
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
|
||||
faster than any individual tool.
|
||||
|
||||
Ruff is actively developed and used in major open-source projects like:
|
||||
Ruff is extremely actively developed and used in major open-source projects like:
|
||||
|
||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
@@ -43,6 +42,26 @@ Ruff is actively developed and used in major open-source projects like:
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
## Testimonials
|
||||
|
||||
[**Sebastián Ramírez**](https://twitter.com/tiangolo/status/1591912354882764802), creator
|
||||
of [FastAPI](https://github.com/tiangolo/fastapi):
|
||||
|
||||
> Ruff is so fast that sometimes I add an intentional bug in the code just to confirm it's actually
|
||||
> running and checking the code.
|
||||
|
||||
[**Bryan Van de Ven**](https://github.com/bokeh/bokeh/pull/12605), co-creator
|
||||
of [Bokeh](https://github.com/bokeh/bokeh/), original author
|
||||
of [Conda](https://docs.conda.io/en/latest/):
|
||||
|
||||
> Ruff is ~150-200x faster than flake8 on my machine, scanning the whole repo takes ~0.2s instead of
|
||||
> ~20s. This is an enormous quality of life improvement for local dev. It's fast enough that I added
|
||||
> it as an actual commit hook, which is terrific.
|
||||
|
||||
[**Tim Abbott**](https://github.com/charliermarsh/ruff/issues/465#issuecomment-1317400028), lead developer of [Zulip](https://github.com/zulip/zulip):
|
||||
|
||||
> This is just ridiculously fast... `ruff` is amazing.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Installation and Usage](#installation-and-usage)
|
||||
@@ -56,15 +75,16 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
1. [pep8-naming (N)](#pep8-naming)
|
||||
1. [flake8-bandit (S)](#flake8-bandit)
|
||||
1. [flake8-comprehensions (C)](#flake8-comprehensions)
|
||||
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
|
||||
1. [flake8-bugbear (B)](#flake8-bugbear)
|
||||
1. [flake8-builtins (A)](#flake8-builtins)
|
||||
1. [flake8-debugger (T)](#flake8-debugger)
|
||||
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
|
||||
1. [flake8-print (T)](#flake8-print)
|
||||
1. [flake8-quotes (Q)](#flake8-quotes)
|
||||
1. [flake8-annotations (ANN)](#flake8-annotations)
|
||||
1. [flake8-2020 (YTT)](#flake8-2020)
|
||||
1. [flake8-blind-except (BLE)](#flake8-blind-except)
|
||||
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
|
||||
1. [mccabe (C90)](#mccabe)
|
||||
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
|
||||
1. [Meta rules (M)](#meta-rules)
|
||||
@@ -73,6 +93,7 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
1. [Development](#development)
|
||||
1. [Releases](#releases)
|
||||
1. [Benchmarks](#benchmarks)
|
||||
1. [Reference](#reference)
|
||||
1. [License](#license)
|
||||
1. [Contributing](#contributing)
|
||||
|
||||
@@ -80,12 +101,18 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
|
||||
### Installation
|
||||
|
||||
Available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
|
||||
Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
|
||||
|
||||
```shell
|
||||
pip install ruff
|
||||
```
|
||||
|
||||
If you're a **macOS Homebrew** or a **Linuxbrew** user, you can also install `ruff` via Homebrew:
|
||||
|
||||
```shell
|
||||
brew install ruff
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To run Ruff, try any of the following:
|
||||
@@ -107,7 +134,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.136
|
||||
rev: v0.0.143
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -117,8 +144,10 @@ _Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as
|
||||
|
||||
## Configuration
|
||||
|
||||
Ruff is configurable both via `pyproject.toml` and the command line. If left unspecified, the
|
||||
default configuration is equivalent to:
|
||||
Ruff is configurable both via `pyproject.toml` and the command line. For a full list of configurable
|
||||
options, see the [API reference](#reference).
|
||||
|
||||
If left unspecified, the default configuration is equivalent to:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
@@ -179,8 +208,10 @@ ignore = ["E501"]
|
||||
fix = true
|
||||
unfixable = ["F401"]
|
||||
|
||||
# Ignore `E402` (import violations in any `__init__.py` file, and in `path/to/file.py`.
|
||||
per-file-ignores = {"__init__.py" = ["E402"], "path/to/file.py" = ["E402"]}
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"path/to/file.py" = ["E402"]
|
||||
```
|
||||
|
||||
Plugin configurations should be expressed as subsections, e.g.:
|
||||
@@ -194,7 +225,9 @@ select = ["E", "F", "Q"]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Alternatively, common configuration settings can be provided via the command-line:
|
||||
For a full list of configurable options, see the [API reference](#reference).
|
||||
|
||||
Some common configuration settings can be provided via the command-line:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/ --select F401 --select F403
|
||||
@@ -271,21 +304,10 @@ Options:
|
||||
Print version information
|
||||
```
|
||||
|
||||
### Excluding files
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
|
||||
`foo_*.py` ).
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
### Ignoring errors
|
||||
|
||||
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
|
||||
either on the command-line or in your `project.toml` file.
|
||||
either on the command-line or in your `project.toml` file.
|
||||
|
||||
To ignore an error in-line, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
|
||||
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
|
||||
@@ -350,6 +372,20 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | |
|
||||
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | |
|
||||
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | |
|
||||
| F501 | PercentFormatInvalidFormat | '...' % ... has invalid format string: ... | |
|
||||
| F502 | PercentFormatExpectedMapping | '...' % ... expected mapping but got sequence | |
|
||||
| F503 | PercentFormatExpectedSequence | '...' % ... expected sequence but got mapping | |
|
||||
| F504 | PercentFormatExtraNamedArguments | '...' % ... has unused named argument(s): ... | |
|
||||
| F505 | PercentFormatMissingArgument | '...' % ... is missing argument(s) for placeholder(s): ... | |
|
||||
| F506 | PercentFormatMixedPositionalAndNamed | '...' % ... has mixed positional and named placeholders | |
|
||||
| F507 | PercentFormatPositionalCountMismatch | '...' % ... has 4 placeholder(s) but 2 substitution(s) | |
|
||||
| F508 | PercentFormatStarRequiresSequence | '...' % ... `*` specifier requires sequence | |
|
||||
| F509 | PercentFormatUnsupportedFormatCharacter | '...' % ... has unsupported format character 'c' | |
|
||||
| F521 | StringDotFormatInvalidFormat | '...'.format(...) has invalid format string: ... | |
|
||||
| F522 | StringDotFormatExtraNamedArguments | '...'.format(...) has unused named argument(s): ... | |
|
||||
| F523 | StringDotFormatExtraPositionalArguments | '...'.format(...) has unused arguments at position(s): ... | |
|
||||
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
|
||||
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders | |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
|
||||
@@ -533,6 +569,14 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
|
||||
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
|
||||
|
||||
### flake8-debugger
|
||||
|
||||
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| T100 | Debugger | Import for `...` found | |
|
||||
|
||||
### flake8-boolean-trap
|
||||
|
||||
For more, see [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/0.1.0/) on PyPI.
|
||||
@@ -570,10 +614,12 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
| B020 | LoopVariableOverridesIterator | Loop control variable `...` overrides iterable it iterates | |
|
||||
| B021 | FStringDocstring | f-string used as docstring. This will be interpreted by python as a joined string rather than a docstring. | |
|
||||
| B022 | UselessContextlibSuppress | No arguments passed to `contextlib.suppress`. No exceptions will be suppressed and therefore this context manager is redundant | |
|
||||
| B023 | FunctionUsesLoopVariable | Function definition does not bind loop variable `...` | |
|
||||
| B024 | AbstractBaseClassWithoutAbstractMethod | `...` is an abstract base class, but it has no abstract methods | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
|
||||
| B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | |
|
||||
| B904 | RaiseWithoutFromInsideExcept | Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling | |
|
||||
|
||||
### flake8-builtins
|
||||
|
||||
@@ -802,28 +848,28 @@ stylistic lint rules that are obviated by autoformatting.
|
||||
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
|
||||
automatically convert your existing configuration.)
|
||||
|
||||
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
|
||||
number of plugins, (2) alongside Black, and (3) on Python 3 code.
|
||||
Ruff can be used as a drop-in replacement for Flake8 when used (1) without or with a small number of
|
||||
plugins, (2) alongside Black, and (3) on Python 3 code.
|
||||
|
||||
Under those conditions Ruff is missing 14 rules related to string `.format` calls, 1 rule related
|
||||
to docstring parsing, and 1 rule related to redefined variables.
|
||||
Under those conditions, Ruff implements every rule in Flake8, with the exception of `F811`.
|
||||
|
||||
Ruff re-implements some of the most popular Flake8 plugins and related code quality tools natively,
|
||||
including:
|
||||
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
|
||||
natively, including:
|
||||
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`yesqa`](https://github.com/asottile/yesqa)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (25/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
|
||||
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
|
||||
@@ -832,12 +878,12 @@ including:
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
pattern matching and parenthesized context managers.
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules. (To date, popular Flake8
|
||||
plugins have been re-implemented within Ruff directly.)
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules. (Instead, popular Flake8
|
||||
plugins are re-implemented in Rust as part of Ruff itself.)
|
||||
|
||||
### Which tools does Ruff replace?
|
||||
|
||||
@@ -845,16 +891,17 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
|
||||
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
|
||||
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (26/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
|
||||
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
|
||||
@@ -1139,6 +1186,614 @@ Summary
|
||||
389.73 ± 9.92 times faster than 'flake8 resources/test/cpython'
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Options
|
||||
|
||||
#### [`dummy_variable_rgx`](#dummy_variable_rgx)
|
||||
|
||||
A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating
|
||||
(e.g.) unused-variable checks.
|
||||
|
||||
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`)
|
||||
|
||||
**Type**: `Regex`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Only ignore variables named "_".
|
||||
dummy_variable_rgx = "^_$"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`exclude`](#exclude)
|
||||
|
||||
A list of file patterns to exclude from linting.
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
|
||||
`foo_*.py` ).
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify the excluded
|
||||
paths.
|
||||
|
||||
**Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]`
|
||||
|
||||
**Type**: `Vec<FilePattern>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
exclude = [".venv"]
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
#### [`extend_exclude`](#extend_exclude)
|
||||
|
||||
A list of file patterns to omit from linting, in addition to those specified by `exclude`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<FilePattern>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# In addition to the standard set of exclusions, omit all tests, plus a specific file.
|
||||
extend-exclude = ["tests", "src/bad.py"]
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
#### [`ignore`](#ignore)
|
||||
|
||||
A list of check code prefixes to ignore. Prefixes can specify exact checks (like `F841`), entire
|
||||
categories (like `F`), or anything in between.
|
||||
|
||||
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
|
||||
more specific prefixes override less specific prefixes.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Skip unused variable checks (`F841`).
|
||||
ignore = ["F841"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`extend_ignore`](#extend_ignore)
|
||||
|
||||
A list of check code prefixes to ignore, in addition to those specified by `ignore`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Skip unused variable checks (`F841`).
|
||||
extend-ignore = ["F841"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`select`](#select)
|
||||
|
||||
A list of check code prefixes to enable. Prefixes can specify exact checks (like `F841`), entire
|
||||
categories (like `F`), or anything in between.
|
||||
|
||||
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
|
||||
more specific prefixes override less specific prefixes.
|
||||
|
||||
**Default value**: `["E", "F"]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
|
||||
select = ["E", "F", "B", "Q"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`extend_select`](#extend_select)
|
||||
|
||||
A list of check code prefixes to enable, in addition to those specified by `select`.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# On top of the default `select` (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
|
||||
extend-select = ["B", "Q"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`fix`](#fix)
|
||||
|
||||
Enable autofix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix`
|
||||
command-line flags).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
fix = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`fixable`](#fixable)
|
||||
|
||||
A list of check code prefixes to consider autofix-able.
|
||||
|
||||
**Default value**: `["A", "ANN", "B", "BLE", "C", "D", "E", "F", "FBT", "I", "M", "N", "Q", "RUF", "S", "T", "U", "W", "YTT"]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Only allow autofix behavior for `E` and `F` checks.
|
||||
fixable = ["E", "F"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`unfixable`](#unfixable)
|
||||
|
||||
A list of check code prefixes to consider un-autofix-able.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<CheckCodePrefix>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Disable autofix for unused imports (`F401`).
|
||||
unfixable = ["F401"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`line_length`](#line_length)
|
||||
|
||||
The line length to use when enforcing long-lines violations (like E501).
|
||||
|
||||
**Default value**: `88`
|
||||
|
||||
**Type**: `usize`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Allow lines to be as long as 120 characters.
|
||||
line-length = 120
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`per_file_ignores`](#per_file_ignores)
|
||||
|
||||
A list of mappings from file pattern to check code prefixes to exclude, when considering any
|
||||
matching files.
|
||||
|
||||
**Default value**: `{}`
|
||||
|
||||
**Type**: `HashMap<String, Vec<CheckCodePrefix>>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"path/to/file.py" = ["E402"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`show_source`](#show_source)
|
||||
|
||||
Whether to show source code snippets when reporting lint error violations (overridden by the
|
||||
`--show-source` command-line flag).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# By default, always show source code snippets.
|
||||
show_source = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`src`](#src)
|
||||
|
||||
The source code paths to consider, e.g., when resolving first- vs. third-party imports.
|
||||
|
||||
**Default value**: `["."]`
|
||||
|
||||
**Type**: `Vec<PathBuf>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Allow imports relative to the "src" and "test" directories.
|
||||
src = ["src", "test"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`target_version`](#target_version)
|
||||
|
||||
The Python version to target, e.g., when considering automatic code upgrades, like rewriting type
|
||||
annotations. Note that the target version will _not_ be inferred from the _current_ Python version,
|
||||
and instead must be specified explicitly (as seen below).
|
||||
|
||||
**Default value**: `"py310"`
|
||||
|
||||
**Type**: `PythonVersion`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Always generate Python 3.7-compatible code.
|
||||
target-version = "py37"
|
||||
```
|
||||
|
||||
### `flake8-annotations`
|
||||
|
||||
#### [`mypy_init_return`](#mypy_init_return)
|
||||
|
||||
Whether to allow the omission of a return type hint for `__init__` if at least one argument is
|
||||
annotated.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-annotations]
|
||||
mypy_init_return = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`suppress_dummy_args`](#suppress_dummy_args)
|
||||
|
||||
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable regex (like
|
||||
`_`).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-annotations]
|
||||
suppress_dummy_args = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`suppress_none_returning`](#suppress_none_returning)
|
||||
|
||||
Whether to suppress `ANN200`-level errors for functions that meet either of the following criteria:
|
||||
|
||||
- Contain no `return` statement.
|
||||
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-annotations]
|
||||
suppress_none_returning = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`allow_star_arg_any`](#allow_star_arg_any)
|
||||
|
||||
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-annotations]
|
||||
allow_star_arg_any = true
|
||||
```
|
||||
|
||||
### `flake8-bugbear`
|
||||
|
||||
#### [`extend_immutable_calls`](#extend_immutable_calls)
|
||||
|
||||
Additional callable functions to consider "immutable" when evaluating, e.g., no-mutable-default-argument
|
||||
checks (`B006`).
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-bugbear]
|
||||
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
```
|
||||
|
||||
### `flake8-quotes`
|
||||
|
||||
#### [`inline_quotes`](#inline_quotes)
|
||||
|
||||
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
**Type**: `Quote`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`multiline_quotes`](#multiline_quotes)
|
||||
|
||||
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
**Type**: `Quote`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-quotes]
|
||||
multiline-quotes = "single"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`docstring_quotes`](#docstring_quotes)
|
||||
|
||||
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
|
||||
|
||||
**Default value**: `"double"`
|
||||
|
||||
**Type**: `Quote`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-quotes]
|
||||
docstring-quotes = "single"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`avoid_escape`](#avoid_escape)
|
||||
|
||||
Whether to avoid using single quotes if a string contains single quotes, or vice-versa with
|
||||
double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the
|
||||
need to escape quotation marks within strings.
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-quotes]
|
||||
# Don't bother trying to avoid escapes.
|
||||
avoid-escape = false
|
||||
```
|
||||
|
||||
### `flake8-tidy-imports`
|
||||
|
||||
#### [`ban_relative_imports`](#ban_relative_imports)
|
||||
|
||||
Whether to ban all relative imports (`"all"`), or only those imports that extend into the parent
|
||||
module and beyond (`"parents"`).
|
||||
|
||||
**Default value**: `"parents"`
|
||||
|
||||
**Type**: `Strictness`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
# Disallow all relative imports.
|
||||
ban-relative-imports = "all"
|
||||
```
|
||||
|
||||
### `isort`
|
||||
|
||||
#### [`known_first_party`](known_first_party)
|
||||
|
||||
A list of modules to consider first-party, regardless of whether they can be identified as such
|
||||
via introspection of the local filesystem.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
known-first-party = ["src"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`known_third_party`](known_third_party)
|
||||
|
||||
A list of modules to consider third-party, regardless of whether they can be identified as such
|
||||
via introspection of the local filesystem.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
known-third-party = ["fastapi"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`extra_standard_library`](extra_standard_library)
|
||||
|
||||
A list of modules to consider standard-library, in addition to those known to Ruff in advance.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
extra-standard-library = ["path"]
|
||||
```
|
||||
|
||||
### `mccabe`
|
||||
|
||||
#### [`max_complexity`](#max_complexity)
|
||||
|
||||
The maximum McCabe complexity to allow before triggering `C901` errors.
|
||||
|
||||
**Default value**: `10`
|
||||
|
||||
**Type**: `usize`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
# Flag errors (`C901`) whenever the complexity level exceeds 5.
|
||||
max-complexity = 5
|
||||
```
|
||||
|
||||
### `pep8-naming`
|
||||
|
||||
#### [`ignore_names`](#ignore_names)
|
||||
|
||||
A list of names to ignore when considering `pep8-naming` violations.
|
||||
|
||||
**Default value**: `["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = ["callMethod"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`classmethod_decorators`](#classmethod_decorators)
|
||||
|
||||
A list of decorators that, when applied to a method, indicate that the method should be treated as
|
||||
a class method. For example, Ruff will expect that any method decorated by a decorator in this list
|
||||
takes a `cls` argument as its first argument.
|
||||
|
||||
**Default value**: `["classmethod"]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.pep8-naming]
|
||||
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
|
||||
classmethod-decorators = ["classmethod", "pydantic.validator"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`staticmethod_decorators`](#staticmethod_decorators)
|
||||
|
||||
A list of decorators that, when applied to a method, indicate that the method should be treated as
|
||||
a static method. For example, Ruff will expect that any method decorated by a decorator in this list
|
||||
has no `self` or `cls` argument.
|
||||
|
||||
**Default value**: `["staticmethod"]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.pep8-naming]
|
||||
# Allow a shorthand alias, `@stcmthd`, to trigger static method treatment.
|
||||
staticmethod-decorators = ["staticmethod", "stcmthd"]
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
8
flake8_to_ruff/Cargo.lock
generated
8
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1265,7 +1265,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"itertools",
|
||||
@@ -1280,7 +1280,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.136-dev.0"
|
||||
version = "0.0.143-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -10,6 +10,7 @@ pub enum Plugin {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8Docstrings,
|
||||
Flake8TidyImports,
|
||||
Flake8Print,
|
||||
@@ -30,6 +31,7 @@ impl FromStr for Plugin {
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
@@ -51,9 +53,10 @@ impl Plugin {
|
||||
Plugin::Flake8Bugbear => CheckCodePrefix::B,
|
||||
Plugin::Flake8Builtins => CheckCodePrefix::A,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
|
||||
Plugin::Flake8Debugger => CheckCodePrefix::T1,
|
||||
Plugin::Flake8Docstrings => CheckCodePrefix::D,
|
||||
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
|
||||
Plugin::Flake8Print => CheckCodePrefix::T,
|
||||
Plugin::Flake8Print => CheckCodePrefix::T2,
|
||||
Plugin::Flake8Quotes => CheckCodePrefix::Q,
|
||||
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
|
||||
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
|
||||
@@ -69,6 +72,7 @@ impl Plugin {
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
Plugin::Flake8Debugger => vec![CheckCodePrefix::T1],
|
||||
Plugin::Flake8Docstrings => {
|
||||
// Use the user-provided docstring.
|
||||
for key in ["docstring-convention", "docstring_convention"] {
|
||||
@@ -86,7 +90,7 @@ impl Plugin {
|
||||
DocstringConvention::PEP8.select()
|
||||
}
|
||||
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T],
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
|
||||
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
|
||||
@@ -364,6 +368,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
|
||||
Plugin::Flake8Bugbear,
|
||||
Plugin::Flake8Builtins,
|
||||
Plugin::Flake8Comprehensions,
|
||||
Plugin::Flake8Debugger,
|
||||
Plugin::Flake8Docstrings,
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::Flake8Print,
|
||||
|
||||
82
resources/test/fixtures/B023.py
vendored
Normal file
82
resources/test/fixtures/B023.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Should emit:
|
||||
B023 - on lines 12, 13, 16, 28, 29, 30, 31, 40, 42, 50, 51, 52, 53, 61, 68.
|
||||
"""
|
||||
|
||||
functions = []
|
||||
z = 0
|
||||
|
||||
for x in range(3):
|
||||
y = x + 1
|
||||
# Subject to late-binding problems
|
||||
functions.append(lambda: x)
|
||||
functions.append(lambda: y) # not just the loop var
|
||||
|
||||
def f_bad_1():
|
||||
return x
|
||||
|
||||
# Actually OK
|
||||
functions.append(lambda x: x * 2)
|
||||
functions.append(lambda x=x: x)
|
||||
functions.append(lambda: z) # OK because not assigned in the loop
|
||||
|
||||
def f_ok_1(x):
|
||||
return x * 2
|
||||
|
||||
|
||||
def check_inside_functions_too():
|
||||
ls = [lambda: x for x in range(2)]
|
||||
st = {lambda: x for x in range(2)}
|
||||
gn = (lambda: x for x in range(2))
|
||||
dt = {x: lambda: x for x in range(2)}
|
||||
|
||||
|
||||
async def pointless_async_iterable():
|
||||
yield 1
|
||||
|
||||
|
||||
async def container_for_problems():
|
||||
async for x in pointless_async_iterable():
|
||||
functions.append(lambda: x)
|
||||
|
||||
[lambda: x async for x in pointless_async_iterable()]
|
||||
|
||||
|
||||
a = 10
|
||||
b = 0
|
||||
while True:
|
||||
a = a_ = a - 1
|
||||
b += 1
|
||||
functions.append(lambda: a)
|
||||
functions.append(lambda: a_)
|
||||
functions.append(lambda: b)
|
||||
functions.append(lambda: c) # not a name error because of late binding!
|
||||
c: bool = a > 3
|
||||
if not c:
|
||||
break
|
||||
|
||||
# Nested loops should not duplicate reports
|
||||
for j in range(2):
|
||||
for k in range(3):
|
||||
lambda: j * k
|
||||
|
||||
|
||||
for j, k, l in [(1, 2, 3)]:
|
||||
|
||||
def f():
|
||||
j = None # OK because it's an assignment
|
||||
[l for k in range(2)] # error for l, not for k
|
||||
|
||||
assert a and functions
|
||||
|
||||
a.attribute = 1 # modifying an attribute doesn't make it a loop variable
|
||||
functions[0] = lambda: None # same for an element
|
||||
|
||||
for var in range(2):
|
||||
|
||||
def explicit_capture(captured=var):
|
||||
return captured
|
||||
|
||||
|
||||
for i in range(3):
|
||||
lambda: f"{i}"
|
||||
55
resources/test/fixtures/B904.py
vendored
Normal file
55
resources/test/fixtures/B904.py
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Should emit:
|
||||
B904 - on lines 10, 11 and 16
|
||||
"""
|
||||
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
if "abc":
|
||||
raise TypeError
|
||||
raise UserWarning
|
||||
except AssertionError:
|
||||
raise # Bare `raise` should not be an error
|
||||
except Exception as err:
|
||||
assert err
|
||||
raise Exception("No cause here...")
|
||||
except BaseException as base_err:
|
||||
# Might use this instead of bare raise with the `.with_traceback()` method
|
||||
raise base_err
|
||||
finally:
|
||||
raise Exception("Nothing to chain from, so no warning here")
|
||||
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
# should not emit, since we are not raising something
|
||||
def proxy():
|
||||
raise NameError
|
||||
|
||||
|
||||
try:
|
||||
from preferred_library import Thing
|
||||
except ImportError:
|
||||
try:
|
||||
from fallback_library import Thing
|
||||
except ImportError:
|
||||
|
||||
class Thing:
|
||||
def __getattr__(self, name):
|
||||
# same as the case above, should not emit.
|
||||
raise AttributeError
|
||||
|
||||
|
||||
try:
|
||||
from preferred_library import Thing
|
||||
except ImportError:
|
||||
try:
|
||||
from fallback_library import Thing
|
||||
except ImportError:
|
||||
|
||||
def context_switch():
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
raise
|
||||
7
resources/test/fixtures/D.py
vendored
7
resources/test/fixtures/D.py
vendored
@@ -3,6 +3,7 @@ from functools import wraps
|
||||
import os
|
||||
from .expected import Expectation
|
||||
from typing import overload
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
expectation = Expectation()
|
||||
@@ -42,9 +43,13 @@ class class_:
|
||||
"D418: Function/ Method decorated with @overload"
|
||||
" shouldn't contain a docstring")
|
||||
|
||||
@override
|
||||
def overridden_method(a):
|
||||
return str(a)
|
||||
|
||||
@property
|
||||
def foo(self):
|
||||
"""The foo of the thing, which isn't in imperitive mood."""
|
||||
"""The foo of the thing, which isn't in imperative mood."""
|
||||
return "hello"
|
||||
|
||||
@expect('D102: Missing docstring in public method')
|
||||
|
||||
3
resources/test/fixtures/E501.py
vendored
3
resources/test/fixtures/E501.py
vendored
@@ -49,3 +49,6 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
|
||||
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""", # noqa: E501
|
||||
}
|
||||
|
||||
# OK
|
||||
# A very long URL: https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
|
||||
|
||||
13
resources/test/fixtures/F502.py
vendored
Normal file
13
resources/test/fixtures/F502.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
dog = {"bob": "bob"}
|
||||
|
||||
"%(bob)s" % dog
|
||||
"%(bob)s" % {"bob": "bob"}
|
||||
"%(bob)s" % {**{"bob": "bob"}}
|
||||
"%(bob)s" % ["bob"] # F202
|
||||
"%(bob)s" % ("bob",) # F202
|
||||
"%(bob)s" % {"bob"} # F202
|
||||
"%(bob)s" % [*["bob"]] # F202
|
||||
"%(bob)s" % {"bob": "bob" for _ in range(1)}
|
||||
"%(bob)s" % ["bob" for _ in range(1)] # F202
|
||||
"%(bob)s" % ("bob" for _ in range(1)) # F202
|
||||
"%(bob)s" % {"bob" for _ in range(1)} # F202
|
||||
26
resources/test/fixtures/F503.py
vendored
Normal file
26
resources/test/fixtures/F503.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
dog = {"bob": "bob"}
|
||||
|
||||
# Single placeholder always fine
|
||||
"%s" % dog
|
||||
"%s" % {"bob": "bob"}
|
||||
"%s" % {**{"bob": "bob"}}
|
||||
"%s" % ["bob"]
|
||||
"%s" % ("bob",)
|
||||
"%s" % {"bob"}
|
||||
"%s" % [*["bob"]]
|
||||
"%s" % {"bob": "bob" for _ in range(1)}
|
||||
"%s" % ["bob" for _ in range(1)]
|
||||
"%s" % ("bob" for _ in range(1))
|
||||
"%s" % {"bob" for _ in range(1)}
|
||||
# Multiple placeholders
|
||||
"%s %s" % dog
|
||||
"%s %s" % {"bob": "bob"} # F503
|
||||
"%s %s" % {**{"bob": "bob"}} # F503
|
||||
"%s %s" % ["bob"]
|
||||
"%s %s" % ("bob",)
|
||||
"%s %s" % {"bob"}
|
||||
"%s %s" % [*["bob"]]
|
||||
"%s %s" % {"bob": "bob" for _ in range(1)} # F503
|
||||
"%s %s" % ["bob" for _ in range(1)]
|
||||
"%s %s" % ("bob" for _ in range(1))
|
||||
"%s %s" % {"bob" for _ in range(1)}
|
||||
6
resources/test/fixtures/F504.py
vendored
Normal file
6
resources/test/fixtures/F504.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ruff has no way of knowing if the following are F505s
|
||||
a = "wrong"
|
||||
"%(a)s %(c)s" % {a: "?", "b": "!"} # F504 ("b" not used)
|
||||
|
||||
hidden = {"a": "!"}
|
||||
"%(a)s %(c)s" % {"x": 1, **hidden} # Ok (cannot see through splat)
|
||||
25
resources/test/fixtures/F50x.py
vendored
Normal file
25
resources/test/fixtures/F50x.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
'%(foo)' % {'foo': 'bar'} # F501
|
||||
'%s %(foo)s' % {'foo': 'bar'} # F506
|
||||
'%(foo)s %s' % {'foo': 'bar'} # F506
|
||||
'%j' % (1,) # F509
|
||||
'%s %s' % (1,) # F507
|
||||
'%s %s' % (1, 2, 3) # F507
|
||||
'%(bar)s' % {} # F505
|
||||
'%(bar)s' % {'bar': 1, 'baz': 2} # F504
|
||||
'%(bar)s' % (1, 2, 3) # F502
|
||||
'%s %s' % {'k': 'v'} # F503
|
||||
'%(bar)*s' % {'bar': 'baz'} # F506, F508
|
||||
|
||||
# ok: single %s with mapping
|
||||
'%s' % {'foo': 'bar', 'baz': 'womp'}
|
||||
# ok: %% should not count towards placeholder count
|
||||
'%% %s %% %s' % (1, 2)
|
||||
# ok: * consumes one positional argument
|
||||
'%.*f' % (2, 1.1234)
|
||||
'%*.*f' % (5, 2, 3.1234)
|
||||
# ok *args and **kwargs
|
||||
a = []
|
||||
'%s %s' % [*a]
|
||||
'%s %s' % (*a,)
|
||||
k = {}
|
||||
'%(k)s' % {**k}
|
||||
21
resources/test/fixtures/F521.py
vendored
Normal file
21
resources/test/fixtures/F521.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
"{".format(1)
|
||||
"}".format(1)
|
||||
"{foo[}".format(foo=1)
|
||||
# too much string recursion (placeholder-in-placeholder)
|
||||
"{:{:{}}}".format(1, 2, 3)
|
||||
# ruff picks these issues up, but flake8 doesn't
|
||||
"{foo[]}".format(foo={"": 1})
|
||||
"{foo..}".format(foo=1)
|
||||
"{foo..bar}".format(foo=1)
|
||||
|
||||
# The following are all "good" uses of .format
|
||||
"{.__class__}".format("")
|
||||
"{foo[bar]}".format(foo={"bar": "barv"})
|
||||
"{[bar]}".format({"bar": "barv"})
|
||||
"{:{}} {}".format(1, 15, 2)
|
||||
"{:2}".format(1)
|
||||
"{foo}-{}".format(1, foo=2)
|
||||
a = ()
|
||||
"{}".format(*a)
|
||||
k = {}
|
||||
"{foo}".format(**k)
|
||||
4
resources/test/fixtures/F522.py
vendored
Normal file
4
resources/test/fixtures/F522.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"{}".format(1, bar=2) # F522
|
||||
"{bar}{}".format(1, bar=2, spam=3) # F522
|
||||
"{bar:{spam}}".format(bar=2, spam=3) # No issues
|
||||
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
|
||||
12
resources/test/fixtures/F523.py
vendored
Normal file
12
resources/test/fixtures/F523.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# With indexes
|
||||
"{0}".format(1, 2) # F523
|
||||
"{1}".format(1, 2, 3) # F523
|
||||
"{1:{0}}".format(1, 2) # No issues
|
||||
"{1:{0}}".format(1, 2, 3) # F523
|
||||
"{0}{2}".format(1, 2) # F523, # F524
|
||||
|
||||
# With no indexes
|
||||
"{}".format(1, 2) # F523
|
||||
"{}".format(1, 2, 3) # F523
|
||||
"{:{}}".format(1, 2) # No issues
|
||||
"{:{}}".format(1, 2, 3) # F523
|
||||
6
resources/test/fixtures/F524.py
vendored
Normal file
6
resources/test/fixtures/F524.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
"{} {}".format(1) # F524
|
||||
"{2}".format() # F524
|
||||
"{bar}".format() # F524
|
||||
"{0} {bar}".format(1) # F524
|
||||
"{0} {bar}".format() # F524
|
||||
"{bar} {0}".format() # F524
|
||||
2
resources/test/fixtures/F525.py
vendored
Normal file
2
resources/test/fixtures/F525.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
"{} {1}".format(1, 2) # F525
|
||||
"{0} {}".format(1, 2) # F523, F525
|
||||
10
resources/test/fixtures/F821_3.py
vendored
10
resources/test/fixtures/F821_3.py
vendored
@@ -12,3 +12,13 @@ x: dict["key", "value"]
|
||||
|
||||
# OK
|
||||
x: dict[str, str]
|
||||
|
||||
# OK
|
||||
def unimportant(name):
|
||||
pass
|
||||
|
||||
|
||||
def dang(dict, set, list):
|
||||
unimportant(name=dict["name"])
|
||||
unimportant(name=set["name"])
|
||||
unimportant(name=list["name"])
|
||||
|
||||
8
resources/test/fixtures/F841.py
vendored
8
resources/test/fixtures/F841.py
vendored
@@ -57,3 +57,11 @@ def f5():
|
||||
def f6():
|
||||
annotations = []
|
||||
assert len([annotations for annotations in annotations])
|
||||
|
||||
|
||||
def f7():
|
||||
def connect():
|
||||
return None, None
|
||||
|
||||
with connect() as (connection, cursor):
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
14
resources/test/fixtures/T100.py
vendored
Normal file
14
resources/test/fixtures/T100.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
breakpoint()
|
||||
|
||||
|
||||
import pdb
|
||||
from builtins import breakpoint
|
||||
from pdb import set_trace as st
|
||||
from celery.contrib.rdb import set_trace
|
||||
from celery.contrib import rdb
|
||||
import celery.contrib.rdb
|
||||
|
||||
|
||||
breakpoint()
|
||||
st()
|
||||
set_trace()
|
||||
2
resources/test/fixtures/U009_0.py
vendored
2
resources/test/fixtures/U009_0.py
vendored
@@ -1,3 +1,3 @@
|
||||
# coding=utf8
|
||||
|
||||
print('Hello world')
|
||||
print("Hello world")
|
||||
|
||||
3
resources/test/fixtures/U009_4.py
vendored
Normal file
3
resources/test/fixtures/U009_4.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# coding=utf8 # noqa: U009
|
||||
|
||||
print("Hello world")
|
||||
42
resources/test/fixtures/U015.py
vendored
42
resources/test/fixtures/U015.py
vendored
@@ -36,3 +36,45 @@ with open("foo", "U") as fa, open("bar", "U") as fb:
|
||||
pass
|
||||
with open("foo", "Ub") as fa, open("bar", "Ub") as fb:
|
||||
pass
|
||||
|
||||
open("foo", mode="U")
|
||||
open(name="foo", mode="U")
|
||||
open(mode="U", name="foo")
|
||||
|
||||
with open("foo", mode="U") as f:
|
||||
pass
|
||||
with open(name="foo", mode="U") as f:
|
||||
pass
|
||||
with open(mode="U", name="foo") as f:
|
||||
pass
|
||||
|
||||
open("foo", mode="Ub")
|
||||
open(name="foo", mode="Ub")
|
||||
open(mode="Ub", name="foo")
|
||||
|
||||
with open("foo", mode="Ub") as f:
|
||||
pass
|
||||
with open(name="foo", mode="Ub") as f:
|
||||
pass
|
||||
with open(mode="Ub", name="foo") as f:
|
||||
pass
|
||||
|
||||
open(file="foo", mode='U', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
|
||||
open(mode='U', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
|
||||
open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
|
||||
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
|
||||
open = 1
|
||||
open("foo", "U")
|
||||
open("foo", "Ur")
|
||||
open("foo", "Ub")
|
||||
open("foo", "rUb")
|
||||
open("foo", "r")
|
||||
open("foo", "rt")
|
||||
open("f", "r", encoding="UTF-8")
|
||||
open("f", "wt")
|
||||
|
||||
@@ -44,7 +44,7 @@ expectation.expected.add((
|
||||
@expect("D407: Missing dashed underline after section ('Returns')",
|
||||
arg_count=3)
|
||||
@expect("D413: Missing blank line after last section ('Raises')", arg_count=3)
|
||||
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
|
||||
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None, **kwargs):
|
||||
"""Fetches rows from a Bigtable.
|
||||
|
||||
Retrieves rows pertaining to the given keys from the Table instance
|
||||
@@ -57,6 +57,7 @@ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
|
||||
to fetch.
|
||||
other_silly_variable: Another optional variable, that has a much
|
||||
longer name than the other args, and which does nothing.
|
||||
**kwargs: More keyword arguments.
|
||||
|
||||
Returns:
|
||||
A dict mapping keys to the corresponding table row data
|
||||
|
||||
@@ -73,7 +73,7 @@ expectation.expected.add((
|
||||
"(found 'A')", arg_count=3)
|
||||
@expect("D413: Missing blank line after last section ('Examples')",
|
||||
arg_count=3)
|
||||
def foo(var1, var2, long_var_name='hi'):
|
||||
def foo(var1, var2, long_var_name='hi', **kwargs):
|
||||
r"""A one-line summary that does not use variable names.
|
||||
|
||||
Several sentences providing an extended description. Refer to
|
||||
@@ -91,6 +91,8 @@ def foo(var1, var2, long_var_name='hi'):
|
||||
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||
long_var_name : {'hi', 'ho'}, optional
|
||||
Choices in brackets, default first when optional.
|
||||
**kwargs : int
|
||||
More keyword arguments.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.136"
|
||||
version = "0.0.143"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -8,7 +8,7 @@ anyhow = { version = "1.0.66" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
codegen = { version = "0.2.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
|
||||
|
||||
@@ -117,7 +117,8 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
|
||||
// Construct the output contents.
|
||||
let mut output = String::new();
|
||||
output.push_str("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
output
|
||||
.push_str("//! File automatically generated by `examples/generate_check_code_prefix.rs`.");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str("use serde::{{Serialize, Deserialize}};");
|
||||
@@ -129,6 +130,21 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str(&scope.to_string());
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
// Add the list of output categories (not generated).
|
||||
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
|
||||
output.push('\n');
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
if prefix.chars().all(char::is_alphabetic) {
|
||||
output.push_str(&format!("CheckCodePrefix::{prefix},"));
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
output.push_str("];");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
// Write the output to `src/checks_gen.rs` (or stdout).
|
||||
if cli.dry_run {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
use rustpython_ast::{
|
||||
Arguments, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::SourceCodeLocator;
|
||||
@@ -213,6 +215,27 @@ pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
|
||||
handler_names
|
||||
}
|
||||
|
||||
/// Return the set of all bound argument names.
|
||||
pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
|
||||
let mut arg_names: FxHashSet<&'a str> = FxHashSet::default();
|
||||
for arg in &arguments.posonlyargs {
|
||||
arg_names.insert(arg.node.arg.as_str());
|
||||
}
|
||||
for arg in &arguments.args {
|
||||
arg_names.insert(arg.node.arg.as_str());
|
||||
}
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
arg_names.insert(arg.node.arg.as_str());
|
||||
}
|
||||
for arg in &arguments.kwonlyargs {
|
||||
arg_names.insert(arg.node.arg.as_str());
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
arg_names.insert(arg.node.arg.as_str());
|
||||
}
|
||||
arg_names
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
// Check: is this a `super` call?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
use rustpython_ast::{Expr, Keyword, Stmt};
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
fn id() -> usize {
|
||||
@@ -9,6 +9,12 @@ fn id() -> usize {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Node<'a> {
|
||||
Stmt(&'a Stmt),
|
||||
Expr(&'a Expr),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Range {
|
||||
pub location: Location,
|
||||
|
||||
@@ -19,8 +19,8 @@ pub trait Visitor<'a> {
|
||||
fn visit_constant(&mut self, constant: &'a Constant) {
|
||||
walk_constant(self, constant);
|
||||
}
|
||||
fn visit_expr_context(&mut self, expr_content: &'a ExprContext) {
|
||||
walk_expr_context(self, expr_content);
|
||||
fn visit_expr_context(&mut self, expr_context: &'a ExprContext) {
|
||||
walk_expr_context(self, expr_context);
|
||||
}
|
||||
fn visit_boolop(&mut self, boolop: &'a Boolop) {
|
||||
walk_boolop(self, boolop);
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn leading_space(line: &str) -> String {
|
||||
}
|
||||
|
||||
/// Extract the leading indentation from a line.
|
||||
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
|
||||
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
|
||||
let range = Range::from_located(located);
|
||||
checker
|
||||
.locator
|
||||
|
||||
288
src/check_ast.rs
288
src/check_ast.rs
@@ -6,6 +6,7 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Withitem;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -18,10 +19,10 @@ use crate::ast::helpers::{
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Range, Scope,
|
||||
ScopeKind,
|
||||
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Node, Range,
|
||||
Scope, ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
|
||||
use crate::ast::{helpers, operations, visitor};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
@@ -32,11 +33,13 @@ use crate::python::typing::SubscriptKind;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print,
|
||||
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, rules,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
|
||||
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
|
||||
pyupgrade, rules,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -77,10 +80,13 @@ pub struct Checker<'a> {
|
||||
in_deferred_string_annotation: bool,
|
||||
in_literal: bool,
|
||||
in_subscript: bool,
|
||||
in_withitem: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<Vec<&'a str>>>,
|
||||
// Check-specific state.
|
||||
pub(crate) seen_b023: Vec<&'a Expr>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -120,10 +126,13 @@ impl<'a> Checker<'a> {
|
||||
in_deferred_string_annotation: false,
|
||||
in_literal: false,
|
||||
in_subscript: false,
|
||||
in_withitem: false,
|
||||
seen_import_boundary: false,
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: false,
|
||||
except_handlers: vec![],
|
||||
// Check-specific state.
|
||||
seen_b023: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +179,15 @@ impl<'a> Checker<'a> {
|
||||
|| (typing::in_extensions(target)
|
||||
&& match_call_path(call_path, "typing_extensions", target, &self.from_imports))
|
||||
}
|
||||
|
||||
/// Return `true` if `member` is bound as a builtin.
|
||||
pub fn is_builtin(&self, member: &str) -> bool {
|
||||
self.current_scopes()
|
||||
.find_map(|scope| scope.values.get(member))
|
||||
.map_or(false, |binding| {
|
||||
matches!(binding.kind, BindingKind::Builtin)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
@@ -619,6 +637,15 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
// flake8-debugger
|
||||
if self.settings.enabled.contains(&CheckCode::T100) {
|
||||
if let Some(check) =
|
||||
flake8_debugger::checks::debugger_import(stmt, None, &alias.node.name)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
for alias in names {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
@@ -687,12 +714,7 @@ where
|
||||
self.from_imports
|
||||
.entry(module)
|
||||
.or_insert_with(FxHashSet::default)
|
||||
.extend(
|
||||
names
|
||||
.iter()
|
||||
.filter(|alias| alias.node.asname.is_none())
|
||||
.map(|alias| alias.node.name.as_str()),
|
||||
);
|
||||
.extend(names.iter().map(|alias| alias.node.name.as_str()));
|
||||
}
|
||||
for alias in names {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
@@ -848,6 +870,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-debugger
|
||||
if self.settings.enabled.contains(&CheckCode::T100) {
|
||||
if let Some(check) = flake8_debugger::checks::debugger_import(
|
||||
stmt,
|
||||
module.as_ref().map(String::as_str),
|
||||
&alias.node.name,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
if self.settings.enabled.contains(&CheckCode::N811) {
|
||||
if let Some(check) =
|
||||
@@ -948,8 +981,16 @@ where
|
||||
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
|
||||
}
|
||||
}
|
||||
StmtKind::While { .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B023) {
|
||||
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
|
||||
}
|
||||
}
|
||||
StmtKind::For {
|
||||
target, body, iter, ..
|
||||
}
|
||||
| StmtKind::AsyncFor {
|
||||
target, body, iter, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::B007) {
|
||||
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
|
||||
@@ -957,6 +998,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B020) {
|
||||
flake8_bugbear::plugins::loop_variable_overrides_iterator(self, target, iter);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B023) {
|
||||
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
@@ -1240,6 +1284,81 @@ where
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
// pyflakes
|
||||
if self.settings.enabled.contains(&CheckCode::F521)
|
||||
|| self.settings.enabled.contains(&CheckCode::F522)
|
||||
|| self.settings.enabled.contains(&CheckCode::F523)
|
||||
|| self.settings.enabled.contains(&CheckCode::F524)
|
||||
|| self.settings.enabled.contains(&CheckCode::F525)
|
||||
{
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
if attr == "format" {
|
||||
// "...".format(...) call
|
||||
let location = Range::from_located(expr);
|
||||
match pyflakes::format::FormatSummary::try_from(value.as_ref()) {
|
||||
Err(e) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F521) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::StringDotFormatInvalidFormat(
|
||||
e.to_string(),
|
||||
),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(summary) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F522) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_extra_named_arguments(
|
||||
&summary, keywords, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F523) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_extra_positional_arguments(
|
||||
&summary, args, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F524) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_missing_argument(
|
||||
&summary, args, keywords, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F525) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_mixing_automatic(
|
||||
&summary, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::U005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
}
|
||||
@@ -1532,6 +1651,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-debugger
|
||||
if self.settings.enabled.contains(&CheckCode::T100) {
|
||||
if let Some(check) = flake8_debugger::checks::debugger_call(
|
||||
expr,
|
||||
func,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
// Ruff
|
||||
if self.settings.enabled.contains(&CheckCode::RUF101) {
|
||||
rules::plugins::convert_exit_to_sys_exit(self, func);
|
||||
@@ -1586,6 +1717,116 @@ where
|
||||
pyflakes::plugins::invalid_print_syntax(self, left);
|
||||
}
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
left,
|
||||
op: Operator::Mod,
|
||||
right,
|
||||
} => {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &left.node
|
||||
{
|
||||
if self.settings.enabled.contains(&CheckCode::F501)
|
||||
|| self.settings.enabled.contains(&CheckCode::F502)
|
||||
|| self.settings.enabled.contains(&CheckCode::F503)
|
||||
|| self.settings.enabled.contains(&CheckCode::F504)
|
||||
|| self.settings.enabled.contains(&CheckCode::F505)
|
||||
|| self.settings.enabled.contains(&CheckCode::F506)
|
||||
|| self.settings.enabled.contains(&CheckCode::F507)
|
||||
|| self.settings.enabled.contains(&CheckCode::F508)
|
||||
|| self.settings.enabled.contains(&CheckCode::F509)
|
||||
{
|
||||
let location = Range::from_located(expr);
|
||||
match pyflakes::cformat::CFormatSummary::try_from(value.as_ref()) {
|
||||
Err(CFormatError {
|
||||
typ: CFormatErrorType::UnsupportedFormatChar(c),
|
||||
..
|
||||
}) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F509) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::PercentFormatUnsupportedFormatCharacter(c),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F501) {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::PercentFormatInvalidFormat(e.to_string()),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(summary) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F502) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_expected_mapping(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F503) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_expected_sequence(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F504) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_extra_named_arguments(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F505) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_missing_arguments(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F506) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_mixed_positional_and_named(
|
||||
&summary, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F507) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_positional_count_mismatch(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F508) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::percent_format_star_requires_sequence(
|
||||
&summary, right, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
|
||||
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
|
||||
@@ -1732,9 +1973,15 @@ where
|
||||
self.add_check(check);
|
||||
};
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B023) {
|
||||
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Generator));
|
||||
}
|
||||
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B023) {
|
||||
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Generator));
|
||||
}
|
||||
_ => {}
|
||||
@@ -1865,6 +2112,7 @@ where
|
||||
value,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
|member| self.is_builtin(member),
|
||||
) {
|
||||
Some(subscript) => {
|
||||
match subscript {
|
||||
@@ -1926,13 +2174,20 @@ where
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
|
||||
match &excepthandler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, name, .. } => {
|
||||
ExcepthandlerKind::ExceptHandler {
|
||||
type_, name, body, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B904) {
|
||||
{
|
||||
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
|
||||
}
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
@@ -2080,6 +2335,13 @@ where
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
|
||||
}
|
||||
|
||||
fn visit_withitem(&mut self, withitem: &'b Withitem) {
|
||||
let prev_in_withitem = self.in_withitem;
|
||||
self.in_withitem = true;
|
||||
walk_withitem(self, withitem);
|
||||
self.in_withitem = prev_in_withitem;
|
||||
}
|
||||
}
|
||||
|
||||
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
|
||||
@@ -2386,7 +2648,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
if operations::is_unpacking_assignment(parent) {
|
||||
if self.in_withitem || operations::is_unpacking_assignment(parent) {
|
||||
self.add_binding(
|
||||
id,
|
||||
Binding {
|
||||
|
||||
@@ -16,13 +16,16 @@ use crate::settings::Settings;
|
||||
static CODING_COMMENT_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").expect("Invalid regex"));
|
||||
|
||||
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").expect("Invalid regex"));
|
||||
|
||||
/// Whether the given line is too long and should be reported.
|
||||
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
if length > limit {
|
||||
let mut chunks = line.split_whitespace();
|
||||
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
|
||||
// Do not enforce the line length for commented lines with a single word
|
||||
!(first == "#" && chunks.next().is_none())
|
||||
// Do not enforce the line length for commented lines that end with a URL
|
||||
// or contain only a single word.
|
||||
!(first == "#" && chunks.last().map_or(false, |c| URL_REGEX.is_match(c)))
|
||||
} else {
|
||||
// Single word / no printable chars - no way to make the line shorter
|
||||
false
|
||||
@@ -38,6 +41,7 @@ pub fn check_lines(
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
ignore_noqa: bool,
|
||||
) {
|
||||
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
@@ -53,6 +57,30 @@ pub fn check_lines(
|
||||
assert!(check.location.row() >= 1);
|
||||
}
|
||||
|
||||
macro_rules! add_if {
|
||||
($check:expr, $noqa:expr) => {{
|
||||
match $noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push($check.kind.code().as_ref());
|
||||
if ignore_noqa {
|
||||
line_checks.push($check);
|
||||
}
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if codes.contains(&$check.kind.code().as_ref()) {
|
||||
matches.push($check.kind.code().as_ref());
|
||||
if ignore_noqa {
|
||||
line_checks.push($check);
|
||||
}
|
||||
} else {
|
||||
line_checks.push($check);
|
||||
}
|
||||
}
|
||||
(Directive::None, ..) => line_checks.push($check),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
@@ -65,21 +93,24 @@ pub fn check_lines(
|
||||
if lineno < 2 {
|
||||
// PEP3120 makes utf-8 the default encoding.
|
||||
if CODING_COMMENT_REGEX.is_match(line) {
|
||||
let line_length = line.len();
|
||||
let mut check = Check::new(
|
||||
CheckKind::PEP3120UnnecessaryCodingComment,
|
||||
Range {
|
||||
location: Location::new(lineno + 1, 0),
|
||||
end_location: Location::new(lineno + 1, line_length + 1),
|
||||
end_location: Location::new(lineno + 2, 0),
|
||||
},
|
||||
);
|
||||
if autofix && settings.fixable.contains(check.kind.code()) {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(lineno + 1, 0),
|
||||
Location::new(lineno + 1, line_length + 1),
|
||||
Location::new(lineno + 2, 0),
|
||||
));
|
||||
}
|
||||
line_checks.push(check);
|
||||
|
||||
let noqa = noqa_directives.entry(noqa_lineno).or_insert_with(|| {
|
||||
(noqa::extract_noqa_directive(lines[noqa_lineno]), vec![])
|
||||
});
|
||||
add_if!(check, noqa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +140,7 @@ pub fn check_lines(
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
(Directive::None, ..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +148,6 @@ pub fn check_lines(
|
||||
if enforce_line_too_long {
|
||||
let line_length = line.chars().count();
|
||||
if should_enforce_line_length(line, line_length, settings.line_length) {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Range {
|
||||
@@ -129,35 +156,19 @@ pub fn check_lines(
|
||||
},
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => line_checks.push(check),
|
||||
}
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
add_if!(check, noqa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce newlines at end of files.
|
||||
// Enforce newlines at end of files (W292).
|
||||
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
|
||||
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
|
||||
// want to raise W292 anyway).
|
||||
if let Some(line) = lines.last() {
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
|
||||
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::NoNewLineAtEndOfFile,
|
||||
Range {
|
||||
@@ -166,23 +177,16 @@ pub fn check_lines(
|
||||
},
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(.., codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => line_checks.push(check),
|
||||
}
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
add_if!(check, noqa);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used.
|
||||
// Enforce that the noqa directive was actually used (M001).
|
||||
if enforce_noqa {
|
||||
for (row, (directive, matches)) in noqa_directives {
|
||||
match directive {
|
||||
@@ -245,9 +249,11 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
ignored.sort_unstable();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.swap_remove(*index);
|
||||
if !ignore_noqa {
|
||||
ignored.sort_unstable();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.swap_remove(*index);
|
||||
}
|
||||
}
|
||||
checks.extend(line_checks);
|
||||
}
|
||||
@@ -275,6 +281,7 @@ mod tests {
|
||||
..Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
true,
|
||||
false,
|
||||
);
|
||||
checks
|
||||
};
|
||||
|
||||
247
src/checks.rs
247
src/checks.rs
@@ -7,6 +7,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::flake8_debugger::types::DebuggerUsingType;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::flake8_tidy_imports::settings::Strictness;
|
||||
use crate::pyupgrade::types::Primitive;
|
||||
@@ -52,6 +53,20 @@ pub enum CheckCode {
|
||||
F405,
|
||||
F406,
|
||||
F407,
|
||||
F501,
|
||||
F502,
|
||||
F503,
|
||||
F504,
|
||||
F505,
|
||||
F506,
|
||||
F507,
|
||||
F508,
|
||||
F509,
|
||||
F521,
|
||||
F522,
|
||||
F523,
|
||||
F524,
|
||||
F525,
|
||||
F541,
|
||||
F601,
|
||||
F602,
|
||||
@@ -99,10 +114,12 @@ pub enum CheckCode {
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B023,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
B904,
|
||||
// flake8-blind-except
|
||||
BLE001,
|
||||
// flake8-comprehensions
|
||||
@@ -122,6 +139,8 @@ pub enum CheckCode {
|
||||
C415,
|
||||
C416,
|
||||
C417,
|
||||
// flake8-debugger
|
||||
T100,
|
||||
// mccabe
|
||||
C901,
|
||||
// flake8-tidy-imports
|
||||
@@ -265,6 +284,7 @@ pub enum CheckCategory {
|
||||
PEP8Naming,
|
||||
Flake8Bandit,
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8BooleanTrap,
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
@@ -290,6 +310,7 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Debugger => "flake8-debugger",
|
||||
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
@@ -319,6 +340,9 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Comprehensions => {
|
||||
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
|
||||
}
|
||||
CheckCategory::Flake8Debugger => {
|
||||
Some("https://pypi.org/project/flake8-debugger/4.1.2/")
|
||||
}
|
||||
CheckCategory::Flake8TidyImports => {
|
||||
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
|
||||
}
|
||||
@@ -400,8 +424,22 @@ pub enum CheckKind {
|
||||
LateFutureImport,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments(Vec<String>),
|
||||
PercentFormatInvalidFormat(String),
|
||||
PercentFormatMissingArgument(Vec<String>),
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch(usize, usize),
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter(char),
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
StringDotFormatExtraNamedArguments(Vec<String>),
|
||||
StringDotFormatExtraPositionalArguments(Vec<String>),
|
||||
StringDotFormatInvalidFormat(String),
|
||||
StringDotFormatMissingArguments(Vec<String>),
|
||||
StringDotFormatMixingAutomatic,
|
||||
TwoStarredExpressions,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
@@ -416,31 +454,33 @@ pub enum CheckKind {
|
||||
// flake8-blind-except
|
||||
BlindExcept,
|
||||
// flake8-bugbear
|
||||
UnaryPrefixIncrement,
|
||||
AssignmentToOsEnviron,
|
||||
UnreliableCallableCheck,
|
||||
StripWithMultiCharacters,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault(Option<String>),
|
||||
GetAttrWithConstant,
|
||||
SetAttrWithConstant,
|
||||
DoNotAssertFalse,
|
||||
JumpStatementInFinally(String),
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
UselessComparison,
|
||||
CannotRaiseLiteral,
|
||||
NoAssertRaisesException,
|
||||
UselessExpression,
|
||||
CachedInstanceMethod,
|
||||
LoopVariableOverridesIterator(String),
|
||||
FStringDocstring,
|
||||
UselessContextlibSuppress,
|
||||
AbstractBaseClassWithoutAbstractMethod(String),
|
||||
AssignmentToOsEnviron,
|
||||
CachedInstanceMethod,
|
||||
CannotRaiseLiteral,
|
||||
DoNotAssertFalse,
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
DuplicateTryBlockException(String),
|
||||
StarArgUnpackingAfterKeywordArg,
|
||||
EmptyMethodWithoutAbstractDecorator(String),
|
||||
FStringDocstring,
|
||||
FunctionCallArgumentDefault(Option<String>),
|
||||
FunctionUsesLoopVariable(String),
|
||||
GetAttrWithConstant,
|
||||
JumpStatementInFinally(String),
|
||||
LoopVariableOverridesIterator(String),
|
||||
MutableArgumentDefault,
|
||||
NoAssertRaisesException,
|
||||
RaiseWithoutFromInsideExcept,
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
SetAttrWithConstant,
|
||||
StarArgUnpackingAfterKeywordArg,
|
||||
StripWithMultiCharacters,
|
||||
UnaryPrefixIncrement,
|
||||
UnreliableCallableCheck,
|
||||
UnusedLoopControlVariable(String),
|
||||
UselessComparison,
|
||||
UselessContextlibSuppress,
|
||||
UselessExpression,
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
@@ -458,6 +498,8 @@ pub enum CheckKind {
|
||||
UnnecessarySubscriptReversal(String),
|
||||
UnnecessaryComprehension(String),
|
||||
UnnecessaryMap(String),
|
||||
// flake8-debugger
|
||||
Debugger(DebuggerUsingType),
|
||||
// flake8-tidy-imports
|
||||
BannedRelativeImport(Strictness),
|
||||
// flake8-print
|
||||
@@ -644,6 +686,24 @@ impl CheckCode {
|
||||
}
|
||||
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckCode::F501 => CheckKind::PercentFormatInvalidFormat("...".to_string()),
|
||||
CheckCode::F502 => CheckKind::PercentFormatExpectedMapping,
|
||||
CheckCode::F503 => CheckKind::PercentFormatExpectedSequence,
|
||||
CheckCode::F504 => CheckKind::PercentFormatExtraNamedArguments(vec!["...".to_string()]),
|
||||
CheckCode::F505 => CheckKind::PercentFormatMissingArgument(vec!["...".to_string()]),
|
||||
CheckCode::F506 => CheckKind::PercentFormatMixedPositionalAndNamed,
|
||||
CheckCode::F507 => CheckKind::PercentFormatPositionalCountMismatch(4, 2),
|
||||
CheckCode::F508 => CheckKind::PercentFormatStarRequiresSequence,
|
||||
CheckCode::F509 => CheckKind::PercentFormatUnsupportedFormatCharacter('c'),
|
||||
CheckCode::F521 => CheckKind::StringDotFormatInvalidFormat("...".to_string()),
|
||||
CheckCode::F522 => {
|
||||
CheckKind::StringDotFormatExtraNamedArguments(vec!["...".to_string()])
|
||||
}
|
||||
CheckCode::F523 => {
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(vec!["...".to_string()])
|
||||
}
|
||||
CheckCode::F524 => CheckKind::StringDotFormatMissingArguments(vec!["...".to_string()]),
|
||||
CheckCode::F525 => CheckKind::StringDotFormatMixingAutomatic,
|
||||
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
|
||||
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
|
||||
@@ -695,10 +755,12 @@ impl CheckCode {
|
||||
CheckCode::B020 => CheckKind::LoopVariableOverridesIterator("...".to_string()),
|
||||
CheckCode::B021 => CheckKind::FStringDocstring,
|
||||
CheckCode::B022 => CheckKind::UselessContextlibSuppress,
|
||||
CheckCode::B023 => CheckKind::FunctionUsesLoopVariable("...".to_string()),
|
||||
CheckCode::B024 => CheckKind::AbstractBaseClassWithoutAbstractMethod("...".to_string()),
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
|
||||
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
|
||||
CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept,
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -729,6 +791,8 @@ impl CheckCode {
|
||||
}
|
||||
CheckCode::C416 => CheckKind::UnnecessaryComprehension("(list|set)".to_string()),
|
||||
CheckCode::C417 => CheckKind::UnnecessaryMap("(list|set|dict)".to_string()),
|
||||
// flake8-debugger
|
||||
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
|
||||
// flake8-tidy-imports
|
||||
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
|
||||
// flake8-print
|
||||
@@ -909,6 +973,20 @@ impl CheckCode {
|
||||
CheckCode::F405 => CheckCategory::Pyflakes,
|
||||
CheckCode::F406 => CheckCategory::Pyflakes,
|
||||
CheckCode::F407 => CheckCategory::Pyflakes,
|
||||
CheckCode::F501 => CheckCategory::Pyflakes,
|
||||
CheckCode::F502 => CheckCategory::Pyflakes,
|
||||
CheckCode::F503 => CheckCategory::Pyflakes,
|
||||
CheckCode::F504 => CheckCategory::Pyflakes,
|
||||
CheckCode::F505 => CheckCategory::Pyflakes,
|
||||
CheckCode::F506 => CheckCategory::Pyflakes,
|
||||
CheckCode::F507 => CheckCategory::Pyflakes,
|
||||
CheckCode::F508 => CheckCategory::Pyflakes,
|
||||
CheckCode::F509 => CheckCategory::Pyflakes,
|
||||
CheckCode::F521 => CheckCategory::Pyflakes,
|
||||
CheckCode::F522 => CheckCategory::Pyflakes,
|
||||
CheckCode::F523 => CheckCategory::Pyflakes,
|
||||
CheckCode::F524 => CheckCategory::Pyflakes,
|
||||
CheckCode::F525 => CheckCategory::Pyflakes,
|
||||
CheckCode::F541 => CheckCategory::Pyflakes,
|
||||
CheckCode::F601 => CheckCategory::Pyflakes,
|
||||
CheckCode::F602 => CheckCategory::Pyflakes,
|
||||
@@ -954,10 +1032,12 @@ impl CheckCode {
|
||||
CheckCode::B020 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B021 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B022 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B023 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B024 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B026 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B027 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B904 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::BLE001 => CheckCategory::Flake8BlindExcept,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -975,6 +1055,7 @@ impl CheckCode {
|
||||
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::T100 => CheckCategory::Flake8Debugger,
|
||||
CheckCode::I252 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
@@ -1130,8 +1211,22 @@ impl CheckKind {
|
||||
CheckKind::NoneComparison(_) => &CheckCode::E711,
|
||||
CheckKind::NotInTest => &CheckCode::E713,
|
||||
CheckKind::NotIsTest => &CheckCode::E714,
|
||||
CheckKind::PercentFormatExpectedMapping => &CheckCode::F502,
|
||||
CheckKind::PercentFormatExpectedSequence => &CheckCode::F503,
|
||||
CheckKind::PercentFormatExtraNamedArguments(_) => &CheckCode::F504,
|
||||
CheckKind::PercentFormatInvalidFormat(_) => &CheckCode::F501,
|
||||
CheckKind::PercentFormatMissingArgument(_) => &CheckCode::F505,
|
||||
CheckKind::PercentFormatMixedPositionalAndNamed => &CheckCode::F506,
|
||||
CheckKind::PercentFormatPositionalCountMismatch(..) => &CheckCode::F507,
|
||||
CheckKind::PercentFormatStarRequiresSequence => &CheckCode::F508,
|
||||
CheckKind::PercentFormatUnsupportedFormatCharacter(_) => &CheckCode::F509,
|
||||
CheckKind::RaiseNotImplemented => &CheckCode::F901,
|
||||
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
|
||||
CheckKind::StringDotFormatExtraNamedArguments(_) => &CheckCode::F522,
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(_) => &CheckCode::F523,
|
||||
CheckKind::StringDotFormatInvalidFormat(_) => &CheckCode::F521,
|
||||
CheckKind::StringDotFormatMissingArguments(_) => &CheckCode::F524,
|
||||
CheckKind::StringDotFormatMixingAutomatic => &CheckCode::F525,
|
||||
CheckKind::SyntaxError(_) => &CheckCode::E999,
|
||||
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
|
||||
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
|
||||
@@ -1151,31 +1246,33 @@ impl CheckKind {
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
|
||||
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
|
||||
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
|
||||
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
||||
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::UselessComparison => &CheckCode::B015,
|
||||
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::UselessExpression => &CheckCode::B018,
|
||||
CheckKind::CachedInstanceMethod => &CheckCode::B019,
|
||||
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
|
||||
CheckKind::FStringDocstring => &CheckCode::B021,
|
||||
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(_) => &CheckCode::B024,
|
||||
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
|
||||
CheckKind::CachedInstanceMethod => &CheckCode::B019,
|
||||
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
|
||||
CheckKind::FStringDocstring => &CheckCode::B021,
|
||||
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
|
||||
CheckKind::FunctionUsesLoopVariable(_) => &CheckCode::B023,
|
||||
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
||||
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
|
||||
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
|
||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::UselessComparison => &CheckCode::B015,
|
||||
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
|
||||
CheckKind::UselessExpression => &CheckCode::B018,
|
||||
// flake8-blind-except
|
||||
CheckKind::BlindExcept => &CheckCode::BLE001,
|
||||
// flake8-comprehensions
|
||||
@@ -1195,6 +1292,8 @@ impl CheckKind {
|
||||
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
|
||||
CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
|
||||
CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
|
||||
// flake8-debugger
|
||||
CheckKind::Debugger(_) => &CheckCode::T100,
|
||||
// flake8-tidy-imports
|
||||
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
|
||||
// flake8-print
|
||||
@@ -1411,12 +1510,59 @@ impl CheckKind {
|
||||
},
|
||||
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
|
||||
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
|
||||
CheckKind::PercentFormatInvalidFormat(message) => {
|
||||
format!("'...' % ... has invalid format string: {message}")
|
||||
}
|
||||
CheckKind::PercentFormatUnsupportedFormatCharacter(char) => {
|
||||
format!("'...' % ... has unsupported format character '{char}'")
|
||||
}
|
||||
CheckKind::PercentFormatExpectedMapping => {
|
||||
"'...' % ... expected mapping but got sequence".to_string()
|
||||
}
|
||||
CheckKind::PercentFormatExpectedSequence => {
|
||||
"'...' % ... expected sequence but got mapping".to_string()
|
||||
}
|
||||
CheckKind::PercentFormatExtraNamedArguments(missing) => {
|
||||
let message = missing.join(", ");
|
||||
format!("'...' % ... has unused named argument(s): {message}")
|
||||
}
|
||||
CheckKind::PercentFormatMissingArgument(missing) => {
|
||||
let message = missing.join(", ");
|
||||
format!("'...' % ... is missing argument(s) for placeholder(s): {message}")
|
||||
}
|
||||
CheckKind::PercentFormatMixedPositionalAndNamed => {
|
||||
"'...' % ... has mixed positional and named placeholders".to_string()
|
||||
}
|
||||
CheckKind::PercentFormatPositionalCountMismatch(wanted, got) => {
|
||||
format!("'...' % ... has {wanted} placeholder(s) but {got} substitution(s)")
|
||||
}
|
||||
CheckKind::PercentFormatStarRequiresSequence => {
|
||||
"'...' % ... `*` specifier requires sequence".to_string()
|
||||
}
|
||||
CheckKind::RaiseNotImplemented => {
|
||||
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
|
||||
}
|
||||
CheckKind::ReturnOutsideFunction => {
|
||||
"`return` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::StringDotFormatExtraNamedArguments(missing) => {
|
||||
let message = missing.join(", ");
|
||||
format!("'...'.format(...) has unused named argument(s): {message}")
|
||||
}
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(missing) => {
|
||||
let message = missing.join(", ");
|
||||
format!("'...'.format(...) has unused arguments at position(s): {message}")
|
||||
}
|
||||
CheckKind::StringDotFormatInvalidFormat(message) => {
|
||||
format!("'...'.format(...) has invalid format string: {message}")
|
||||
}
|
||||
CheckKind::StringDotFormatMissingArguments(missing) => {
|
||||
let message = missing.join(", ");
|
||||
format!("'...'.format(...) is missing argument(s) for placeholder(s): {message}")
|
||||
}
|
||||
CheckKind::StringDotFormatMixingAutomatic => {
|
||||
"'...'.format(...) mixes automatic and manual numbering".to_string()
|
||||
}
|
||||
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
|
||||
CheckKind::ExpressionsInStarAssignment => {
|
||||
"Too many expressions in star-unpacking assignment".to_string()
|
||||
@@ -1502,6 +1648,9 @@ impl CheckKind {
|
||||
"Do not perform function call in argument defaults".to_string()
|
||||
}
|
||||
}
|
||||
CheckKind::FunctionUsesLoopVariable(name) => {
|
||||
format!("Function definition does not bind loop variable `{name}`")
|
||||
}
|
||||
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
|
||||
value. It is not any safer than normal property \
|
||||
access."
|
||||
@@ -1580,6 +1729,11 @@ impl CheckKind {
|
||||
decorator"
|
||||
)
|
||||
}
|
||||
CheckKind::RaiseWithoutFromInsideExcept => {
|
||||
"Within an except clause, raise exceptions with raise ... from err or raise ... \
|
||||
from None to distinguish them from errors in exception handling"
|
||||
.to_string()
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
|
||||
@@ -1653,6 +1807,11 @@ impl CheckKind {
|
||||
format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)")
|
||||
}
|
||||
}
|
||||
// flake8-debugger
|
||||
CheckKind::Debugger(using_type) => match using_type {
|
||||
DebuggerUsingType::Call(name) => format!("Trace found: `{name}` used"),
|
||||
DebuggerUsingType::Import(name) => format!("Import for `{name}` found"),
|
||||
},
|
||||
// flake8-tidy-imports
|
||||
CheckKind::BannedRelativeImport(strictness) => match strictness {
|
||||
Strictness::Parents => {
|
||||
|
||||
@@ -59,10 +59,14 @@ pub enum CheckCodePrefix {
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B023,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
B9,
|
||||
B90,
|
||||
B904,
|
||||
BLE,
|
||||
BLE0,
|
||||
BLE00,
|
||||
@@ -183,6 +187,12 @@ pub enum CheckCodePrefix {
|
||||
F406,
|
||||
F407,
|
||||
F5,
|
||||
F52,
|
||||
F521,
|
||||
F522,
|
||||
F523,
|
||||
F524,
|
||||
F525,
|
||||
F54,
|
||||
F541,
|
||||
F6,
|
||||
@@ -280,6 +290,9 @@ pub enum CheckCodePrefix {
|
||||
S106,
|
||||
S107,
|
||||
T,
|
||||
T1,
|
||||
T10,
|
||||
T100,
|
||||
T2,
|
||||
T20,
|
||||
T201,
|
||||
@@ -412,10 +425,12 @@ impl CheckCodePrefix {
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B023,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
CheckCode::B904,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -439,6 +454,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B023,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
@@ -488,6 +504,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B023,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
@@ -496,10 +513,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B020 => vec![CheckCode::B020],
|
||||
CheckCodePrefix::B021 => vec![CheckCode::B021],
|
||||
CheckCodePrefix::B022 => vec![CheckCode::B022],
|
||||
CheckCodePrefix::B023 => vec![CheckCode::B023],
|
||||
CheckCodePrefix::B024 => vec![CheckCode::B024],
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B026 => vec![CheckCode::B026],
|
||||
CheckCodePrefix::B027 => vec![CheckCode::B027],
|
||||
CheckCodePrefix::B9 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B90 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B904 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::BLE => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE0 => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE00 => vec![CheckCode::BLE001],
|
||||
@@ -840,6 +861,20 @@ impl CheckCodePrefix {
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F501,
|
||||
CheckCode::F502,
|
||||
CheckCode::F503,
|
||||
CheckCode::F504,
|
||||
CheckCode::F505,
|
||||
CheckCode::F506,
|
||||
CheckCode::F507,
|
||||
CheckCode::F508,
|
||||
CheckCode::F509,
|
||||
CheckCode::F521,
|
||||
CheckCode::F522,
|
||||
CheckCode::F523,
|
||||
CheckCode::F524,
|
||||
CheckCode::F525,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
@@ -887,7 +922,26 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F405 => vec![CheckCode::F405],
|
||||
CheckCodePrefix::F406 => vec![CheckCode::F406],
|
||||
CheckCodePrefix::F407 => vec![CheckCode::F407],
|
||||
CheckCodePrefix::F5 => vec![CheckCode::F541],
|
||||
CheckCodePrefix::F5 => vec![
|
||||
CheckCode::F521,
|
||||
CheckCode::F522,
|
||||
CheckCode::F523,
|
||||
CheckCode::F524,
|
||||
CheckCode::F525,
|
||||
CheckCode::F541,
|
||||
],
|
||||
CheckCodePrefix::F52 => vec![
|
||||
CheckCode::F521,
|
||||
CheckCode::F522,
|
||||
CheckCode::F523,
|
||||
CheckCode::F524,
|
||||
CheckCode::F525,
|
||||
],
|
||||
CheckCodePrefix::F521 => vec![CheckCode::F521],
|
||||
CheckCodePrefix::F522 => vec![CheckCode::F522],
|
||||
CheckCodePrefix::F523 => vec![CheckCode::F523],
|
||||
CheckCodePrefix::F524 => vec![CheckCode::F524],
|
||||
CheckCodePrefix::F525 => vec![CheckCode::F525],
|
||||
CheckCodePrefix::F54 => vec![CheckCode::F541],
|
||||
CheckCodePrefix::F541 => vec![CheckCode::F541],
|
||||
CheckCodePrefix::F6 => vec![
|
||||
@@ -1107,7 +1161,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::S105 => vec![CheckCode::S105],
|
||||
CheckCodePrefix::S106 => vec![CheckCode::S106],
|
||||
CheckCodePrefix::S107 => vec![CheckCode::S107],
|
||||
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T1 => vec![CheckCode::T100],
|
||||
CheckCodePrefix::T10 => vec![CheckCode::T100],
|
||||
CheckCodePrefix::T100 => vec![CheckCode::T100],
|
||||
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T201 => vec![CheckCode::T201],
|
||||
@@ -1281,10 +1338,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B020 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B021 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B022 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B023 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B024 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B027 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B9 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::B90 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B904 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::BLE => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::BLE0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::BLE00 => PrefixSpecificity::Tens,
|
||||
@@ -1405,6 +1466,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F406 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F407 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F5 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::F52 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::F521 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F522 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F523 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F524 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F525 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F54 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::F541 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::F6 => PrefixSpecificity::Hundreds,
|
||||
@@ -1502,6 +1569,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::T => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::T1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::T10 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::T100 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::T201 => PrefixSpecificity::Explicit,
|
||||
@@ -1551,3 +1621,25 @@ impl CheckCodePrefix {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::A,
|
||||
CheckCodePrefix::ANN,
|
||||
CheckCodePrefix::B,
|
||||
CheckCodePrefix::BLE,
|
||||
CheckCodePrefix::C,
|
||||
CheckCodePrefix::D,
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::FBT,
|
||||
CheckCodePrefix::I,
|
||||
CheckCodePrefix::M,
|
||||
CheckCodePrefix::N,
|
||||
CheckCodePrefix::Q,
|
||||
CheckCodePrefix::RUF,
|
||||
CheckCodePrefix::S,
|
||||
CheckCodePrefix::T,
|
||||
CheckCodePrefix::U,
|
||||
CheckCodePrefix::W,
|
||||
CheckCodePrefix::YTT,
|
||||
];
|
||||
|
||||
23
src/cli.rs
23
src/cli.rs
@@ -1,6 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -9,14 +8,14 @@ use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "Ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
#[arg(required = true)]
|
||||
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Path to the `pyproject.toml` file to use for configuration.
|
||||
#[arg(long)]
|
||||
@@ -61,11 +60,11 @@ pub struct Cli {
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the
|
||||
/// excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<String>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
/// List of error codes to treat as eligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
@@ -78,7 +77,7 @@ pub struct Cli {
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<PatternPrefixPair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
#[arg(long, value_enum, default_value_t = SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
/// Show violations with source code.
|
||||
#[arg(long)]
|
||||
@@ -115,6 +114,9 @@ pub struct Cli {
|
||||
/// Explain a rule.
|
||||
#[arg(long)]
|
||||
pub explain: Option<CheckCode>,
|
||||
/// Generate shell completion
|
||||
#[arg(long, hide = true, value_name = "SHELL")]
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
@@ -149,10 +151,7 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
|
||||
}
|
||||
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<Vec<PerFileIgnore>> {
|
||||
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<CheckCodePrefix>> = FxHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
@@ -161,7 +160,7 @@ pub fn collect_per_file_ignores(
|
||||
.push(pair.prefix);
|
||||
}
|
||||
per_file_ignores
|
||||
.iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, project_root))
|
||||
.into_iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes))
|
||||
.collect()
|
||||
}
|
||||
|
||||
66
src/commands.rs
Normal file
66
src/commands.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::fs::iter_python_files;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::{Configuration, Settings};
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub fn show_settings(
|
||||
configuration: &Configuration,
|
||||
project_root: Option<&PathBuf>,
|
||||
pyproject: Option<&PathBuf>,
|
||||
) {
|
||||
println!("Resolved configuration: {configuration:#?}");
|
||||
println!("Found project root at: {project_root:?}");
|
||||
println!("Found pyproject.toml at: {pyproject:?}");
|
||||
}
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
let mut entries: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.flatten()
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.path().cmp(b.path()));
|
||||
for entry in entries {
|
||||
println!("{}", entry.path().to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Explanation<'a> {
|
||||
code: &'a str,
|
||||
category: &'a str,
|
||||
summary: &'a str,
|
||||
}
|
||||
|
||||
/// Explain a `CheckCode` to the user.
|
||||
pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
|
||||
match format {
|
||||
SerializationFormat::Text => {
|
||||
println!(
|
||||
"{} ({}): {}",
|
||||
code.as_ref(),
|
||||
code.category().title(),
|
||||
code.kind().summary()
|
||||
);
|
||||
}
|
||||
SerializationFormat::Json => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&Explanation {
|
||||
code: code.as_ref(),
|
||||
category: code.category().title(),
|
||||
summary: &code.kind().summary(),
|
||||
})?
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -21,7 +21,7 @@ where
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
// No recurse.
|
||||
// Don't recurse.
|
||||
}
|
||||
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
|
||||
232
src/flake8_bugbear/plugins/function_uses_loop_variable.rs
Normal file
232
src/flake8_bugbear/plugins/function_uses_loop_variable.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Comprehension, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::collect_arg_names;
|
||||
use crate::ast::types::{Node, Range};
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
#[derive(Default)]
|
||||
struct LoadedNamesVisitor<'a> {
|
||||
// Tuple of: name, defining expression, and defining range.
|
||||
names: Vec<(&'a str, &'a Expr, Range)>,
|
||||
// If we're in an f-string, the range of the defining expression.
|
||||
in_f_string: Option<Range>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a, 'b> Visitor<'b> for LoadedNamesVisitor<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::JoinedStr { .. } => {
|
||||
let prev_in_f_string = self.in_f_string;
|
||||
self.in_f_string = Some(Range::from_located(expr));
|
||||
visitor::walk_expr(self, expr);
|
||||
self.in_f_string = prev_in_f_string;
|
||||
}
|
||||
ExprKind::Name { id, ctx } if matches!(ctx, ExprContext::Load) => {
|
||||
self.names.push((
|
||||
id,
|
||||
expr,
|
||||
self.in_f_string
|
||||
.unwrap_or_else(|| Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SuspiciousVariablesVisitor<'a> {
|
||||
names: Vec<(&'a str, &'a Expr, Range)>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all suspicious variables (those referenced in
|
||||
/// functions, but not bound as arguments).
|
||||
impl<'a, 'b> Visitor<'b> for SuspiciousVariablesVisitor<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { args, body, .. }
|
||||
| StmtKind::AsyncFunctionDef { args, body, .. } => {
|
||||
// Collect all loaded variable names.
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
|
||||
// Collect all argument names.
|
||||
let arg_names = collect_arg_names(args);
|
||||
|
||||
// Treat any non-arguments as "suspicious".
|
||||
self.names.extend(
|
||||
visitor
|
||||
.names
|
||||
.into_iter()
|
||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
||||
);
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::Lambda { args, body } => {
|
||||
// Collect all loaded variable names.
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
visitor.visit_expr(body);
|
||||
|
||||
// Collect all argument names.
|
||||
let arg_names = collect_arg_names(args);
|
||||
|
||||
// Treat any non-arguments as "suspicious".
|
||||
self.names.extend(
|
||||
visitor
|
||||
.names
|
||||
.into_iter()
|
||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
||||
);
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NamesFromAssignmentsVisitor<'a> {
|
||||
names: FxHashSet<&'a str>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all names used in an assignment expression.
|
||||
impl<'a, 'b> Visitor<'b> for NamesFromAssignmentsVisitor<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::Name { id, .. } => {
|
||||
self.names.insert(id.as_str());
|
||||
}
|
||||
ExprKind::Starred { value, .. } => {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
|
||||
for expr in elts {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssignedNamesVisitor<'a> {
|
||||
names: FxHashSet<&'a str>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a, 'b> Visitor<'b> for AssignedNamesVisitor<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
if matches!(
|
||||
&stmt.node,
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
|
||||
) {
|
||||
// Don't recurse.
|
||||
return;
|
||||
}
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::Assign { targets, .. } => {
|
||||
let mut visitor = NamesFromAssignmentsVisitor::default();
|
||||
for expr in targets {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
self.names.extend(visitor.names);
|
||||
}
|
||||
StmtKind::AugAssign { target, .. }
|
||||
| StmtKind::AnnAssign { target, .. }
|
||||
| StmtKind::For { target, .. }
|
||||
| StmtKind::AsyncFor { target, .. } => {
|
||||
let mut visitor = NamesFromAssignmentsVisitor::default();
|
||||
visitor.visit_expr(target);
|
||||
self.names.extend(visitor.names);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
visitor::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
if matches!(&expr.node, ExprKind::Lambda { .. }) {
|
||||
// Don't recurse.
|
||||
return;
|
||||
}
|
||||
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_comprehension(&mut self, comprehension: &'b Comprehension) {
|
||||
let mut visitor = NamesFromAssignmentsVisitor::default();
|
||||
visitor.visit_expr(&comprehension.target);
|
||||
self.names.extend(visitor.names);
|
||||
|
||||
visitor::walk_comprehension(self, comprehension);
|
||||
}
|
||||
}
|
||||
|
||||
/// B023
|
||||
pub fn function_uses_loop_variable<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>)
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
// Identify any "suspicious" variables. These are defined as variables that are
|
||||
// referenced in a function or lambda body, but aren't bound as arguments.
|
||||
let suspicious_variables = {
|
||||
let mut visitor = SuspiciousVariablesVisitor::<'b>::default();
|
||||
match node {
|
||||
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
|
||||
Node::Expr(expr) => visitor.visit_expr(expr),
|
||||
}
|
||||
visitor.names
|
||||
};
|
||||
|
||||
if !suspicious_variables.is_empty() {
|
||||
// Identify any variables that are assigned in the loop (ignoring functions).
|
||||
let reassigned_in_loop = {
|
||||
let mut visitor = AssignedNamesVisitor::<'b>::default();
|
||||
match node {
|
||||
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
|
||||
Node::Expr(expr) => visitor.visit_expr(expr),
|
||||
}
|
||||
visitor.names
|
||||
};
|
||||
|
||||
// If a variable was used in a function or lambda body, and assigned in the
|
||||
// loop, flag it.
|
||||
for (name, expr, range) in suspicious_variables {
|
||||
if reassigned_in_loop.contains(name) {
|
||||
if !checker.seen_b023.contains(&expr) {
|
||||
checker.seen_b023.push(expr);
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::FunctionUsesLoopVariable(name.to_string()),
|
||||
range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@ pub use cannot_raise_literal::cannot_raise_literal;
|
||||
pub use duplicate_exceptions::duplicate_exceptions;
|
||||
pub use f_string_docstring::f_string_docstring;
|
||||
pub use function_call_argument_default::function_call_argument_default;
|
||||
pub use function_uses_loop_variable::function_uses_loop_variable;
|
||||
pub use getattr_with_constant::getattr_with_constant;
|
||||
pub use jump_statement_in_finally::jump_statement_in_finally;
|
||||
pub use loop_variable_overrides_iterator::loop_variable_overrides_iterator;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use raise_without_from_inside_except::raise_without_from_inside_except;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use setattr_with_constant::setattr_with_constant;
|
||||
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
|
||||
@@ -31,10 +33,12 @@ mod cannot_raise_literal;
|
||||
mod duplicate_exceptions;
|
||||
mod f_string_docstring;
|
||||
mod function_call_argument_default;
|
||||
mod function_uses_loop_variable;
|
||||
mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod mutable_argument_default;
|
||||
mod raise_without_from_inside_except;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod setattr_with_constant;
|
||||
mod star_arg_unpacking_after_keyword_arg;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use rustpython_ast::{ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::python::string::is_lower;
|
||||
|
||||
struct RaiseVisitor {
|
||||
checks: Vec<Check>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for RaiseVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match &stmt.node {
|
||||
StmtKind::Raise { exc, cause } => {
|
||||
if cause.is_none() {
|
||||
if let Some(exc) = exc {
|
||||
match &exc.node {
|
||||
ExprKind::Name { id, .. } if is_lower(id) => {}
|
||||
_ => {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::RaiseWithoutFromInsideExcept,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef { .. }
|
||||
| StmtKind::FunctionDef { .. }
|
||||
| StmtKind::AsyncFunctionDef { .. }
|
||||
| StmtKind::Try { .. } => {}
|
||||
StmtKind::If { body, .. }
|
||||
| StmtKind::While { body, .. }
|
||||
| StmtKind::With { body, .. }
|
||||
| StmtKind::AsyncWith { body, .. }
|
||||
| StmtKind::For { body, .. }
|
||||
| StmtKind::AsyncFor { body, .. } => {
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
|
||||
let mut visitor = RaiseVisitor { checks: vec![] };
|
||||
for stmt in body {
|
||||
visitor.visit_stmt(stmt);
|
||||
}
|
||||
checker.add_checks(visitor.checks.into_iter());
|
||||
}
|
||||
@@ -39,9 +39,7 @@ pub fn fix_unnecessary_generator_list(
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
let Expression::GeneratorExp(generator_exp) = &arg.value else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
@@ -82,9 +80,7 @@ pub fn fix_unnecessary_generator_set(
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
let Expression::GeneratorExp(generator_exp) = &arg.value else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
@@ -126,28 +122,20 @@ pub fn fix_unnecessary_generator_dict(
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
// Extract the (k, v) from `(k, v) for ...`.
|
||||
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
|
||||
generator_exp
|
||||
} else {
|
||||
let Expression::GeneratorExp(generator_exp) = &arg.value else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
};
|
||||
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
|
||||
tuple
|
||||
} else {
|
||||
let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
|
||||
};
|
||||
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
|
||||
value
|
||||
} else {
|
||||
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the first element"
|
||||
));
|
||||
};
|
||||
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
|
||||
value
|
||||
} else {
|
||||
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the second element"
|
||||
));
|
||||
@@ -192,9 +180,7 @@ pub fn fix_unnecessary_list_comprehension_set(
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
|
||||
list_comp
|
||||
} else {
|
||||
let Expression::ListComp(list_comp) = &arg.value else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
|
||||
};
|
||||
|
||||
@@ -233,25 +219,18 @@ pub fn fix_unnecessary_list_comprehension_dict(
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
|
||||
list_comp
|
||||
} else {
|
||||
let Expression::ListComp(list_comp) = &arg.value else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
|
||||
};
|
||||
|
||||
let tuple = if let Expression::Tuple(tuple) = &*list_comp.elt {
|
||||
tuple
|
||||
} else {
|
||||
let Expression::Tuple(tuple) = &*list_comp.elt else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
|
||||
};
|
||||
|
||||
let (key, comma, value) = match &tuple.elements[..] {
|
||||
[Element::Simple {
|
||||
let [Element::Simple {
|
||||
value: key,
|
||||
comma: Some(comma),
|
||||
}, Element::Simple { value, .. }] => (key, comma, value),
|
||||
_ => return Err(anyhow::anyhow!("Expected tuple with two elements")),
|
||||
};
|
||||
}, Element::Simple { value, .. }] = &tuple.elements[..] else { return Err(anyhow::anyhow!("Expected tuple with two elements")) };
|
||||
|
||||
body.value = Expression::DictComp(Box::new(DictComp {
|
||||
key: Box::new(key.clone()),
|
||||
@@ -409,9 +388,7 @@ pub fn fix_unnecessary_collection_call(
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let name = if let Expression::Name(name) = &call.func.as_ref() {
|
||||
name
|
||||
} else {
|
||||
let Expression::Name(name) = &call.func.as_ref() else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Name"));
|
||||
};
|
||||
|
||||
|
||||
64
src/flake8_debugger/checks.rs
Normal file
64
src/flake8_debugger/checks.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_debugger::types::DebuggerUsingType;
|
||||
|
||||
const DEBUGGERS: &[(&str, &str)] = &[
|
||||
("pdb", "set_trace"),
|
||||
("pudb", "set_trace"),
|
||||
("ipdb", "set_trace"),
|
||||
("ipdb", "sset_trace"),
|
||||
("IPython.terminal.embed", "InteractiveShellEmbed"),
|
||||
("IPython.frontend.terminal.embed", "InteractiveShellEmbed"),
|
||||
("celery.contrib.rdb", "set_trace"),
|
||||
("builtins", "breakpoint"),
|
||||
("", "breakpoint"),
|
||||
];
|
||||
|
||||
/// Checks for the presence of a debugger call.
|
||||
pub fn debugger_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
|
||||
import_aliases: &FxHashMap<&str, &str>,
|
||||
) -> Option<Check> {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), import_aliases);
|
||||
if DEBUGGERS
|
||||
.iter()
|
||||
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
|
||||
{
|
||||
Some(Check::new(
|
||||
CheckKind::Debugger(DebuggerUsingType::Call(call_path.join("."))),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the presence of a debugger import.
|
||||
pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Check> {
|
||||
if let Some(module) = module {
|
||||
if let Some((module_name, member)) = DEBUGGERS
|
||||
.iter()
|
||||
.find(|(module_name, member)| module_name == &module && member == &name)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::Debugger(DebuggerUsingType::Import(format!("{module_name}.{member}"))),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
} else if DEBUGGERS
|
||||
.iter()
|
||||
.any(|(module_name, ..)| module_name == &name)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::Debugger(DebuggerUsingType::Import(name.to_string())),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
2
src/flake8_debugger/mod.rs
Normal file
2
src/flake8_debugger/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod checks;
|
||||
pub mod types;
|
||||
7
src/flake8_debugger/types.rs
Normal file
7
src/flake8_debugger/types.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DebuggerUsingType {
|
||||
Call(String),
|
||||
Import(String),
|
||||
}
|
||||
152
src/fs.rs
152
src/fs.rs
@@ -1,16 +1,16 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore};
|
||||
|
||||
/// Extract the absolute path and basename (as strings) from a Path.
|
||||
fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
@@ -25,32 +25,8 @@ fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
Ok((file_path, file_basename))
|
||||
}
|
||||
|
||||
fn is_excluded<'a, T>(file_path: &str, file_basename: &str, exclude: T) -> bool
|
||||
where
|
||||
T: Iterator<Item = &'a FilePattern>,
|
||||
{
|
||||
for pattern in exclude {
|
||||
match pattern {
|
||||
FilePattern::Simple(basename) => {
|
||||
if *basename == file_basename {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
FilePattern::Complex(absolute, basename) => {
|
||||
if absolute.matches(file_path) {
|
||||
return true;
|
||||
}
|
||||
if basename
|
||||
.as_ref()
|
||||
.map(|pattern| pattern.matches(file_basename))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
false
|
||||
fn is_excluded(file_path: &str, file_basename: &str, exclude: &globset::GlobSet) -> bool {
|
||||
exclude.is_match(file_path) || exclude.is_match(file_basename)
|
||||
}
|
||||
|
||||
fn is_included(path: &Path) -> bool {
|
||||
@@ -60,18 +36,12 @@ fn is_included(path: &Path) -> bool {
|
||||
|
||||
pub fn iter_python_files<'a>(
|
||||
path: &'a Path,
|
||||
exclude: &'a [FilePattern],
|
||||
extend_exclude: &'a [FilePattern],
|
||||
exclude: &'a globset::GlobSet,
|
||||
extend_exclude: &'a globset::GlobSet,
|
||||
) -> impl Iterator<Item = Result<DirEntry, walkdir::Error>> + 'a {
|
||||
// Run some checks over the provided patterns, to enable optimizations below.
|
||||
let has_exclude = !exclude.is_empty();
|
||||
let has_extend_exclude = !extend_exclude.is_empty();
|
||||
let exclude_simple = exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
let extend_exclude_simple = extend_exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
|
||||
WalkDir::new(normalize_path(path))
|
||||
.into_iter()
|
||||
@@ -83,17 +53,11 @@ pub fn iter_python_files<'a>(
|
||||
let path = entry.path();
|
||||
match extract_path_names(path) {
|
||||
Ok((file_path, file_basename)) => {
|
||||
let file_type = entry.file_type();
|
||||
|
||||
if has_exclude
|
||||
&& (!exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, exclude.iter())
|
||||
{
|
||||
if has_exclude && is_excluded(file_path, file_basename, exclude) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
false
|
||||
} else if has_extend_exclude
|
||||
&& (!extend_exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, extend_exclude.iter())
|
||||
&& is_excluded(file_path, file_basename, extend_exclude)
|
||||
{
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
false
|
||||
@@ -119,19 +83,15 @@ pub fn iter_python_files<'a>(
|
||||
/// Create tree set with codes matching the pattern/code pairs.
|
||||
pub(crate) fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [PerFileIgnore],
|
||||
) -> Result<FxHashSet<&'a CheckCode>> {
|
||||
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
let (file_path, file_basename) = extract_path_names(path)?;
|
||||
Ok(pattern_code_pairs
|
||||
.iter()
|
||||
.filter(|pattern_code_pair| {
|
||||
is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
[&pattern_code_pair.pattern].into_iter(),
|
||||
)
|
||||
.filter(|(absolute, basename, _)| {
|
||||
basename.is_match(file_basename) || absolute.is_match(file_path)
|
||||
})
|
||||
.flat_map(|pattern_code_pair| &pattern_code_pair.codes)
|
||||
.flat_map(|(_, _, codes)| codes)
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -171,9 +131,10 @@ pub(crate) fn read_file(path: &Path) -> Result<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::GlobSet;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::fs::{extract_path_names, is_excluded, is_included};
|
||||
@@ -194,73 +155,86 @@ mod tests {
|
||||
assert!(!is_included(&path));
|
||||
}
|
||||
|
||||
fn make_exclusion(file_pattern: FilePattern, project_root: Option<&PathBuf>) -> GlobSet {
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
file_pattern.add_to(&mut builder, project_root).unwrap();
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusions() -> Result<()> {
|
||||
let project_root = Path::new("/tmp/");
|
||||
|
||||
let path = Path::new("foo").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"bar",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("bar".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("baz.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/baz.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar/baz.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/*.py",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("foo/bar/*.py".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz",
|
||||
Some(&project_root.to_path_buf()),
|
||||
)?];
|
||||
let exclude = FilePattern::User("baz".to_string());
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(!is_excluded(file_path, file_basename, exclude.iter()));
|
||||
assert!(!is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
&make_exclusion(exclude, Some(&project_root.to_path_buf()))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -35,6 +35,7 @@ pub mod checks;
|
||||
pub mod checks_gen;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
pub mod commands;
|
||||
mod cst;
|
||||
mod directives;
|
||||
mod docstrings;
|
||||
@@ -46,6 +47,7 @@ pub mod flake8_boolean_trap;
|
||||
pub mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
mod flake8_debugger;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
pub mod flake8_tidy_imports;
|
||||
@@ -70,6 +72,7 @@ pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
#[cfg(feature = "update-informer")]
|
||||
pub mod updates;
|
||||
mod vendored;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
@@ -86,10 +89,10 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_configuration(Configuration::from_pyproject(
|
||||
pyproject.as_ref(),
|
||||
let settings = Settings::from_configuration(
|
||||
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?,
|
||||
project_root.as_ref(),
|
||||
)?);
|
||||
)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
@@ -113,6 +116,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
&directives,
|
||||
&settings,
|
||||
autofix,
|
||||
false,
|
||||
)?;
|
||||
|
||||
Ok(checks)
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::path::Path;
|
||||
use anyhow::Result;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use log::debug;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -46,6 +47,7 @@ impl AddAssign for Diagnostics {
|
||||
|
||||
/// Generate a list of `Check` violations from the source code contents at the
|
||||
/// given `Path`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
@@ -54,6 +56,7 @@ pub(crate) fn check_path(
|
||||
directives: &Directives,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
ignore_noqa: bool,
|
||||
) -> Result<Vec<Check>> {
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
@@ -113,6 +116,7 @@ pub(crate) fn check_path(
|
||||
&directives.noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
ignore_noqa,
|
||||
);
|
||||
|
||||
// Create path ignores.
|
||||
@@ -179,6 +183,7 @@ pub fn lint_path(
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
@@ -242,15 +247,19 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
directives::Flags::from_settings(settings),
|
||||
);
|
||||
|
||||
// Generate checks.
|
||||
// Generate checks, ignoring any existing `noqa` directives.
|
||||
let checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&directives,
|
||||
&Directives {
|
||||
noqa_line_for: IntMap::default(),
|
||||
isort_exclusions: directives.isort_exclusions,
|
||||
},
|
||||
settings,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
|
||||
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
|
||||
@@ -313,6 +322,7 @@ pub fn lint_stdin(
|
||||
&directives,
|
||||
settings,
|
||||
autofix.into(),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
@@ -373,6 +383,7 @@ pub fn test_path(path: &Path, settings: &Settings, autofix: bool) -> Result<Vec<
|
||||
&directives,
|
||||
settings,
|
||||
autofix,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -413,10 +424,12 @@ mod tests {
|
||||
#[test_case(CheckCode::B020, Path::new("B020.py"); "B020")]
|
||||
#[test_case(CheckCode::B021, Path::new("B021.py"); "B021")]
|
||||
#[test_case(CheckCode::B022, Path::new("B022.py"); "B022")]
|
||||
#[test_case(CheckCode::B023, Path::new("B023.py"); "B023")]
|
||||
#[test_case(CheckCode::B024, Path::new("B024.py"); "B024")]
|
||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
|
||||
#[test_case(CheckCode::B027, Path::new("B027.py"); "B027")]
|
||||
#[test_case(CheckCode::B904, Path::new("B904.py"); "B904")]
|
||||
#[test_case(CheckCode::BLE001, Path::new("BLE.py"); "BLE001")]
|
||||
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
||||
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
||||
@@ -505,6 +518,24 @@ mod tests {
|
||||
#[test_case(CheckCode::F405, Path::new("F405.py"); "F405")]
|
||||
#[test_case(CheckCode::F406, Path::new("F406.py"); "F406")]
|
||||
#[test_case(CheckCode::F407, Path::new("F407.py"); "F407")]
|
||||
#[test_case(CheckCode::F501, Path::new("F50x.py"); "F501")]
|
||||
#[test_case(CheckCode::F502, Path::new("F50x.py"); "F502_0")]
|
||||
#[test_case(CheckCode::F502, Path::new("F502.py"); "F502_1")]
|
||||
#[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")]
|
||||
#[test_case(CheckCode::F503, Path::new("F503.py"); "F503_1")]
|
||||
#[test_case(CheckCode::F504, Path::new("F50x.py"); "F504_0")]
|
||||
#[test_case(CheckCode::F504, Path::new("F504.py"); "F504_1")]
|
||||
#[test_case(CheckCode::F505, Path::new("F50x.py"); "F505_0")]
|
||||
#[test_case(CheckCode::F505, Path::new("F504.py"); "F505_1")]
|
||||
#[test_case(CheckCode::F506, Path::new("F50x.py"); "F506")]
|
||||
#[test_case(CheckCode::F507, Path::new("F50x.py"); "F507")]
|
||||
#[test_case(CheckCode::F508, Path::new("F50x.py"); "F508")]
|
||||
#[test_case(CheckCode::F509, Path::new("F50x.py"); "F509")]
|
||||
#[test_case(CheckCode::F521, Path::new("F521.py"); "F521")]
|
||||
#[test_case(CheckCode::F522, Path::new("F522.py"); "F522")]
|
||||
#[test_case(CheckCode::F523, Path::new("F523.py"); "F523")]
|
||||
#[test_case(CheckCode::F524, Path::new("F524.py"); "F524")]
|
||||
#[test_case(CheckCode::F525, Path::new("F525.py"); "F525")]
|
||||
#[test_case(CheckCode::F541, Path::new("F541.py"); "F541")]
|
||||
#[test_case(CheckCode::F601, Path::new("F601.py"); "F601")]
|
||||
#[test_case(CheckCode::F602, Path::new("F602.py"); "F602")]
|
||||
@@ -551,6 +582,7 @@ mod tests {
|
||||
#[test_case(CheckCode::S105, Path::new("S105.py"); "S105")]
|
||||
#[test_case(CheckCode::S106, Path::new("S106.py"); "S106")]
|
||||
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
|
||||
#[test_case(CheckCode::T100, Path::new("T100.py"); "T100")]
|
||||
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
|
||||
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
|
||||
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]
|
||||
@@ -564,6 +596,7 @@ mod tests {
|
||||
#[test_case(CheckCode::U009, Path::new("U009_1.py"); "U009_1")]
|
||||
#[test_case(CheckCode::U009, Path::new("U009_2.py"); "U009_2")]
|
||||
#[test_case(CheckCode::U009, Path::new("U009_3.py"); "U009_3")]
|
||||
#[test_case(CheckCode::U009, Path::new("U009_4.py"); "U009_4")]
|
||||
#[test_case(CheckCode::U010, Path::new("U010.py"); "U010")]
|
||||
#[test_case(CheckCode::U011, Path::new("U011_0.py"); "U011_0")]
|
||||
#[test_case(CheckCode::U011, Path::new("U011_1.py"); "U011_1")]
|
||||
|
||||
107
src/main.rs
107
src/main.rs
@@ -17,30 +17,26 @@ use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use ::ruff::cache;
|
||||
use ::ruff::checks::{CheckCode, CheckKind};
|
||||
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
|
||||
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::message::Message;
|
||||
use ::ruff::printer::{Printer, SerializationFormat};
|
||||
use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::FilePattern;
|
||||
use ::ruff::settings::user::UserConfiguration;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{cache, commands};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use ruff::linter::Diagnostics;
|
||||
use rustpython_ast::Location;
|
||||
use serde::Serialize;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
/// Shim that calls `par_iter` except for wasm because there's no wasm support
|
||||
@@ -57,60 +53,6 @@ fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl Iterator<Item = &T> {
|
||||
iterable.iter()
|
||||
}
|
||||
|
||||
fn show_settings(
|
||||
configuration: Configuration,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) {
|
||||
println!(
|
||||
"{:#?}",
|
||||
UserConfiguration::from_configuration(configuration, project_root, pyproject)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Explanation<'a> {
|
||||
code: &'a str,
|
||||
category: &'a str,
|
||||
summary: &'a str,
|
||||
}
|
||||
|
||||
fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
|
||||
match format {
|
||||
SerializationFormat::Text => {
|
||||
println!(
|
||||
"{} ({}): {}",
|
||||
code.as_ref(),
|
||||
code.category().title(),
|
||||
code.kind().summary()
|
||||
);
|
||||
}
|
||||
SerializationFormat::Json => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&Explanation {
|
||||
code: code.as_ref(),
|
||||
category: code.category().title(),
|
||||
summary: &code.kind().summary(),
|
||||
})?
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
let mut entries: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.flatten()
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.path().cmp(b.path()));
|
||||
for entry in entries {
|
||||
println!("{}", entry.path().to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_stdin() -> Result<String> {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().lock().read_to_string(&mut buffer)?;
|
||||
@@ -251,6 +193,16 @@ fn inner_main() -> Result<ExitCode> {
|
||||
let log_level = extract_log_level(&cli);
|
||||
set_up_logging(&log_level)?;
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, cli.format)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
if let Some(shell) = cli.generate_shell_completion {
|
||||
shell.generate(&mut Cli::command(), &mut std::io::stdout());
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&cli.files);
|
||||
match &project_root {
|
||||
@@ -266,28 +218,16 @@ fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
// Reconcile configuration from pyproject.toml and command-line arguments.
|
||||
let exclude: Vec<FilePattern> = cli
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
|
||||
.collect::<Result<_>>()?;
|
||||
let extend_exclude: Vec<FilePattern> = cli
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root.as_ref()))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let mut configuration =
|
||||
Configuration::from_pyproject(pyproject.as_ref(), project_root.as_ref())?;
|
||||
if !exclude.is_empty() {
|
||||
configuration.exclude = exclude;
|
||||
if !cli.exclude.is_empty() {
|
||||
configuration.exclude = cli.exclude;
|
||||
}
|
||||
if !extend_exclude.is_empty() {
|
||||
configuration.extend_exclude = extend_exclude;
|
||||
if !cli.extend_exclude.is_empty() {
|
||||
configuration.extend_exclude = cli.extend_exclude;
|
||||
}
|
||||
if !cli.per_file_ignores.is_empty() {
|
||||
configuration.per_file_ignores =
|
||||
collect_per_file_ignores(cli.per_file_ignores, project_root.as_ref())?;
|
||||
configuration.per_file_ignores = collect_per_file_ignores(cli.per_file_ignores);
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
configuration.select = cli.select;
|
||||
@@ -326,26 +266,21 @@ fn inner_main() -> Result<ExitCode> {
|
||||
configuration.show_source = true;
|
||||
}
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
explain(&code, cli.format)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
eprintln!("Error: specify --show-settings or show-files (not both).");
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(configuration, project_root, pyproject);
|
||||
commands::show_settings(&configuration, project_root.as_ref(), pyproject.as_ref());
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Extract settings for internal use.
|
||||
let fix_enabled: bool = configuration.fix;
|
||||
let settings = Settings::from_configuration(configuration);
|
||||
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
|
||||
|
||||
if cli.show_files {
|
||||
show_files(&cli.files, &settings);
|
||||
commands::show_files(&cli.files, &settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
38
src/noqa.rs
38
src/noqa.rs
@@ -99,18 +99,42 @@ fn add_noqa_inner(
|
||||
Some(codes) => {
|
||||
match extract_noqa_directive(line) {
|
||||
Directive::None => {
|
||||
output.push_str(line);
|
||||
// Add existing content.
|
||||
output.push_str(line.trim_end());
|
||||
|
||||
// Add `noqa` directive.
|
||||
output.push_str(" # noqa: ");
|
||||
|
||||
// Add codes.
|
||||
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
|
||||
let suffix = codes.join(", ");
|
||||
output.push_str(&suffix);
|
||||
output.push('\n');
|
||||
count += 1;
|
||||
}
|
||||
Directive::All(_, start, _) | Directive::Codes(_, start, ..) => {
|
||||
output.push_str(&line[..start]);
|
||||
output.push_str("# noqa: ");
|
||||
let mut new_line = String::new();
|
||||
|
||||
// Add existing content.
|
||||
new_line.push_str(line[..start].trim_end());
|
||||
|
||||
// Add `noqa` directive.
|
||||
new_line.push_str(" # noqa: ");
|
||||
|
||||
// Add codes.
|
||||
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
|
||||
let suffix = codes.join(", ");
|
||||
new_line.push_str(&suffix);
|
||||
|
||||
output.push_str(&new_line);
|
||||
output.push('\n');
|
||||
|
||||
// Only count if the new line is an actual edit.
|
||||
if &new_line != line {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
|
||||
output.push_str(&codes.join(", "));
|
||||
output.push('\n');
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind};
|
||||
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::whitespace;
|
||||
@@ -15,7 +15,7 @@ use crate::docstrings::constants;
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::docstrings::sections::{section_contexts, SectionContext};
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::visibility::{is_init, is_magic, is_overload, is_staticmethod, Visibility};
|
||||
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
|
||||
|
||||
/// D100, D101, D102, D103, D104, D105, D106, D107
|
||||
pub fn not_missing(
|
||||
@@ -88,7 +88,7 @@ pub fn not_missing(
|
||||
}
|
||||
}
|
||||
DefinitionKind::Method(stmt) => {
|
||||
if is_overload(stmt) {
|
||||
if is_overload(stmt) || is_override(stmt) {
|
||||
true
|
||||
} else if is_magic(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::D105) {
|
||||
@@ -1303,8 +1303,9 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
|
||||
args: arguments, ..
|
||||
} = &parent.node
|
||||
{
|
||||
// Collect all the arguments into a single vector.
|
||||
let mut all_arguments: Vec<&Arg> = arguments
|
||||
// Look for arguments that weren't included in the docstring.
|
||||
let mut missing_arg_names: BTreeSet<String> = BTreeSet::default();
|
||||
for arg in arguments
|
||||
.args
|
||||
.iter()
|
||||
.chain(arguments.posonlyargs.iter())
|
||||
@@ -1316,33 +1317,38 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
|
||||
&& !is_staticmethod(parent),
|
||||
),
|
||||
)
|
||||
.collect();
|
||||
{
|
||||
let arg_name = arg.node.arg.as_str();
|
||||
if !arg_name.starts_with('_') && !docstrings_args.contains(&arg_name) {
|
||||
missing_arg_names.insert(arg_name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Check specifically for `vararg` and `kwarg`, which can be prefixed with a
|
||||
// single or double star, respectively.
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
all_arguments.push(arg);
|
||||
let arg_name = arg.node.arg.as_str();
|
||||
let starred_arg_name = format!("*{arg_name}");
|
||||
if !arg_name.starts_with('_')
|
||||
&& !docstrings_args.contains(&arg_name)
|
||||
&& !docstrings_args.contains(&starred_arg_name.as_str())
|
||||
{
|
||||
missing_arg_names.insert(starred_arg_name);
|
||||
}
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
all_arguments.push(arg);
|
||||
}
|
||||
|
||||
// Look for arguments that weren't included in the docstring.
|
||||
let mut missing_args: BTreeSet<&str> = BTreeSet::default();
|
||||
for arg in all_arguments {
|
||||
let arg_name = arg.node.arg.as_str();
|
||||
if arg_name.starts_with('_') {
|
||||
continue;
|
||||
let starred_arg_name = format!("**{arg_name}");
|
||||
if !arg_name.starts_with('_')
|
||||
&& !docstrings_args.contains(&arg_name)
|
||||
&& !docstrings_args.contains(&starred_arg_name.as_str())
|
||||
{
|
||||
missing_arg_names.insert(starred_arg_name);
|
||||
}
|
||||
if docstrings_args.contains(&arg_name) {
|
||||
continue;
|
||||
}
|
||||
missing_args.insert(arg_name);
|
||||
}
|
||||
|
||||
if !missing_args.is_empty() {
|
||||
let names = missing_args
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.sorted()
|
||||
.collect();
|
||||
if !missing_arg_names.is_empty() {
|
||||
let names = missing_arg_names.into_iter().sorted().collect();
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DocumentAllArguments(names),
|
||||
Range::from_located(parent),
|
||||
|
||||
102
src/pyflakes/cformat.rs
Normal file
102
src/pyflakes/cformat.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Implements helper functions for using vendored/cformat.rs
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::cformat::{
|
||||
CFormatError, CFormatPart, CFormatQuantity, CFormatSpec, CFormatString,
|
||||
};
|
||||
|
||||
pub(crate) struct CFormatSummary {
|
||||
pub starred: bool,
|
||||
pub num_positional: usize,
|
||||
pub keywords: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for CFormatSummary {
|
||||
type Error = CFormatError;
|
||||
|
||||
fn try_from(literal: &str) -> Result<Self, Self::Error> {
|
||||
let format_string = CFormatString::from_str(literal)?;
|
||||
|
||||
let mut starred = false;
|
||||
let mut num_positional = 0;
|
||||
let mut keywords = FxHashSet::default();
|
||||
|
||||
for format_part in format_string.parts {
|
||||
if let CFormatPart::Spec(CFormatSpec {
|
||||
mapping_key,
|
||||
min_field_width,
|
||||
precision,
|
||||
..
|
||||
}) = format_part.1
|
||||
{
|
||||
match mapping_key {
|
||||
Some(k) => {
|
||||
keywords.insert(k);
|
||||
}
|
||||
None => {
|
||||
num_positional += 1;
|
||||
}
|
||||
};
|
||||
if min_field_width == Some(CFormatQuantity::FromValuesTuple) {
|
||||
num_positional += 1;
|
||||
starred = true;
|
||||
}
|
||||
if precision == Some(CFormatQuantity::FromValuesTuple) {
|
||||
num_positional += 1;
|
||||
starred = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CFormatSummary {
|
||||
starred,
|
||||
num_positional,
|
||||
keywords,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cformat_summary() {
|
||||
let literal = "%(foo)s %s %d %(bar)x";
|
||||
|
||||
let expected_positional = 2;
|
||||
let expected_keywords = ["foo", "bar"].into_iter().map(String::from).collect();
|
||||
|
||||
let format_summary = CFormatSummary::try_from(literal).unwrap();
|
||||
assert!(!format_summary.starred);
|
||||
assert_eq!(format_summary.num_positional, expected_positional);
|
||||
assert_eq!(format_summary.keywords, expected_keywords);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cformat_summary_starred() {
|
||||
let format_summary1 = CFormatSummary::try_from("%*s %*d").unwrap();
|
||||
assert!(format_summary1.starred);
|
||||
assert_eq!(format_summary1.num_positional, 4);
|
||||
|
||||
let format_summary2 = CFormatSummary::try_from("%s %.*d").unwrap();
|
||||
assert!(format_summary2.starred);
|
||||
assert_eq!(format_summary2.num_positional, 3);
|
||||
|
||||
let format_summary3 = CFormatSummary::try_from("%s %*.*d").unwrap();
|
||||
assert!(format_summary3.starred);
|
||||
assert_eq!(format_summary3.num_positional, 4);
|
||||
|
||||
let format_summary4 = CFormatSummary::try_from("%s %1d").unwrap();
|
||||
assert!(!format_summary4.starred);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cformat_summary_invalid() {
|
||||
assert!(CFormatSummary::try_from("%").is_err());
|
||||
assert!(CFormatSummary::try_from("%(foo).").is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,353 @@
|
||||
use std::string::ToString;
|
||||
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Keyword, KeywordData};
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::ast::types::{BindingKind, FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pyflakes::cformat::CFormatSummary;
|
||||
use crate::pyflakes::format::FormatSummary;
|
||||
|
||||
fn has_star_star_kwargs(keywords: &[Keyword]) -> bool {
|
||||
keywords.iter().any(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.is_none()
|
||||
})
|
||||
}
|
||||
|
||||
fn has_star_args(args: &[Expr]) -> bool {
|
||||
args.iter()
|
||||
.any(|a| matches!(&a.node, ExprKind::Starred { .. }))
|
||||
}
|
||||
|
||||
/// F502
|
||||
pub(crate) fn percent_format_expected_mapping(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.keywords.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Tuple, List, Set (+comprehensions)
|
||||
match right.node {
|
||||
ExprKind::List { .. }
|
||||
| ExprKind::Tuple { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::GeneratorExp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatExpectedMapping,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F503
|
||||
pub(crate) fn percent_format_expected_sequence(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional <= 1 {
|
||||
None
|
||||
} else {
|
||||
match right.node {
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatExpectedSequence,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// F504
|
||||
pub(crate) fn percent_format_extra_named_arguments(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let ExprKind::Dict { keys, values } = &right.node {
|
||||
if values.len() > keys.len() {
|
||||
return None; // contains **x splat
|
||||
}
|
||||
|
||||
let missing: Vec<&String> = keys
|
||||
.iter()
|
||||
.filter_map(|k| match &k.node {
|
||||
// We can only check that string literals exist
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
if summary.keywords.contains(value) {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatExtraNamedArguments(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// F505
|
||||
pub(crate) fn percent_format_missing_arguments(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let ExprKind::Dict { keys, values } = &right.node {
|
||||
if values.len() > keys.len() {
|
||||
return None; // contains **x splat
|
||||
}
|
||||
|
||||
let mut keywords = FxHashSet::default();
|
||||
for key in keys {
|
||||
match &key.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
keywords.insert(value);
|
||||
}
|
||||
_ => {
|
||||
return None; // Dynamic keys present
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let missing: Vec<&String> = summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatMissingArgument(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// F506
|
||||
pub(crate) fn percent_format_mixed_positional_and_named(
|
||||
summary: &CFormatSummary,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.num_positional == 0 || summary.keywords.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatMixedPositionalAndNamed,
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F507
|
||||
pub(crate) fn percent_format_positional_count_mismatch(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if !summary.keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &right.node {
|
||||
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } | ExprKind::Set { elts, .. } => {
|
||||
let mut found = 0;
|
||||
for elt in elts {
|
||||
if let ExprKind::Starred { .. } = &elt.node {
|
||||
return None;
|
||||
}
|
||||
found += 1;
|
||||
}
|
||||
|
||||
if found == summary.num_positional {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::PercentFormatPositionalCountMismatch(summary.num_positional, found),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// F508
|
||||
pub(crate) fn percent_format_star_requires_sequence(
|
||||
summary: &CFormatSummary,
|
||||
right: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.starred {
|
||||
match &right.node {
|
||||
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
|
||||
CheckKind::PercentFormatStarRequiresSequence,
|
||||
location,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// F522
|
||||
pub(crate) fn string_dot_format_extra_named_arguments(
|
||||
summary: &FormatSummary,
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_star_kwargs(keywords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let keywords = keywords.iter().filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
});
|
||||
|
||||
let missing: Vec<&String> = keywords
|
||||
.filter(|&k| !summary.keywords.contains(k))
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatExtraNamedArguments(
|
||||
missing.iter().map(|&s| s.clone()).collect(),
|
||||
),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F523
|
||||
pub(crate) fn string_dot_format_extra_positional_arguments(
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_args(args) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let missing: Vec<String> = (0..args.len())
|
||||
.filter(|i| !(summary.autos.contains(i) || summary.indexes.contains(i)))
|
||||
.map(|i| i.to_string())
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatExtraPositionalArguments(missing),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F524
|
||||
pub(crate) fn string_dot_format_missing_argument(
|
||||
summary: &FormatSummary,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if has_star_args(args) || has_star_star_kwargs(keywords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let keywords: FxHashSet<_> = keywords
|
||||
.iter()
|
||||
.filter_map(|k| {
|
||||
let KeywordData { arg, .. } = &k.node;
|
||||
arg.as_ref()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let missing: Vec<String> = summary
|
||||
.autos
|
||||
.iter()
|
||||
.chain(summary.indexes.iter())
|
||||
.filter(|&&i| i >= args.len())
|
||||
.map(ToString::to_string)
|
||||
.chain(
|
||||
summary
|
||||
.keywords
|
||||
.iter()
|
||||
.filter(|k| !keywords.contains(k))
|
||||
.cloned(),
|
||||
)
|
||||
.collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatMissingArguments(missing),
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F525
|
||||
pub(crate) fn string_dot_format_mixing_automatic(
|
||||
summary: &FormatSummary,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if summary.autos.is_empty() || summary.indexes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Check::new(
|
||||
CheckKind::StringDotFormatMixingAutomatic,
|
||||
location,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// F631
|
||||
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
|
||||
@@ -22,14 +22,10 @@ pub fn remove_unused_imports(
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
|
||||
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
|
||||
};
|
||||
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(SmallStatement::Import(body)) = body.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::ImportFrom"
|
||||
));
|
||||
@@ -80,22 +76,16 @@ pub fn remove_unused_import_froms(
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
|
||||
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
|
||||
};
|
||||
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::ImportFrom"
|
||||
));
|
||||
};
|
||||
|
||||
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
|
||||
aliases
|
||||
} else {
|
||||
let ImportNames::Aliases(aliases) = &mut body.names else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
|
||||
};
|
||||
|
||||
|
||||
134
src/pyflakes/format.rs
Normal file
134
src/pyflakes/format.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Implements helper functions for using vendored/format.rs
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::vendored::format::{
|
||||
FieldName, FieldType, FormatParseError, FormatPart, FormatString, FromTemplate,
|
||||
};
|
||||
|
||||
impl fmt::Display for FormatParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let message = match self {
|
||||
FormatParseError::EmptyAttribute => "Empty attribute in format string",
|
||||
FormatParseError::InvalidCharacterAfterRightBracket => {
|
||||
"Only '.' or '[' may follow ']' in format field specifier"
|
||||
}
|
||||
FormatParseError::InvalidFormatSpecifier => "Max string recursion exceeded",
|
||||
FormatParseError::MissingStartBracket => "Single '}' encountered in format string",
|
||||
FormatParseError::MissingRightBracket => "Expected '}' before end of string",
|
||||
FormatParseError::UnmatchedBracket => "Single '{' encountered in format string",
|
||||
_ => "Unexpected error parsing format string",
|
||||
};
|
||||
|
||||
write!(f, "{message}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FormatSummary {
|
||||
pub autos: FxHashSet<usize>,
|
||||
pub indexes: FxHashSet<usize>,
|
||||
pub keywords: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for FormatSummary {
|
||||
type Error = FormatParseError;
|
||||
|
||||
fn try_from(literal: &str) -> Result<Self, Self::Error> {
|
||||
let format_string = FormatString::from_str(literal)?;
|
||||
|
||||
let mut autos = FxHashSet::default();
|
||||
let mut indexes = FxHashSet::default();
|
||||
let mut keywords = FxHashSet::default();
|
||||
|
||||
for format_part in format_string.format_parts {
|
||||
if let FormatPart::Field {
|
||||
field_name,
|
||||
format_spec,
|
||||
..
|
||||
} = format_part
|
||||
{
|
||||
let parsed = FieldName::parse(&field_name)?;
|
||||
match parsed.field_type {
|
||||
FieldType::Auto => autos.insert(autos.len()),
|
||||
FieldType::Index(i) => indexes.insert(i),
|
||||
FieldType::Keyword(k) => keywords.insert(k),
|
||||
};
|
||||
|
||||
let nested = FormatString::from_str(&format_spec)?;
|
||||
for nested_part in nested.format_parts {
|
||||
if let FormatPart::Field { field_name, .. } = nested_part {
|
||||
let parsed = FieldName::parse(&field_name)?;
|
||||
match parsed.field_type {
|
||||
FieldType::Auto => autos.insert(autos.len()),
|
||||
FieldType::Index(i) => indexes.insert(i),
|
||||
FieldType::Keyword(k) => keywords.insert(k),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FormatSummary {
|
||||
autos,
|
||||
indexes,
|
||||
keywords,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vendored::format::FromTemplate;
|
||||
|
||||
#[test]
|
||||
fn test_format_summary() {
|
||||
let literal = "foo{foo}a{}b{2}c{2}d{1}{}{}e{bar}{foo}f{spam}";
|
||||
|
||||
let expected_autos = [0usize, 1usize, 2usize].into_iter().collect();
|
||||
let expected_indexes = [1usize, 2usize].into_iter().collect();
|
||||
let expected_keywords = ["foo", "bar", "spam"]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
let format_summary = FormatSummary::try_from(literal).unwrap();
|
||||
|
||||
assert_eq!(format_summary.autos, expected_autos);
|
||||
assert_eq!(format_summary.indexes, expected_indexes);
|
||||
assert_eq!(format_summary.keywords, expected_keywords);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_summary_nested() {
|
||||
let literal = "foo{foo}a{:{}{}}b{2:{3}{4}}c{2}d{1}{}e{bar:{spam}{eggs}}";
|
||||
|
||||
let expected_autos = [0usize, 1usize, 2usize, 3usize].into_iter().collect();
|
||||
let expected_indexes = [1usize, 2usize, 3usize, 4usize].into_iter().collect();
|
||||
let expected_keywords = ["foo", "bar", "spam", "eggs"]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
let format_summary = FormatSummary::try_from(literal).unwrap();
|
||||
|
||||
assert_eq!(format_summary.autos, expected_autos);
|
||||
assert_eq!(format_summary.indexes, expected_indexes);
|
||||
assert_eq!(format_summary.keywords, expected_keywords);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_summary_invalid() {
|
||||
assert!(FormatSummary::try_from("{").is_err());
|
||||
|
||||
let literal = "{foo}a{}b{bar..}";
|
||||
assert!(FormatString::from_str(literal).is_ok());
|
||||
assert!(FormatSummary::try_from(literal).is_err());
|
||||
|
||||
let literal_nested = "{foo}a{}b{bar:{spam..}}";
|
||||
assert!(FormatString::from_str(literal_nested).is_ok());
|
||||
assert!(FormatSummary::try_from(literal_nested).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod cformat;
|
||||
pub mod checks;
|
||||
pub mod fixes;
|
||||
pub mod format;
|
||||
pub mod plugins;
|
||||
|
||||
@@ -209,15 +209,21 @@ pub enum SubscriptKind {
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(
|
||||
pub fn match_annotated_subscript<F>(
|
||||
expr: &Expr,
|
||||
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
|
||||
import_aliases: &FxHashMap<&str, &str>,
|
||||
) -> Option<SubscriptKind> {
|
||||
is_builtin: F,
|
||||
) -> Option<SubscriptKind>
|
||||
where
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
if !call_path.is_empty() {
|
||||
for (module, member) in SUBSCRIPTS {
|
||||
if match_call_path(&call_path, module, member, from_imports) {
|
||||
if match_call_path(&call_path, module, member, from_imports)
|
||||
&& (!module.is_empty() || is_builtin(member))
|
||||
{
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +111,7 @@ pub fn remove_super_arguments(locator: &SourceCodeLocator, expr: &Expr) -> Optio
|
||||
let range = Range::from_located(expr);
|
||||
let contents = locator.slice_source_code_range(&range);
|
||||
|
||||
let mut tree = match libcst_native::parse_module(&contents, None) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let mut tree = libcst_native::parse_module(&contents, None).ok()?;
|
||||
|
||||
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
|
||||
@@ -150,22 +147,16 @@ pub fn remove_unnecessary_future_import(
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
|
||||
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
|
||||
};
|
||||
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::ImportFrom"
|
||||
));
|
||||
};
|
||||
|
||||
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
|
||||
aliases
|
||||
} else {
|
||||
let ImportNames::Aliases(aliases) = &mut body.names else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::error;
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Located, Location};
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, KeywordData, Location};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::token::Tok;
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
const OPEN_FUNC_NAME: &str = "open";
|
||||
const MODE_KEYWORD_ARGUMENT: &str = "mode";
|
||||
|
||||
enum OpenMode {
|
||||
U,
|
||||
@@ -56,15 +57,19 @@ impl OpenMode {
|
||||
}
|
||||
}
|
||||
|
||||
fn match_open(expr: &Expr) -> Option<&Expr> {
|
||||
if let ExprKind::Call { func, args, .. } = &expr.node {
|
||||
// TODO(andberger): Verify that "open" is still bound to the built-in function.
|
||||
fn match_open(expr: &Expr) -> (Option<&Expr>, Vec<Keyword>) {
|
||||
if let ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} = &expr.node
|
||||
{
|
||||
if match_name_or_attr(func, OPEN_FUNC_NAME) {
|
||||
// Return the "open mode" parameter.
|
||||
return args.get(1);
|
||||
// Return the "open mode" parameter and keywords.
|
||||
return (args.get(1), keywords.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
(None, vec![])
|
||||
}
|
||||
|
||||
fn create_check(
|
||||
@@ -101,19 +106,36 @@ fn create_remove_param_fix(
|
||||
location: expr.location,
|
||||
end_location: expr.end_location.unwrap(),
|
||||
});
|
||||
// Find the last comma before mode_param
|
||||
// and delete that comma as well as mode_param.
|
||||
// Find the last comma before mode_param and create a deletion fix
|
||||
// starting from the comma and ending after mode_param.
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
let mut is_first_arg: bool = false;
|
||||
let mut delete_first_arg: bool = false;
|
||||
for (start, tok, end) in lexer::make_tokenizer(&content).flatten() {
|
||||
let start = helpers::to_absolute(start, expr.location);
|
||||
let end = helpers::to_absolute(end, expr.location);
|
||||
if start == mode_param.location {
|
||||
if is_first_arg {
|
||||
delete_first_arg = true;
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(end);
|
||||
break;
|
||||
}
|
||||
if delete_first_arg && matches!(tok, Tok::Name { .. }) {
|
||||
fix_end = Some(start);
|
||||
break;
|
||||
}
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
is_first_arg = true;
|
||||
fix_start = Some(end);
|
||||
}
|
||||
if matches!(tok, Tok::Comma) {
|
||||
fix_start = Some(start);
|
||||
is_first_arg = false;
|
||||
if !delete_first_arg {
|
||||
fix_start = Some(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
match (fix_start, fix_end) {
|
||||
@@ -126,20 +148,45 @@ fn create_remove_param_fix(
|
||||
|
||||
/// U015
|
||||
pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
||||
// TODO(andberger): Add "mode" keyword argument handling to handle invocations
|
||||
// on the following formats:
|
||||
// - `open("foo", mode="U")`
|
||||
// - `open(name="foo", mode="U")`
|
||||
// - `open(mode="U", name="foo")`
|
||||
if let Some(mode_param) = match_open(expr) {
|
||||
if let Located {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(mode_param_value),
|
||||
..
|
||||
},
|
||||
// If `open` has been rebound, skip this check entirely.
|
||||
if !checker.is_builtin(OPEN_FUNC_NAME) {
|
||||
return;
|
||||
}
|
||||
let (mode_param, keywords): (Option<&Expr>, Vec<Keyword>) = match_open(expr);
|
||||
if mode_param.is_none() && !keywords.is_empty() {
|
||||
if let Some(value) = keywords.iter().find_map(|keyword| {
|
||||
let KeywordData { arg, value } = &keyword.node;
|
||||
if arg
|
||||
.as_ref()
|
||||
.map(|arg| arg == MODE_KEYWORD_ARGUMENT)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(mode_param_value),
|
||||
..
|
||||
} = &value.node
|
||||
{
|
||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||
checker.add_check(create_check(
|
||||
expr,
|
||||
value,
|
||||
mode.replacement_value(),
|
||||
checker.locator,
|
||||
checker.patch(&CheckCode::U015),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(mode_param) = mode_param {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(mode_param_value),
|
||||
..
|
||||
} = mode_param
|
||||
} = &mode_param.node
|
||||
{
|
||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||
checker.add_check(create_check(
|
||||
|
||||
@@ -21,12 +21,7 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
|
||||
|
||||
/// Return `true` if `exit` is (still) bound as a built-in in the current scope.
|
||||
fn has_builtin_exit_in_scope(checker: &Checker) -> bool {
|
||||
!is_module_star_imported(checker, "sys")
|
||||
&& checker
|
||||
.current_scopes()
|
||||
.find_map(|scope| scope.values.get("exit"))
|
||||
.map(|binding| matches!(binding.kind, BindingKind::Builtin))
|
||||
.unwrap_or_default()
|
||||
!is_module_star_imported(checker, "sys") && checker.is_builtin("exit")
|
||||
}
|
||||
|
||||
/// Return the appropriate `sys.exit` reference based on the current set of
|
||||
|
||||
@@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
|
||||
use crate::settings::pyproject::load_options;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{
|
||||
@@ -46,25 +46,25 @@ pub struct Configuration {
|
||||
|
||||
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
||||
vec![
|
||||
FilePattern::Simple(".bzr"),
|
||||
FilePattern::Simple(".direnv"),
|
||||
FilePattern::Simple(".eggs"),
|
||||
FilePattern::Simple(".git"),
|
||||
FilePattern::Simple(".hg"),
|
||||
FilePattern::Simple(".mypy_cache"),
|
||||
FilePattern::Simple(".nox"),
|
||||
FilePattern::Simple(".pants.d"),
|
||||
FilePattern::Simple(".ruff_cache"),
|
||||
FilePattern::Simple(".svn"),
|
||||
FilePattern::Simple(".tox"),
|
||||
FilePattern::Simple(".venv"),
|
||||
FilePattern::Simple("__pypackages__"),
|
||||
FilePattern::Simple("_build"),
|
||||
FilePattern::Simple("buck-out"),
|
||||
FilePattern::Simple("build"),
|
||||
FilePattern::Simple("dist"),
|
||||
FilePattern::Simple("node_modules"),
|
||||
FilePattern::Simple("venv"),
|
||||
FilePattern::Builtin(".bzr"),
|
||||
FilePattern::Builtin(".direnv"),
|
||||
FilePattern::Builtin(".eggs"),
|
||||
FilePattern::Builtin(".git"),
|
||||
FilePattern::Builtin(".hg"),
|
||||
FilePattern::Builtin(".mypy_cache"),
|
||||
FilePattern::Builtin(".nox"),
|
||||
FilePattern::Builtin(".pants.d"),
|
||||
FilePattern::Builtin(".ruff_cache"),
|
||||
FilePattern::Builtin(".svn"),
|
||||
FilePattern::Builtin(".tox"),
|
||||
FilePattern::Builtin(".venv"),
|
||||
FilePattern::Builtin("__pypackages__"),
|
||||
FilePattern::Builtin("_build"),
|
||||
FilePattern::Builtin("buck-out"),
|
||||
FilePattern::Builtin("build"),
|
||||
FilePattern::Builtin("dist"),
|
||||
FilePattern::Builtin("node_modules"),
|
||||
FilePattern::Builtin("venv"),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -103,50 +103,21 @@ impl Configuration {
|
||||
},
|
||||
),
|
||||
target_version: options.target_version.unwrap_or(PythonVersion::Py310),
|
||||
exclude: options
|
||||
.exclude
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
exclude: options.exclude.map_or_else(
|
||||
|| DEFAULT_EXCLUDE.clone(),
|
||||
|paths| paths.into_iter().map(FilePattern::User).collect(),
|
||||
),
|
||||
extend_exclude: options
|
||||
.extend_exclude
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect::<Result<_>>()?,
|
||||
.map(|paths| paths.into_iter().map(FilePattern::User).collect())
|
||||
.unwrap_or_default(),
|
||||
extend_ignore: options.extend_ignore.unwrap_or_default(),
|
||||
select: options
|
||||
.select
|
||||
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||
extend_select: options.extend_select.unwrap_or_default(),
|
||||
fix: options.fix.unwrap_or_default(),
|
||||
fixable: options.fixable.unwrap_or_else(|| {
|
||||
// TODO(charlie): Autogenerate this list.
|
||||
vec![
|
||||
CheckCodePrefix::A,
|
||||
CheckCodePrefix::B,
|
||||
CheckCodePrefix::BLE,
|
||||
CheckCodePrefix::C,
|
||||
CheckCodePrefix::D,
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::I,
|
||||
CheckCodePrefix::M,
|
||||
CheckCodePrefix::N,
|
||||
CheckCodePrefix::Q,
|
||||
CheckCodePrefix::RUF,
|
||||
CheckCodePrefix::S,
|
||||
CheckCodePrefix::T,
|
||||
CheckCodePrefix::U,
|
||||
CheckCodePrefix::W,
|
||||
CheckCodePrefix::YTT,
|
||||
]
|
||||
}),
|
||||
fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
|
||||
unfixable: options.unfixable.unwrap_or_default(),
|
||||
ignore: options.ignore.unwrap_or_default(),
|
||||
line_length: options.line_length.unwrap_or(88),
|
||||
@@ -154,13 +125,10 @@ impl Configuration {
|
||||
.per_file_ignores
|
||||
.map(|per_file_ignores| {
|
||||
per_file_ignores
|
||||
.iter()
|
||||
.map(|(pattern, prefixes)| {
|
||||
PerFileIgnore::new(pattern, prefixes, project_root)
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes))
|
||||
.collect()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default(),
|
||||
show_source: options.show_source.unwrap_or_default(),
|
||||
// Plugins
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
//! command-line options. Structure is optimized for internal usage, as opposed
|
||||
//! to external visibility or parsing.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::{Glob, GlobMatcher, GlobSet};
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
@@ -14,7 +17,7 @@ use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
|
||||
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
|
||||
pep8_naming,
|
||||
};
|
||||
|
||||
@@ -22,17 +25,16 @@ pub mod configuration;
|
||||
pub mod options;
|
||||
pub mod pyproject;
|
||||
pub mod types;
|
||||
pub mod user;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub dummy_variable_rgx: Regex,
|
||||
pub enabled: FxHashSet<CheckCode>,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub exclude: GlobSet,
|
||||
pub extend_exclude: GlobSet,
|
||||
pub fixable: FxHashSet<CheckCode>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>,
|
||||
pub show_source: bool,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub target_version: PythonVersion,
|
||||
@@ -47,8 +49,11 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_configuration(config: Configuration) -> Self {
|
||||
Self {
|
||||
pub fn from_configuration(
|
||||
config: Configuration,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
dummy_variable_rgx: config.dummy_variable_rgx,
|
||||
enabled: resolve_codes(
|
||||
&config
|
||||
@@ -62,8 +67,8 @@ impl Settings {
|
||||
.chain(config.extend_ignore.into_iter())
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
exclude: config.exclude,
|
||||
extend_exclude: config.extend_exclude,
|
||||
exclude: resolve_globset(config.exclude, project_root)?,
|
||||
extend_exclude: resolve_globset(config.extend_exclude, project_root)?,
|
||||
fixable: resolve_codes(&config.fixable, &config.unfixable),
|
||||
flake8_annotations: config.flake8_annotations,
|
||||
flake8_bugbear: config.flake8_bugbear,
|
||||
@@ -73,11 +78,11 @@ impl Settings {
|
||||
mccabe: config.mccabe,
|
||||
line_length: config.line_length,
|
||||
pep8_naming: config.pep8_naming,
|
||||
per_file_ignores: config.per_file_ignores,
|
||||
per_file_ignores: resolve_per_file_ignores(config.per_file_ignores, project_root)?,
|
||||
src: config.src,
|
||||
target_version: config.target_version,
|
||||
show_source: config.show_source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_rule(check_code: CheckCode) -> Self {
|
||||
@@ -85,8 +90,8 @@ impl Settings {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: FxHashSet::from_iter([check_code.clone()]),
|
||||
fixable: FxHashSet::from_iter([check_code]),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: GlobSet::empty(),
|
||||
extend_exclude: GlobSet::empty(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
@@ -107,8 +112,8 @@ impl Settings {
|
||||
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
|
||||
enabled: FxHashSet::from_iter(check_codes.clone()),
|
||||
fixable: FxHashSet::from_iter(check_codes),
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
exclude: GlobSet::empty(),
|
||||
extend_exclude: GlobSet::empty(),
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
@@ -136,8 +141,10 @@ impl Hash for Settings {
|
||||
value.hash(state);
|
||||
}
|
||||
self.line_length.hash(state);
|
||||
for value in &self.per_file_ignores {
|
||||
value.hash(state);
|
||||
for (absolute, basename, codes) in &self.per_file_ignores {
|
||||
absolute.glob().hash(state);
|
||||
basename.glob().hash(state);
|
||||
codes.hash(state);
|
||||
}
|
||||
self.show_source.hash(state);
|
||||
self.target_version.hash(state);
|
||||
@@ -152,6 +159,42 @@ impl Hash for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a list of patterns, create a `GlobSet`.
|
||||
pub fn resolve_globset(
|
||||
patterns: Vec<FilePattern>,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<GlobSet> {
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
for pattern in patterns {
|
||||
pattern.add_to(&mut builder, project_root)?;
|
||||
}
|
||||
builder.build().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
/// Given a list of patterns, create a `GlobSet`.
|
||||
pub fn resolve_per_file_ignores(
|
||||
per_file_ignores: Vec<PerFileIgnore>,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>> {
|
||||
per_file_ignores
|
||||
.into_iter()
|
||||
.map(|per_file_ignore| {
|
||||
// Construct absolute path matcher.
|
||||
let path = Path::new(&per_file_ignore.pattern);
|
||||
let absolute_path = match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
None => fs::normalize_path(path),
|
||||
};
|
||||
let absolute = Glob::new(&absolute_path.to_string_lossy())?.compile_matcher();
|
||||
|
||||
// Construct basename matcher.
|
||||
let basename = Glob::new(&per_file_ignore.pattern)?.compile_matcher();
|
||||
|
||||
Ok((absolute, basename, per_file_ignore.codes))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Given a set of selected and ignored prefixes, resolve the set of enabled
|
||||
/// error codes.
|
||||
fn resolve_codes(select: &[CheckCodePrefix], ignore: &[CheckCodePrefix]) -> FxHashSet<CheckCode> {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use glob::Pattern;
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
@@ -44,46 +44,59 @@ impl FromStr for PythonVersion {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FilePattern {
|
||||
Simple(&'static str),
|
||||
Complex(Pattern, Option<Pattern>),
|
||||
Builtin(&'static str),
|
||||
User(String),
|
||||
}
|
||||
|
||||
impl FilePattern {
|
||||
pub fn from_user(pattern: &str, project_root: Option<&PathBuf>) -> Result<Self> {
|
||||
let path = Path::new(pattern);
|
||||
let absolute_path = match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
None => fs::normalize_path(path),
|
||||
};
|
||||
pub fn add_to(
|
||||
self,
|
||||
builder: &mut GlobSetBuilder,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
FilePattern::Builtin(pattern) => {
|
||||
builder.add(Glob::from_str(pattern)?);
|
||||
}
|
||||
FilePattern::User(pattern) => {
|
||||
// Add absolute path.
|
||||
let path = Path::new(&pattern);
|
||||
let absolute_path = match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
None => fs::normalize_path(path),
|
||||
};
|
||||
builder.add(Glob::new(&absolute_path.to_string_lossy())?);
|
||||
|
||||
let absolute = Pattern::new(&absolute_path.to_string_lossy())?;
|
||||
let basename = if pattern.contains(std::path::MAIN_SEPARATOR) {
|
||||
None
|
||||
} else {
|
||||
Some(Pattern::new(pattern)?)
|
||||
};
|
||||
// Add basename path.
|
||||
if !pattern.contains(std::path::MAIN_SEPARATOR) {
|
||||
builder.add(Glob::from_str(&pattern)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FilePattern::Complex(absolute, basename))
|
||||
impl FromStr for FilePattern {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::User(s.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct PerFileIgnore {
|
||||
pub pattern: FilePattern,
|
||||
pub pattern: String,
|
||||
pub codes: BTreeSet<CheckCode>,
|
||||
}
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(
|
||||
pattern: &str,
|
||||
prefixes: &[CheckCodePrefix],
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Result<Self> {
|
||||
let pattern = FilePattern::from_user(pattern, project_root)?;
|
||||
pub fn new(pattern: String, prefixes: &[CheckCodePrefix]) -> Self {
|
||||
let codes = prefixes.iter().flat_map(CheckCodePrefix::codes).collect();
|
||||
Ok(Self { pattern, codes })
|
||||
Self { pattern, codes }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +128,9 @@ impl<'de> Deserialize<'de> for PatternPrefixPair {
|
||||
impl FromStr for PatternPrefixPair {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (pattern_str, code_string) = {
|
||||
let tokens = string.split(':').collect::<Vec<_>>();
|
||||
let tokens = s.split(':').collect::<Vec<_>>();
|
||||
if tokens.len() != 2 {
|
||||
return Err(anyhow!("Expected {}", Self::EXPECTED_PATTERN));
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//! Structs to render user-facing settings.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{FilePattern, PythonVersion};
|
||||
use crate::{
|
||||
flake8_annotations, flake8_quotes, flake8_tidy_imports, isort, pep8_naming, Configuration,
|
||||
};
|
||||
|
||||
/// Struct to render user-facing exclusion patterns.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Exclusion {
|
||||
basename: Option<String>,
|
||||
absolute: Option<String>,
|
||||
}
|
||||
|
||||
impl Exclusion {
|
||||
pub fn from_file_pattern(file_pattern: FilePattern) -> Self {
|
||||
match file_pattern {
|
||||
FilePattern::Simple(basename) => Exclusion {
|
||||
basename: Some(basename.to_string()),
|
||||
absolute: None,
|
||||
},
|
||||
FilePattern::Complex(absolute, basename) => Exclusion {
|
||||
basename: basename.map(|pattern| pattern.to_string()),
|
||||
absolute: Some(absolute.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to render user-facing configuration.
|
||||
#[derive(Debug)]
|
||||
pub struct UserConfiguration {
|
||||
pub dummy_variable_rgx: Regex,
|
||||
pub exclude: Vec<Exclusion>,
|
||||
pub extend_exclude: Vec<Exclusion>,
|
||||
pub extend_ignore: Vec<CheckCodePrefix>,
|
||||
pub extend_select: Vec<CheckCodePrefix>,
|
||||
pub fix: bool,
|
||||
pub fixable: Vec<CheckCodePrefix>,
|
||||
pub ignore: Vec<CheckCodePrefix>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<(Exclusion, Vec<CheckCode>)>,
|
||||
pub select: Vec<CheckCodePrefix>,
|
||||
pub show_source: bool,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub target_version: PythonVersion,
|
||||
pub unfixable: Vec<CheckCodePrefix>,
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
pub flake8_quotes: flake8_quotes::settings::Settings,
|
||||
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
|
||||
pub isort: isort::settings::Settings,
|
||||
pub pep8_naming: pep8_naming::settings::Settings,
|
||||
// Non-settings exposed to the user
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl UserConfiguration {
|
||||
pub fn from_configuration(
|
||||
configuration: Configuration,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dummy_variable_rgx: configuration.dummy_variable_rgx,
|
||||
exclude: configuration
|
||||
.exclude
|
||||
.into_iter()
|
||||
.map(Exclusion::from_file_pattern)
|
||||
.collect(),
|
||||
extend_exclude: configuration
|
||||
.extend_exclude
|
||||
.into_iter()
|
||||
.map(Exclusion::from_file_pattern)
|
||||
.collect(),
|
||||
extend_ignore: configuration.extend_ignore,
|
||||
extend_select: configuration.extend_select,
|
||||
fix: configuration.fix,
|
||||
fixable: configuration.fixable,
|
||||
unfixable: configuration.unfixable,
|
||||
ignore: configuration.ignore,
|
||||
line_length: configuration.line_length,
|
||||
per_file_ignores: configuration
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|per_file_ignore| {
|
||||
(
|
||||
Exclusion::from_file_pattern(per_file_ignore.pattern),
|
||||
Vec::from_iter(per_file_ignore.codes),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
select: configuration.select,
|
||||
src: configuration.src,
|
||||
target_version: configuration.target_version,
|
||||
show_source: configuration.show_source,
|
||||
flake8_annotations: configuration.flake8_annotations,
|
||||
flake8_quotes: configuration.flake8_quotes,
|
||||
flake8_tidy_imports: configuration.flake8_tidy_imports,
|
||||
isort: configuration.isort,
|
||||
pep8_naming: configuration.pep8_naming,
|
||||
project_root,
|
||||
pyproject,
|
||||
}
|
||||
}
|
||||
}
|
||||
158
src/snapshots/ruff__linter__tests__B023_B023.py.snap
Normal file
158
src/snapshots/ruff__linter__tests__B023_B023.py.snap
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 12
|
||||
column: 29
|
||||
end_location:
|
||||
row: 12
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: y
|
||||
location:
|
||||
row: 13
|
||||
column: 29
|
||||
end_location:
|
||||
row: 13
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 16
|
||||
column: 15
|
||||
end_location:
|
||||
row: 16
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 28
|
||||
column: 18
|
||||
end_location:
|
||||
row: 28
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 29
|
||||
column: 18
|
||||
end_location:
|
||||
row: 29
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 30
|
||||
column: 18
|
||||
end_location:
|
||||
row: 30
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 31
|
||||
column: 21
|
||||
end_location:
|
||||
row: 31
|
||||
column: 22
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 40
|
||||
column: 33
|
||||
end_location:
|
||||
row: 40
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: x
|
||||
location:
|
||||
row: 42
|
||||
column: 13
|
||||
end_location:
|
||||
row: 42
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: a
|
||||
location:
|
||||
row: 50
|
||||
column: 29
|
||||
end_location:
|
||||
row: 50
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: a_
|
||||
location:
|
||||
row: 51
|
||||
column: 29
|
||||
end_location:
|
||||
row: 51
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: b
|
||||
location:
|
||||
row: 52
|
||||
column: 29
|
||||
end_location:
|
||||
row: 52
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: c
|
||||
location:
|
||||
row: 53
|
||||
column: 29
|
||||
end_location:
|
||||
row: 53
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: j
|
||||
location:
|
||||
row: 61
|
||||
column: 16
|
||||
end_location:
|
||||
row: 61
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: k
|
||||
location:
|
||||
row: 61
|
||||
column: 20
|
||||
end_location:
|
||||
row: 61
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: l
|
||||
location:
|
||||
row: 68
|
||||
column: 9
|
||||
end_location:
|
||||
row: 68
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
FunctionUsesLoopVariable: i
|
||||
location:
|
||||
row: 82
|
||||
column: 12
|
||||
end_location:
|
||||
row: 82
|
||||
column: 18
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__B904_B904.py.snap
Normal file
29
src/snapshots/ruff__linter__tests__B904_B904.py.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: RaiseWithoutFromInsideExcept
|
||||
location:
|
||||
row: 10
|
||||
column: 8
|
||||
end_location:
|
||||
row: 10
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind: RaiseWithoutFromInsideExcept
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind: RaiseWithoutFromInsideExcept
|
||||
location:
|
||||
row: 16
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 39
|
||||
fix: ~
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: PublicClass
|
||||
location:
|
||||
row: 14
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 67
|
||||
row: 72
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,26 +4,26 @@ expression: checks
|
||||
---
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 22
|
||||
row: 23
|
||||
column: 4
|
||||
end_location:
|
||||
row: 25
|
||||
row: 26
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 51
|
||||
row: 56
|
||||
column: 4
|
||||
end_location:
|
||||
row: 54
|
||||
row: 59
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 63
|
||||
row: 68
|
||||
column: 4
|
||||
end_location:
|
||||
row: 67
|
||||
row: 72
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: PublicFunction
|
||||
location:
|
||||
row: 395
|
||||
row: 400
|
||||
column: 0
|
||||
end_location:
|
||||
row: 396
|
||||
row: 401
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: MagicMethod
|
||||
location:
|
||||
row: 59
|
||||
row: 64
|
||||
column: 4
|
||||
end_location:
|
||||
row: 62
|
||||
row: 67
|
||||
column: 4
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ expression: checks
|
||||
---
|
||||
- kind: PublicInit
|
||||
location:
|
||||
row: 55
|
||||
row: 60
|
||||
column: 4
|
||||
end_location:
|
||||
row: 58
|
||||
row: 63
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: PublicInit
|
||||
location:
|
||||
row: 529
|
||||
row: 534
|
||||
column: 4
|
||||
end_location:
|
||||
row: 533
|
||||
row: 538
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -5,69 +5,69 @@ expression: checks
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 132
|
||||
row: 137
|
||||
column: 4
|
||||
end_location:
|
||||
row: 132
|
||||
row: 137
|
||||
column: 24
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 131
|
||||
row: 136
|
||||
column: 0
|
||||
end_location:
|
||||
row: 132
|
||||
row: 137
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 146
|
||||
row: 151
|
||||
column: 4
|
||||
end_location:
|
||||
row: 146
|
||||
row: 151
|
||||
column: 37
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 145
|
||||
row: 150
|
||||
column: 0
|
||||
end_location:
|
||||
row: 146
|
||||
row: 151
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 541
|
||||
row: 546
|
||||
column: 4
|
||||
end_location:
|
||||
row: 544
|
||||
row: 549
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 540
|
||||
row: 545
|
||||
column: 0
|
||||
end_location:
|
||||
row: 541
|
||||
row: 546
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 563
|
||||
row: 568
|
||||
column: 4
|
||||
end_location:
|
||||
row: 566
|
||||
row: 571
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 562
|
||||
row: 567
|
||||
column: 0
|
||||
end_location:
|
||||
row: 563
|
||||
row: 568
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -5,69 +5,69 @@ expression: checks
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 137
|
||||
row: 142
|
||||
column: 4
|
||||
end_location:
|
||||
row: 137
|
||||
row: 142
|
||||
column: 24
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 138
|
||||
row: 143
|
||||
column: 0
|
||||
end_location:
|
||||
row: 139
|
||||
row: 144
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 146
|
||||
row: 151
|
||||
column: 4
|
||||
end_location:
|
||||
row: 146
|
||||
row: 151
|
||||
column: 37
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 147
|
||||
row: 152
|
||||
column: 0
|
||||
end_location:
|
||||
row: 148
|
||||
row: 153
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 550
|
||||
row: 555
|
||||
column: 4
|
||||
end_location:
|
||||
row: 553
|
||||
row: 558
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 554
|
||||
row: 559
|
||||
column: 0
|
||||
end_location:
|
||||
row: 555
|
||||
row: 560
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 563
|
||||
row: 568
|
||||
column: 4
|
||||
end_location:
|
||||
row: 566
|
||||
row: 571
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 567
|
||||
row: 572
|
||||
column: 0
|
||||
end_location:
|
||||
row: 568
|
||||
row: 573
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -5,52 +5,52 @@ expression: checks
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 156
|
||||
row: 161
|
||||
column: 4
|
||||
end_location:
|
||||
row: 156
|
||||
row: 161
|
||||
column: 32
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 156
|
||||
row: 161
|
||||
column: 0
|
||||
end_location:
|
||||
row: 156
|
||||
row: 161
|
||||
column: 0
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 4
|
||||
end_location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 45
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 0
|
||||
end_location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 0
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 521
|
||||
row: 526
|
||||
column: 4
|
||||
end_location:
|
||||
row: 527
|
||||
row: 532
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 521
|
||||
row: 526
|
||||
column: 0
|
||||
end_location:
|
||||
row: 521
|
||||
row: 526
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -5,35 +5,35 @@ expression: checks
|
||||
- kind:
|
||||
OneBlankLineAfterClass: 0
|
||||
location:
|
||||
row: 176
|
||||
row: 181
|
||||
column: 4
|
||||
end_location:
|
||||
row: 176
|
||||
row: 181
|
||||
column: 24
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 177
|
||||
row: 182
|
||||
column: 0
|
||||
end_location:
|
||||
row: 177
|
||||
row: 182
|
||||
column: 0
|
||||
- kind:
|
||||
OneBlankLineAfterClass: 0
|
||||
location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 4
|
||||
end_location:
|
||||
row: 187
|
||||
row: 192
|
||||
column: 45
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 188
|
||||
row: 193
|
||||
column: 0
|
||||
end_location:
|
||||
row: 188
|
||||
row: 193
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -4,34 +4,34 @@ expression: checks
|
||||
---
|
||||
- kind: BlankLineAfterSummary
|
||||
location:
|
||||
row: 195
|
||||
row: 200
|
||||
column: 4
|
||||
end_location:
|
||||
row: 198
|
||||
row: 203
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 196
|
||||
row: 201
|
||||
column: 0
|
||||
end_location:
|
||||
row: 196
|
||||
row: 201
|
||||
column: 0
|
||||
- kind: BlankLineAfterSummary
|
||||
location:
|
||||
row: 205
|
||||
row: 210
|
||||
column: 4
|
||||
end_location:
|
||||
row: 210
|
||||
row: 215
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: "\n"
|
||||
location:
|
||||
row: 206
|
||||
row: 211
|
||||
column: 0
|
||||
end_location:
|
||||
row: 208
|
||||
row: 213
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -4,34 +4,34 @@ expression: checks
|
||||
---
|
||||
- kind: NoUnderIndentation
|
||||
location:
|
||||
row: 227
|
||||
row: 232
|
||||
column: 0
|
||||
end_location:
|
||||
row: 227
|
||||
row: 232
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " "
|
||||
location:
|
||||
row: 227
|
||||
row: 232
|
||||
column: 0
|
||||
end_location:
|
||||
row: 227
|
||||
row: 232
|
||||
column: 0
|
||||
- kind: NoUnderIndentation
|
||||
location:
|
||||
row: 435
|
||||
row: 440
|
||||
column: 0
|
||||
end_location:
|
||||
row: 435
|
||||
row: 440
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " "
|
||||
location:
|
||||
row: 435
|
||||
row: 440
|
||||
column: 0
|
||||
end_location:
|
||||
row: 435
|
||||
row: 440
|
||||
column: 4
|
||||
|
||||
|
||||
@@ -4,50 +4,50 @@ expression: checks
|
||||
---
|
||||
- kind: NoOverIndentation
|
||||
location:
|
||||
row: 247
|
||||
row: 252
|
||||
column: 0
|
||||
end_location:
|
||||
row: 247
|
||||
row: 252
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " "
|
||||
location:
|
||||
row: 247
|
||||
row: 252
|
||||
column: 0
|
||||
end_location:
|
||||
row: 247
|
||||
row: 252
|
||||
column: 7
|
||||
- kind: NoOverIndentation
|
||||
location:
|
||||
row: 259
|
||||
row: 264
|
||||
column: 0
|
||||
end_location:
|
||||
row: 259
|
||||
row: 264
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " "
|
||||
location:
|
||||
row: 259
|
||||
row: 264
|
||||
column: 0
|
||||
end_location:
|
||||
row: 259
|
||||
row: 264
|
||||
column: 8
|
||||
- kind: NoOverIndentation
|
||||
location:
|
||||
row: 267
|
||||
row: 272
|
||||
column: 0
|
||||
end_location:
|
||||
row: 267
|
||||
row: 272
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: " "
|
||||
location:
|
||||
row: 267
|
||||
row: 272
|
||||
column: 0
|
||||
end_location:
|
||||
row: 267
|
||||
row: 272
|
||||
column: 8
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ expression: checks
|
||||
---
|
||||
- kind: NewLineAfterLastParagraph
|
||||
location:
|
||||
row: 276
|
||||
row: 281
|
||||
column: 4
|
||||
end_location:
|
||||
row: 278
|
||||
row: 283
|
||||
column: 19
|
||||
fix:
|
||||
patch:
|
||||
content: "\n "
|
||||
location:
|
||||
row: 278
|
||||
row: 283
|
||||
column: 16
|
||||
end_location:
|
||||
row: 278
|
||||
row: 283
|
||||
column: 16
|
||||
|
||||
|
||||
@@ -4,50 +4,50 @@ expression: checks
|
||||
---
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 283
|
||||
row: 288
|
||||
column: 4
|
||||
end_location:
|
||||
row: 283
|
||||
row: 288
|
||||
column: 33
|
||||
fix:
|
||||
patch:
|
||||
content: Whitespace at the end.
|
||||
location:
|
||||
row: 283
|
||||
row: 288
|
||||
column: 7
|
||||
end_location:
|
||||
row: 283
|
||||
row: 288
|
||||
column: 30
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 288
|
||||
row: 293
|
||||
column: 4
|
||||
end_location:
|
||||
row: 288
|
||||
row: 293
|
||||
column: 37
|
||||
fix:
|
||||
patch:
|
||||
content: Whitespace at everywhere.
|
||||
location:
|
||||
row: 288
|
||||
row: 293
|
||||
column: 7
|
||||
end_location:
|
||||
row: 288
|
||||
row: 293
|
||||
column: 34
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 294
|
||||
row: 299
|
||||
column: 4
|
||||
end_location:
|
||||
row: 297
|
||||
row: 302
|
||||
column: 7
|
||||
fix:
|
||||
patch:
|
||||
content: Whitespace at the beginning.
|
||||
location:
|
||||
row: 294
|
||||
row: 299
|
||||
column: 7
|
||||
end_location:
|
||||
row: 294
|
||||
row: 299
|
||||
column: 36
|
||||
|
||||
|
||||
@@ -5,35 +5,35 @@ expression: checks
|
||||
- kind:
|
||||
NoBlankLineBeforeClass: 1
|
||||
location:
|
||||
row: 165
|
||||
row: 170
|
||||
column: 4
|
||||
end_location:
|
||||
row: 165
|
||||
row: 170
|
||||
column: 29
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 164
|
||||
row: 169
|
||||
column: 0
|
||||
end_location:
|
||||
row: 165
|
||||
row: 170
|
||||
column: 0
|
||||
- kind:
|
||||
NoBlankLineBeforeClass: 1
|
||||
location:
|
||||
row: 176
|
||||
row: 181
|
||||
column: 4
|
||||
end_location:
|
||||
row: 176
|
||||
row: 181
|
||||
column: 24
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 175
|
||||
row: 180
|
||||
column: 0
|
||||
end_location:
|
||||
row: 176
|
||||
row: 181
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: MultiLineSummaryFirstLine
|
||||
location:
|
||||
row: 124
|
||||
row: 129
|
||||
column: 4
|
||||
end_location:
|
||||
row: 126
|
||||
row: 131
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,154 +4,154 @@ expression: checks
|
||||
---
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 195
|
||||
row: 200
|
||||
column: 4
|
||||
end_location:
|
||||
row: 198
|
||||
row: 203
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 205
|
||||
column: 4
|
||||
end_location:
|
||||
row: 210
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
column: 4
|
||||
end_location:
|
||||
row: 215
|
||||
column: 4
|
||||
end_location:
|
||||
row: 219
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 225
|
||||
row: 220
|
||||
column: 4
|
||||
end_location:
|
||||
row: 229
|
||||
row: 224
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 235
|
||||
row: 230
|
||||
column: 4
|
||||
end_location:
|
||||
row: 239
|
||||
row: 234
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 240
|
||||
column: 4
|
||||
end_location:
|
||||
row: 244
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 245
|
||||
row: 250
|
||||
column: 4
|
||||
end_location:
|
||||
row: 249
|
||||
row: 254
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 255
|
||||
row: 260
|
||||
column: 4
|
||||
end_location:
|
||||
row: 259
|
||||
row: 264
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 265
|
||||
row: 270
|
||||
column: 4
|
||||
end_location:
|
||||
row: 269
|
||||
row: 274
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 276
|
||||
row: 281
|
||||
column: 4
|
||||
end_location:
|
||||
row: 278
|
||||
row: 283
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 294
|
||||
row: 299
|
||||
column: 4
|
||||
end_location:
|
||||
row: 297
|
||||
row: 302
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 338
|
||||
column: 4
|
||||
end_location:
|
||||
row: 343
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 378
|
||||
column: 4
|
||||
end_location:
|
||||
row: 381
|
||||
row: 348
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 387
|
||||
row: 383
|
||||
column: 4
|
||||
end_location:
|
||||
row: 391
|
||||
row: 386
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 433
|
||||
row: 392
|
||||
column: 4
|
||||
end_location:
|
||||
row: 396
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 438
|
||||
column: 36
|
||||
end_location:
|
||||
row: 436
|
||||
row: 441
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 445
|
||||
row: 450
|
||||
column: 4
|
||||
end_location:
|
||||
row: 449
|
||||
row: 454
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 521
|
||||
row: 526
|
||||
column: 4
|
||||
end_location:
|
||||
row: 527
|
||||
row: 532
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 541
|
||||
row: 546
|
||||
column: 4
|
||||
end_location:
|
||||
row: 544
|
||||
row: 549
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 550
|
||||
row: 555
|
||||
column: 4
|
||||
end_location:
|
||||
row: 553
|
||||
row: 558
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 563
|
||||
row: 568
|
||||
column: 4
|
||||
end_location:
|
||||
row: 566
|
||||
row: 571
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 302
|
||||
column: 4
|
||||
end_location:
|
||||
row: 302
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 307
|
||||
@@ -24,7 +16,7 @@ expression: checks
|
||||
column: 4
|
||||
end_location:
|
||||
row: 312
|
||||
column: 15
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
@@ -36,10 +28,18 @@ expression: checks
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 323
|
||||
row: 322
|
||||
column: 4
|
||||
end_location:
|
||||
row: 323
|
||||
row: 322
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 328
|
||||
column: 4
|
||||
end_location:
|
||||
row: 328
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,60 +4,52 @@ expression: checks
|
||||
---
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 350
|
||||
row: 355
|
||||
column: 4
|
||||
end_location:
|
||||
row: 350
|
||||
row: 355
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 401
|
||||
row: 406
|
||||
column: 24
|
||||
end_location:
|
||||
row: 401
|
||||
row: 406
|
||||
column: 39
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 405
|
||||
row: 410
|
||||
column: 4
|
||||
end_location:
|
||||
row: 405
|
||||
row: 410
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 411
|
||||
row: 416
|
||||
column: 4
|
||||
end_location:
|
||||
row: 411
|
||||
row: 416
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 417
|
||||
row: 422
|
||||
column: 34
|
||||
end_location:
|
||||
row: 417
|
||||
row: 422
|
||||
column: 49
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 424
|
||||
row: 429
|
||||
column: 48
|
||||
end_location:
|
||||
row: 424
|
||||
row: 429
|
||||
column: 63
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 465
|
||||
column: 4
|
||||
end_location:
|
||||
row: 465
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 470
|
||||
@@ -76,34 +68,42 @@ expression: checks
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 482
|
||||
row: 480
|
||||
column: 4
|
||||
end_location:
|
||||
row: 482
|
||||
row: 480
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 504
|
||||
row: 487
|
||||
column: 4
|
||||
end_location:
|
||||
row: 504
|
||||
row: 487
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 509
|
||||
column: 4
|
||||
end_location:
|
||||
row: 509
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 509
|
||||
row: 514
|
||||
column: 4
|
||||
end_location:
|
||||
row: 509
|
||||
row: 514
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 515
|
||||
row: 520
|
||||
column: 4
|
||||
end_location:
|
||||
row: 515
|
||||
row: 520
|
||||
column: 32
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: NoSignature
|
||||
location:
|
||||
row: 373
|
||||
row: 378
|
||||
column: 4
|
||||
end_location:
|
||||
row: 373
|
||||
row: 378
|
||||
column: 30
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,60 +4,52 @@ expression: checks
|
||||
---
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 350
|
||||
row: 355
|
||||
column: 4
|
||||
end_location:
|
||||
row: 350
|
||||
row: 355
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 401
|
||||
row: 406
|
||||
column: 24
|
||||
end_location:
|
||||
row: 401
|
||||
row: 406
|
||||
column: 39
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 405
|
||||
row: 410
|
||||
column: 4
|
||||
end_location:
|
||||
row: 405
|
||||
row: 410
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 411
|
||||
row: 416
|
||||
column: 4
|
||||
end_location:
|
||||
row: 411
|
||||
row: 416
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 417
|
||||
row: 422
|
||||
column: 34
|
||||
end_location:
|
||||
row: 417
|
||||
row: 422
|
||||
column: 49
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 424
|
||||
row: 429
|
||||
column: 48
|
||||
end_location:
|
||||
row: 424
|
||||
row: 429
|
||||
column: 63
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 465
|
||||
column: 4
|
||||
end_location:
|
||||
row: 465
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 470
|
||||
@@ -76,26 +68,34 @@ expression: checks
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 482
|
||||
row: 480
|
||||
column: 4
|
||||
end_location:
|
||||
row: 482
|
||||
row: 480
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 504
|
||||
row: 487
|
||||
column: 4
|
||||
end_location:
|
||||
row: 504
|
||||
row: 487
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 509
|
||||
column: 4
|
||||
end_location:
|
||||
row: 509
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 515
|
||||
row: 520
|
||||
column: 4
|
||||
end_location:
|
||||
row: 515
|
||||
row: 520
|
||||
column: 32
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,26 +4,26 @@ expression: checks
|
||||
---
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 33
|
||||
row: 34
|
||||
column: 4
|
||||
end_location:
|
||||
row: 37
|
||||
row: 38
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 85
|
||||
row: 90
|
||||
column: 4
|
||||
end_location:
|
||||
row: 89
|
||||
row: 94
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 105
|
||||
row: 110
|
||||
column: 0
|
||||
end_location:
|
||||
row: 110
|
||||
row: 115
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -4,26 +4,26 @@ expression: checks
|
||||
---
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 19
|
||||
row: 20
|
||||
column: 8
|
||||
end_location:
|
||||
row: 19
|
||||
row: 20
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 69
|
||||
row: 74
|
||||
column: 4
|
||||
end_location:
|
||||
row: 69
|
||||
row: 74
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 75
|
||||
row: 80
|
||||
column: 8
|
||||
end_location:
|
||||
row: 75
|
||||
row: 80
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__F501_F50x.py.snap
Normal file
14
src/snapshots/ruff__linter__tests__F501_F50x.py.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
PercentFormatInvalidFormat: incomplete format
|
||||
location:
|
||||
row: 1
|
||||
column: 9
|
||||
end_location:
|
||||
row: 1
|
||||
column: 25
|
||||
fix: ~
|
||||
|
||||
61
src/snapshots/ruff__linter__tests__F502_F502.py.snap
Normal file
61
src/snapshots/ruff__linter__tests__F502_F502.py.snap
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 6
|
||||
column: 10
|
||||
end_location:
|
||||
row: 6
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 7
|
||||
column: 10
|
||||
end_location:
|
||||
row: 7
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 8
|
||||
column: 10
|
||||
end_location:
|
||||
row: 8
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 22
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
end_location:
|
||||
row: 11
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 12
|
||||
column: 10
|
||||
end_location:
|
||||
row: 12
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 13
|
||||
column: 10
|
||||
end_location:
|
||||
row: 13
|
||||
column: 37
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__F502_F50x.py.snap
Normal file
13
src/snapshots/ruff__linter__tests__F502_F50x.py.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PercentFormatExpectedMapping
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user