Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
050f34dd25 | ||
|
|
1cd82d588b | ||
|
|
cea9e34942 | ||
|
|
1ede377402 | ||
|
|
82eff641fb | ||
|
|
eb1bc9f092 | ||
|
|
df88504dea | ||
|
|
b9ec3e9137 | ||
|
|
b067b665ff | ||
|
|
22cfd03b13 | ||
|
|
3ae408a364 | ||
|
|
95073b1586 | ||
|
|
3597a94818 | ||
|
|
2727e5f3b7 | ||
|
|
de53b567f5 | ||
|
|
21479a151d | ||
|
|
d822458d97 | ||
|
|
6741ea9790 | ||
|
|
2e1799dd80 | ||
|
|
c48c7669a8 | ||
|
|
d5fbcd708d | ||
|
|
b335a6a5ec | ||
|
|
f5cbee452a | ||
|
|
5d1ea4410a | ||
|
|
f27163d2c7 | ||
|
|
62c126f70e | ||
|
|
8344d5151c | ||
|
|
f7780eb720 | ||
|
|
c544d84b46 | ||
|
|
fbdc075e5c | ||
|
|
d3472104d0 | ||
|
|
0c8b981216 | ||
|
|
0e3f08aa68 | ||
|
|
a75b1c85ee | ||
|
|
726e6c68cf | ||
|
|
34c91224d7 | ||
|
|
50c4bbc52b | ||
|
|
f0239de559 | ||
|
|
2be632f3cc | ||
|
|
ec3fed5a61 | ||
|
|
1da44485d5 | ||
|
|
e5f30ff5a8 | ||
|
|
c92f5c14a3 | ||
|
|
2f92399f4e | ||
|
|
83dfb5fe8b | ||
|
|
cc915371ca | ||
|
|
5576db3d5a | ||
|
|
f26f38d023 | ||
|
|
578ec4d843 | ||
|
|
2f3bebe5a2 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
dev = "run --package ruff_dev --bin ruff_dev"
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.98
|
||||
rev: v0.0.104
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Contributing to ruff
|
||||
# Contributing to Ruff
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to ruff.
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||
|
||||
## The basics
|
||||
|
||||
ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
|
||||
Ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
|
||||
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
|
||||
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
|
||||
your proposed change.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
ruff is written in Rust (1.63.0). You'll need to install the
|
||||
Ruff is written in Rust. You'll need to install the
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
||||
@@ -22,7 +22,7 @@ cargo install cargo-insta
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ruff locally with:
|
||||
After cloning the repository, run Ruff locally with:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures --no-cache
|
||||
@@ -32,9 +32,9 @@ Prior to opening a pull request, ensure that your code has been auto-formatted,
|
||||
both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo fmt # Auto-formatting...
|
||||
cargo clippy # Linting...
|
||||
cargo test # Testing...
|
||||
cargo +nightly fmt --all # Auto-formatting...
|
||||
cargo +nightly clippy --all # Linting...
|
||||
cargo +nightly test --all # Testing...
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
|
||||
@@ -45,12 +45,13 @@ prior to merging.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
There are three phases to adding a new lint rule:
|
||||
There are four phases to adding a new lint rule:
|
||||
|
||||
1. Define the rule in `src/checks.rs`.
|
||||
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks)
|
||||
or `src/check_lines.rs` (for text-based checks).
|
||||
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks),
|
||||
`src/check_tokens.rs` (for token-based checks), or `src/check_lines.rs` (for text-based checks).
|
||||
3. Add a test fixture.
|
||||
4. Update the generated files (documentation and generated code).
|
||||
|
||||
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
|
||||
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
|
||||
@@ -58,37 +59,33 @@ pattern implemented therein.
|
||||
|
||||
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
|
||||
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
|
||||
lint-rule violations as it goes. Grep for the `Check::new` invocations to understand how other,
|
||||
similar rules are implemented.
|
||||
lint-rule violations as it goes. If you need to inspect the AST, you can run `cargo dev print-ast`
|
||||
with a Python file. Grep for the `Check::new` invocations to understand how other, similar rules
|
||||
are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
|
||||
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
|
||||
`src/linter.rs`, like so:
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run Ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache`. Once you're satisfied with the
|
||||
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
|
||||
section of `src/linter.rs`, like so:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn e402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E402.py"),
|
||||
&settings::Settings::for_rule(CheckCode::E402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
|
||||
...
|
||||
```
|
||||
|
||||
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
|
||||
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
|
||||
rest of your changes.
|
||||
|
||||
Finally, to update the documentation, run `cargo dev generate-rules-table` from the repo root. To
|
||||
update the generated prefix map, run `cargo dev generate-check-code-prefix`. Both of these commands
|
||||
should be run whenever a new check is added to the codebase.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
ruff's user-facing settings live in two places: first, the command-line options defined with
|
||||
Ruff's user-facing settings live in two places: first, the command-line options defined with
|
||||
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
|
||||
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
|
||||
settings from a `pyproject.toml` file.
|
||||
@@ -103,9 +100,9 @@ acceptable unused variables (e.g., `_`).
|
||||
|
||||
## Release process
|
||||
|
||||
As of now, ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
Actions, which automatically generates the appropriate wheels across architectures and publishes
|
||||
them to [PyPI](https://pypi.org/project/ruff/).
|
||||
|
||||
ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
|
||||
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -920,7 +920,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.98-dev.0"
|
||||
version = "0.0.104-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.15",
|
||||
@@ -2209,9 +2209,19 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd22239fafefc42138ca5da064f3c17726a80d2379d817a3521240e78dd0064"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"str_indices",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.98"
|
||||
version = "0.0.104"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
@@ -2220,7 +2230,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap 4.0.15",
|
||||
"clearscreen",
|
||||
"codegen",
|
||||
"colored",
|
||||
"common-path",
|
||||
"criterion",
|
||||
@@ -2239,6 +2248,7 @@ dependencies = [
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ropey",
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
@@ -2254,6 +2264,22 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.104"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.15",
|
||||
"codegen",
|
||||
"itertools",
|
||||
"ruff",
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.6"
|
||||
@@ -2559,6 +2585,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "str_indices"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.4"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"flake8_to_ruff",
|
||||
"flake8_to_ruff",
|
||||
"ruff_dev",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.98"
|
||||
version = "0.0.104"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -31,6 +32,7 @@ once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
@@ -55,7 +57,6 @@ getrandom = { version = "0.2.7", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
codegen = { version = "0.2.0" }
|
||||
criterion = { version = "0.4.0" }
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
|
||||
91
README.md
91
README.md
@@ -31,6 +31,10 @@ of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](htt
|
||||
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) 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 [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
|
||||
and [Saleor](https://github.com/saleor/saleor).
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
## Table of Contents
|
||||
@@ -39,16 +43,16 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
2. [Configuration](#configuration)
|
||||
3. [Supported Rules](#supported-rules)
|
||||
1. [Pyflakes](#pyflakes)
|
||||
2. [pycodestyle (error)](#pycodestyle-error)
|
||||
3. [pycodestyle (warning)](#pycodestyle-warning)
|
||||
4. [pydocstyle](#pydocstyle)
|
||||
5. [pyupgrade](#pyupgrade)
|
||||
6. [pep8-naming](#pep8-naming)
|
||||
7. [flake8-comprehensions](#flake8-comprehensions)
|
||||
8. [flake8-bugbear](#flake8-bugbear)
|
||||
9. [flake8-builtins](#flake8-builtins)
|
||||
10. [flake8-print](#flake8-print)
|
||||
11. [flake8-quotes](#flake8-quotes)
|
||||
2. [pycodestyle](#pycodestyle)
|
||||
3. [pydocstyle](#pydocstyle)
|
||||
4. [pyupgrade](#pyupgrade)
|
||||
5. [pep8-naming](#pep8-naming)
|
||||
6. [flake8-comprehensions](#flake8-comprehensions)
|
||||
7. [flake8-bugbear](#flake8-bugbear)
|
||||
8. [flake8-builtins](#flake8-builtins)
|
||||
9. [flake8-print](#flake8-print)
|
||||
10. [flake8-quotes](#flake8-quotes)
|
||||
11. [Ruff-specific rules](#ruff-specific-rules)
|
||||
12. [Meta rules](#meta-rules)
|
||||
5. [Editor Integrations](#editor-integrations)
|
||||
6. [FAQ](#faq)
|
||||
@@ -89,7 +93,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.98
|
||||
rev: v0.0.104
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -290,12 +294,19 @@ add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
## Supported Rules
|
||||
|
||||
Regardless of the rule's origin, Ruff re-implements every rule in Rust as a first-party feature.
|
||||
|
||||
By default, Ruff enables all `E` and `F` error codes, which correspond to those built-in to Flake8.
|
||||
|
||||
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
|
||||
|
||||
<!-- Sections automatically generated by `cargo dev generate-rules-table`. -->
|
||||
<!-- Begin auto-generated sections. -->
|
||||
|
||||
### Pyflakes
|
||||
|
||||
For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| F401 | UnusedImport | `...` imported but unused | 🛠 |
|
||||
@@ -311,7 +322,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
|
||||
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
|
||||
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
|
||||
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | |
|
||||
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | 🛠 |
|
||||
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | |
|
||||
| F634 | IfTuple | If test is a tuple, which is always `True` | |
|
||||
| F701 | BreakOutsideLoop | `break` outside loop | |
|
||||
@@ -325,9 +336,11 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
|
||||
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
|
||||
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | |
|
||||
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
|
||||
|
||||
### pycodestyle (error)
|
||||
### pycodestyle
|
||||
|
||||
For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
@@ -345,16 +358,13 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
|
||||
| E902 | IOError | IOError: `...` | |
|
||||
| E999 | SyntaxError | SyntaxError: `...` | |
|
||||
|
||||
### pycodestyle (warning)
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
|
||||
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
|
||||
|
||||
### pydocstyle
|
||||
|
||||
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| D100 | PublicModule | Missing docstring in public module | |
|
||||
@@ -404,6 +414,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
|
||||
### pyupgrade
|
||||
|
||||
For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
|
||||
@@ -414,9 +426,12 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
|
||||
| U009 | PEP3120UnnecessaryCodingComment | utf-8 encoding declaration is unnecessary | 🛠 |
|
||||
|
||||
### pep8-naming
|
||||
|
||||
For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| N801 | InvalidClassName | Class name `...` should use CapWords convention | |
|
||||
@@ -437,6 +452,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
|
||||
### flake8-comprehensions
|
||||
|
||||
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
|
||||
@@ -445,7 +462,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
|
||||
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
|
||||
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | |
|
||||
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
|
||||
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
|
||||
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
|
||||
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
|
||||
@@ -453,24 +470,33 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
|
||||
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
|
||||
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
|
||||
|
||||
### flake8-bugbear
|
||||
|
||||
For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
| B015 | UselessComparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
|
||||
| B016 | CannotRaiseLiteral | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
|
||||
### flake8-builtins
|
||||
|
||||
For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | |
|
||||
@@ -479,6 +505,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
|
||||
### flake8-print
|
||||
|
||||
For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| T201 | PrintFound | `print` found | 🛠 |
|
||||
@@ -486,6 +514,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
|
||||
### flake8-quotes
|
||||
|
||||
For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | |
|
||||
@@ -493,12 +523,21 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
|
||||
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
|
||||
|
||||
### Ruff-specific rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
|
||||
|
||||
### Meta rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive | 🛠 |
|
||||
|
||||
<!-- End auto-generated sections. -->
|
||||
|
||||
## Editor Integrations
|
||||
|
||||
### PyCharm
|
||||
@@ -567,8 +606,8 @@ including:
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
@@ -589,10 +628,10 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
|
||||
|
||||
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34).
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -684,7 +723,7 @@ extend-ignore = [
|
||||
|
||||
## Development
|
||||
|
||||
Ruff is written in Rust (1.64.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
Ruff is written in Rust (1.65.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
for development.
|
||||
|
||||
Assuming you have `cargo` installed, you can run:
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use ropey::Rope;
|
||||
use ruff::fs;
|
||||
use ruff::source_code_locator::compute_offsets;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
c.bench_function("compute_offsets", |b| {
|
||||
b.iter(|| compute_offsets(black_box(&contents)))
|
||||
c.bench_function("rope", |b| {
|
||||
b.iter(|| {
|
||||
let rope = Rope::from_str(black_box(&contents));
|
||||
rope.line_to_char(black_box(4));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
//! Generate a Markdown-compatible table of supported lint rules.
|
||||
|
||||
use ruff::checks::{CheckCategory, CheckCode};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn main() {
|
||||
for check_category in CheckCategory::iter() {
|
||||
println!("### {}", check_category.title());
|
||||
println!();
|
||||
|
||||
println!("| Code | Name | Message | Fix |");
|
||||
println!("| ---- | ---- | ------- | --- |");
|
||||
for check_code in CheckCode::iter() {
|
||||
if check_code.category() == check_category {
|
||||
let check_kind = check_code.kind();
|
||||
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
|
||||
println!(
|
||||
"| {} | {} | {} | {} |",
|
||||
check_kind.code().as_ref(),
|
||||
check_kind.as_ref(),
|
||||
check_kind.summary().replace("|", r"\|"),
|
||||
fix_token
|
||||
);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.98"
|
||||
version = "0.0.104"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.98"
|
||||
version = "0.0.104"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.98-dev.0"
|
||||
version = "0.0.104-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -17,7 +17,7 @@ pip install flake8-to-ruff
|
||||
|
||||
### Usage
|
||||
|
||||
To run Ruff, try any of the following:
|
||||
To run `flake8-to-ruff`:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/setup.cfg
|
||||
@@ -25,6 +25,60 @@ flake8-to-ruff path/to/tox.ini
|
||||
flake8-to-ruff path/to/.flake8
|
||||
```
|
||||
|
||||
`flake8-to-ruff` will print the relevant `pyproject.toml` sections to standard output, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
'.svn',
|
||||
'CVS',
|
||||
'.bzr',
|
||||
'.hg',
|
||||
'.git',
|
||||
'__pycache__',
|
||||
'.tox',
|
||||
'.idea',
|
||||
'.mypy_cache',
|
||||
'.venv',
|
||||
'node_modules',
|
||||
'_state_machine.py',
|
||||
'test_fstring.py',
|
||||
'bad_coding2.py',
|
||||
'badsyntax_*.py',
|
||||
]
|
||||
select = [
|
||||
'A',
|
||||
'E',
|
||||
'F',
|
||||
'Q',
|
||||
]
|
||||
ignore = []
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = 'single'
|
||||
|
||||
[tool.ruff.pep8-naming]
|
||||
ignore-names = [
|
||||
'foo',
|
||||
'bar',
|
||||
]
|
||||
```
|
||||
|
||||
### Plugins
|
||||
|
||||
`flake8-to-ruff` will attempt to infer any activated plugins based on the settings provided in your
|
||||
configuration file.
|
||||
|
||||
For example, if your `.flake8` file includes a `docstring-convention` property, `flake8-to-ruff`
|
||||
will enable the appropriate [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||
checks.
|
||||
|
||||
Alternatively, you can manually specify plugins on the command-line:
|
||||
|
||||
```shell
|
||||
flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
||||
@@ -33,9 +87,6 @@ flake8-to-ruff path/to/.flake8
|
||||
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
||||
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
||||
for the complete list of supported plugins.)
|
||||
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
|
||||
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
|
||||
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
36
flake8_to_ruff/examples/poetry.ini
Normal file
36
flake8_to_ruff/examples/poetry.ini
Normal file
@@ -0,0 +1,36 @@
|
||||
[flake8]
|
||||
min_python_version = 3.7.0
|
||||
max-line-length = 88
|
||||
ban-relative-imports = true
|
||||
# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy
|
||||
format-greedy = 1
|
||||
inline-quotes = double
|
||||
enable-extensions = TC, TC1
|
||||
type-checking-strict = true
|
||||
eradicate-whitelist-extend = ^-.*;
|
||||
extend-ignore =
|
||||
# E203: Whitespace before ':' (pycqa/pycodestyle#373)
|
||||
E203,
|
||||
# SIM106: Handle error-cases first
|
||||
SIM106,
|
||||
# ANN101: Missing type annotation for self in method
|
||||
ANN101,
|
||||
# ANN102: Missing type annotation for cls in classmethod
|
||||
ANN102,
|
||||
# PIE781: assign-and-return
|
||||
PIE781,
|
||||
# PIE798 no-unnecessary-class: Consider using a module for namespacing instead
|
||||
PIE798,
|
||||
per-file-ignores =
|
||||
# TC002: Move third-party import '...' into a type-checking block
|
||||
__init__.py:TC002,
|
||||
# ANN201: Missing return type annotation for public function
|
||||
tests/test_*:ANN201
|
||||
tests/**/test_*:ANN201
|
||||
extend-exclude =
|
||||
# Frozen and not subject to change in this repo:
|
||||
get-poetry.py,
|
||||
install-poetry.py,
|
||||
# External to the project's coding standards:
|
||||
tests/fixtures/*,
|
||||
tests/**/fixtures/*,
|
||||
19
flake8_to_ruff/examples/python-discord.ini
Normal file
19
flake8_to_ruff/examples/python-discord.ini
Normal file
@@ -0,0 +1,19 @@
|
||||
[flake8]
|
||||
max-line-length=120
|
||||
docstring-convention=all
|
||||
import-order-style=pycharm
|
||||
application_import_names=bot,tests
|
||||
exclude=.cache,.venv,.git,constants.py
|
||||
extend-ignore=
|
||||
B311,W503,E226,S311,T000,E731
|
||||
# Missing Docstrings
|
||||
D100,D104,D105,D107,
|
||||
# Docstring Whitespace
|
||||
D203,D212,D214,D215,
|
||||
# Docstring Quotes
|
||||
D301,D302,
|
||||
# Docstring Content
|
||||
D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417
|
||||
# Type Annotations
|
||||
ANN002,ANN003,ANN101,ANN102,ANN204,ANN206,ANN401
|
||||
per-file-ignores=tests/*:D,ANN
|
||||
6
flake8_to_ruff/examples/requests.ini
Normal file
6
flake8_to_ruff/examples/requests.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[flake8]
|
||||
ignore = E203, E501, W503
|
||||
per-file-ignores =
|
||||
requests/__init__.py:E402, F401
|
||||
requests/compat.py:E402, F401
|
||||
tests/compat.py:F401
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::flake8_quotes::settings::Quote;
|
||||
@@ -6,13 +6,36 @@ use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_quotes, pep8_naming};
|
||||
|
||||
use crate::parser;
|
||||
use crate::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
|
||||
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file.");
|
||||
pub fn convert(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
plugins: Option<Vec<Plugin>>,
|
||||
) -> Result<Pyproject> {
|
||||
// Check if the user has specified a `select`. If not, we'll add our own
|
||||
// default `select`, and populate it based on user plugins.
|
||||
let mut select = flake8
|
||||
.get("select")
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
plugin::resolve_select(
|
||||
flake8,
|
||||
&plugins.unwrap_or_else(|| plugin::infer_plugins(flake8)),
|
||||
)
|
||||
});
|
||||
let mut ignore = flake8
|
||||
.get("ignore")
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options: Options = Default::default();
|
||||
@@ -27,16 +50,18 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
},
|
||||
"select" => {
|
||||
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-select" | "extend_select" => {
|
||||
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
// No-op (handled above).
|
||||
}
|
||||
"ignore" => {
|
||||
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
// No-op (handled above).
|
||||
}
|
||||
"extend-select" | "extend_select" => {
|
||||
// Unlike Flake8, use a single explicit `select`.
|
||||
select.extend(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"extend-ignore" | "extend_ignore" => {
|
||||
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
||||
// Unlike Flake8, use a single explicit `ignore`.
|
||||
ignore.extend(parser::parse_prefix_codes(value.as_ref()));
|
||||
}
|
||||
"exclude" => {
|
||||
options.exclude = Some(parser::parse_strings(value.as_ref()));
|
||||
@@ -86,12 +111,19 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
|
||||
pep8_naming.staticmethod_decorators =
|
||||
Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
// flake8-docstrings
|
||||
"docstring-convention" => {
|
||||
// No-op (handled above).
|
||||
}
|
||||
// Unknown
|
||||
_ => eprintln!("Skipping unsupported property: {key}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and sort.
|
||||
options.select = Some(Vec::from_iter(select));
|
||||
options.ignore = Some(Vec::from_iter(ignore));
|
||||
if flake8_quotes != Default::default() {
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
@@ -108,22 +140,28 @@ mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::convert;
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(HashMap::from([("flake8".to_string(), HashMap::from([]))]))?;
|
||||
let actual = convert(&HashMap::from([]), None)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -138,17 +176,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_converts_dashes() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
)]))?;
|
||||
let actual = convert(
|
||||
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -163,17 +205,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_converts_underscores() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
)]))?;
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(100),
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -188,17 +234,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn it_ignores_parse_errors() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
)]))?;
|
||||
let actual = convert(
|
||||
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
@@ -212,18 +262,124 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_extensions() -> Result<()> {
|
||||
let actual = convert(HashMap::from([(
|
||||
"flake8".to_string(),
|
||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
)]))?;
|
||||
fn it_converts_plugin_options() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_docstring_conventions() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([(
|
||||
"docstring-convention".to_string(),
|
||||
Some("numpy".to_string()),
|
||||
)]),
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
CheckCodePrefix::D214,
|
||||
CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D403,
|
||||
CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
CheckCodePrefix::D406,
|
||||
CheckCodePrefix::D407,
|
||||
CheckCodePrefix::D408,
|
||||
CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
CheckCodePrefix::D414,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_quotes: None,
|
||||
pep8_naming: None,
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_infers_plugins_if_omitted() -> Result<()> {
|
||||
let actual = convert(
|
||||
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: None,
|
||||
exclude: None,
|
||||
extend_exclude: None,
|
||||
select: Some(vec![
|
||||
CheckCodePrefix::E,
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::Q,
|
||||
CheckCodePrefix::W,
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: Some(vec![]),
|
||||
extend_ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
pub mod plugin;
|
||||
|
||||
@@ -6,6 +6,7 @@ use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
use flake8_to_ruff::converter;
|
||||
use flake8_to_ruff::plugin::Plugin;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -17,6 +18,9 @@ struct Cli {
|
||||
/// '.flake8').
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
/// List of plugins to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Option<Vec<Plugin>>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -27,8 +31,13 @@ fn main() -> Result<()> {
|
||||
ini.set_multiline(true);
|
||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||
|
||||
// Extract the Flake8 section.
|
||||
let flake8 = config
|
||||
.get("flake8")
|
||||
.expect("Unable to find flake8 section in INI file.");
|
||||
|
||||
// Create the pyproject.toml.
|
||||
let pyproject = converter::convert(config)?;
|
||||
let pyproject = converter::convert(flake8, cli.plugin)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
|
||||
321
flake8_to_ruff/src/plugin.rs
Normal file
321
flake8_to_ruff/src/plugin.rs
Normal file
@@ -0,0 +1,321 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Docstrings,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
PEP8Naming,
|
||||
Pyupgrade,
|
||||
}
|
||||
|
||||
impl FromStr for Plugin {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||
_ => Err(anyhow!("Unknown plugin: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
|
||||
Plugin::Flake8Docstrings => {
|
||||
// Use the user-provided docstring.
|
||||
for key in ["docstring-convention", "docstring_convention"] {
|
||||
if let Some(Some(value)) = flake8.get(key) {
|
||||
match DocstringConvention::from_str(value) {
|
||||
Ok(convention) => return convention.select(),
|
||||
Err(e) => {
|
||||
eprintln!("Unexpected '{key}' value: {value} ({e}");
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default to PEP8.
|
||||
DocstringConvention::PEP8.select()
|
||||
}
|
||||
Plugin::Flake8Print => vec![CheckCodePrefix::T],
|
||||
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DocstringConvention {
|
||||
All,
|
||||
PEP8,
|
||||
NumPy,
|
||||
Google,
|
||||
}
|
||||
|
||||
impl FromStr for DocstringConvention {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"all" => Ok(DocstringConvention::All),
|
||||
"pep8" => Ok(DocstringConvention::PEP8),
|
||||
"numpy" => Ok(DocstringConvention::NumPy),
|
||||
"google" => Ok(DocstringConvention::Google),
|
||||
_ => Err(anyhow!("Unknown docstring convention: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DocstringConvention {
|
||||
fn select(&self) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
DocstringConvention::All => vec![CheckCodePrefix::D],
|
||||
DocstringConvention::PEP8 => vec![
|
||||
// All errors except D203, D212, D213, D214, D215, D404, D405, D406, D407, D408,
|
||||
// D409, D410, D411, D413, D415, D416 and D417.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
// CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
// CheckCodePrefix::D214,
|
||||
// CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
// CheckCodePrefix::D404,
|
||||
// CheckCodePrefix::D405,
|
||||
// CheckCodePrefix::D406,
|
||||
// CheckCodePrefix::D407,
|
||||
// CheckCodePrefix::D408,
|
||||
// CheckCodePrefix::D409,
|
||||
// CheckCodePrefix::D410,
|
||||
// CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
// CheckCodePrefix::D415,
|
||||
// CheckCodePrefix::D416,
|
||||
// CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
DocstringConvention::NumPy => vec![
|
||||
// All errors except D107, D203, D212, D213, D402, D413, D415, D416, and D417.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
// CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
// CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
CheckCodePrefix::D214,
|
||||
CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D400,
|
||||
// CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
CheckCodePrefix::D406,
|
||||
CheckCodePrefix::D407,
|
||||
CheckCodePrefix::D408,
|
||||
CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
// CheckCodePrefix::D415,
|
||||
// CheckCodePrefix::D416,
|
||||
// CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
DocstringConvention::Google => vec![
|
||||
// All errors except D203, D204, D213, D215, D400, D401, D404, D406, D407, D408,
|
||||
// D409 and D413.
|
||||
CheckCodePrefix::D100,
|
||||
CheckCodePrefix::D101,
|
||||
CheckCodePrefix::D102,
|
||||
CheckCodePrefix::D103,
|
||||
CheckCodePrefix::D104,
|
||||
CheckCodePrefix::D105,
|
||||
CheckCodePrefix::D106,
|
||||
CheckCodePrefix::D107,
|
||||
CheckCodePrefix::D200,
|
||||
CheckCodePrefix::D201,
|
||||
CheckCodePrefix::D202,
|
||||
// CheckCodePrefix::D203,
|
||||
// CheckCodePrefix::D204,
|
||||
CheckCodePrefix::D205,
|
||||
CheckCodePrefix::D206,
|
||||
CheckCodePrefix::D207,
|
||||
CheckCodePrefix::D208,
|
||||
CheckCodePrefix::D209,
|
||||
CheckCodePrefix::D210,
|
||||
CheckCodePrefix::D211,
|
||||
CheckCodePrefix::D212,
|
||||
// CheckCodePrefix::D213,
|
||||
CheckCodePrefix::D214,
|
||||
// CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
// CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D402,
|
||||
CheckCodePrefix::D403,
|
||||
// CheckCodePrefix::D404,
|
||||
CheckCodePrefix::D405,
|
||||
// CheckCodePrefix::D406,
|
||||
// CheckCodePrefix::D407,
|
||||
// CheckCodePrefix::D408,
|
||||
// CheckCodePrefix::D409,
|
||||
CheckCodePrefix::D410,
|
||||
CheckCodePrefix::D411,
|
||||
CheckCodePrefix::D412,
|
||||
// CheckCodePrefix::D413,
|
||||
CheckCodePrefix::D414,
|
||||
CheckCodePrefix::D415,
|
||||
CheckCodePrefix::D416,
|
||||
CheckCodePrefix::D417,
|
||||
CheckCodePrefix::D418,
|
||||
CheckCodePrefix::D419,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer the enabled plugins based on user-provided settings.
|
||||
pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
|
||||
let mut plugins = BTreeSet::new();
|
||||
for key in flake8.keys() {
|
||||
match key.as_str() {
|
||||
// flake8-docstrings
|
||||
"docstring-convention" | "docstring_convention" => {
|
||||
plugins.insert(Plugin::Flake8Docstrings);
|
||||
}
|
||||
// flake8-builtins
|
||||
"builtins-ignorelist" | "builtins_ignorelist" => {
|
||||
plugins.insert(Plugin::Flake8Builtins);
|
||||
}
|
||||
// flake8-quotes
|
||||
"quotes" | "inline-quotes" | "inline_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"multiline-quotes" | "multiline_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"docstring-quotes" | "docstring_quotes" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
"avoid-escape" | "avoid_escape" => {
|
||||
plugins.insert(Plugin::Flake8Quotes);
|
||||
}
|
||||
// pep8-naming
|
||||
"ignore-names" | "ignore_names" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
"classmethod-decorators" | "classmethod_decorators" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
"staticmethod-decorators" | "staticmethod_decorators" => {
|
||||
plugins.insert(Plugin::PEP8Naming);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Vec::from_iter(plugins)
|
||||
}
|
||||
|
||||
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
|
||||
pub fn resolve_select(
|
||||
flake8: &HashMap<String, Option<String>>,
|
||||
plugins: &[Plugin],
|
||||
) -> BTreeSet<CheckCodePrefix> {
|
||||
// Include default Pyflakes and pycodestyle checks.
|
||||
let mut select = BTreeSet::from([CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W]);
|
||||
|
||||
// Add prefix codes for every plugin.
|
||||
for plugin in plugins {
|
||||
for prefix in plugin.select(flake8) {
|
||||
select.insert(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
select
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::plugin::{infer_plugins, Plugin};
|
||||
|
||||
#[test]
|
||||
fn it_infers_plugins() {
|
||||
let actual = infer_plugins(&HashMap::from([(
|
||||
"inline-quotes".to_string(),
|
||||
Some("single".to_string()),
|
||||
)]));
|
||||
let expected = vec![Plugin::Flake8Quotes];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = infer_plugins(&HashMap::from([(
|
||||
"staticmethod-decorators".to_string(),
|
||||
Some("[]".to_string()),
|
||||
)]));
|
||||
let expected = vec![Plugin::PEP8Naming];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
18
resources/test/fixtures/B003.py
vendored
Normal file
18
resources/test/fixtures/B003.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Should emit:
|
||||
B003 - on line 9
|
||||
"""
|
||||
|
||||
import os
|
||||
from os import environ
|
||||
|
||||
os.environ = {}
|
||||
environ = {} # that's fine, assigning a new meaning to the module-level name
|
||||
|
||||
|
||||
class Object:
|
||||
os = None
|
||||
|
||||
|
||||
o = Object()
|
||||
o.os.environ = {}
|
||||
27
resources/test/fixtures/B015.py
vendored
Normal file
27
resources/test/fixtures/B015.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
assert 1 == 1
|
||||
|
||||
1 == 1
|
||||
|
||||
assert 1 in (1, 2)
|
||||
|
||||
1 in (1, 2)
|
||||
|
||||
|
||||
if 1 == 2:
|
||||
pass
|
||||
|
||||
|
||||
def test():
|
||||
assert 1 in (1, 2)
|
||||
|
||||
1 in (1, 2)
|
||||
|
||||
|
||||
data = [x for x in [1, 2, 3] if x in (1, 2)]
|
||||
|
||||
|
||||
class TestClass:
|
||||
1 == 1
|
||||
|
||||
|
||||
print(1 == 1)
|
||||
11
resources/test/fixtures/B016.py
vendored
Normal file
11
resources/test/fixtures/B016.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Should emit:
|
||||
B016 - on lines 6, 7, and 8
|
||||
"""
|
||||
|
||||
raise False
|
||||
raise 1
|
||||
raise "string"
|
||||
raise Exception(False)
|
||||
raise Exception(1)
|
||||
raise Exception("string")
|
||||
59
resources/test/fixtures/B018.py
vendored
Normal file
59
resources/test/fixtures/B018.py
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
class Foo1:
|
||||
"""abc"""
|
||||
|
||||
|
||||
class Foo2:
|
||||
"""abc"""
|
||||
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
b"foo" # Binary
|
||||
True # NameConstant (True)
|
||||
False # NameConstant (False)
|
||||
None # NameConstant (None)
|
||||
[1, 2] # list
|
||||
{1, 2} # set
|
||||
{"foo": "bar"} # dict
|
||||
|
||||
|
||||
class Foo3:
|
||||
123
|
||||
a = 2
|
||||
"str"
|
||||
1
|
||||
|
||||
|
||||
def foo1():
|
||||
"""my docstring"""
|
||||
|
||||
|
||||
def foo2():
|
||||
"""my docstring"""
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
b"foo" # Binary
|
||||
True # NameConstant (True)
|
||||
False # NameConstant (False)
|
||||
None # NameConstant (None)
|
||||
[1, 2] # list
|
||||
{1, 2} # set
|
||||
{"foo": "bar"} # dict
|
||||
|
||||
|
||||
def foo3():
|
||||
123
|
||||
a = 2
|
||||
"str"
|
||||
3
|
||||
|
||||
|
||||
def foo4():
|
||||
...
|
||||
35
resources/test/fixtures/E501.py
vendored
35
resources/test/fixtures/E501.py
vendored
@@ -14,3 +14,38 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
|
||||
|
||||
_ = "---------------------------------------------------------------------------AAAAAAA"
|
||||
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
|
||||
|
||||
|
||||
def caller(string: str) -> None:
|
||||
assert string is not None
|
||||
|
||||
|
||||
caller(
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
caller(
|
||||
"""
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""", # noqa: E501
|
||||
)
|
||||
|
||||
multiple_strings_per_line = {
|
||||
# OK
|
||||
"Lorem ipsum dolor": "sit amet",
|
||||
# E501 Line too long
|
||||
"Lorem ipsum dolor": "sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
# E501 Line too long
|
||||
"Lorem ipsum dolor": """
|
||||
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""",
|
||||
# OK
|
||||
"Lorem ipsum dolor": "sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", # noqa: E501
|
||||
# OK
|
||||
"Lorem ipsum dolor": """
|
||||
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""", # noqa: E501
|
||||
}
|
||||
|
||||
3
resources/test/fixtures/F632.py
vendored
3
resources/test/fixtures/F632.py
vendored
@@ -3,3 +3,6 @@ if x is "abc":
|
||||
|
||||
if 123 is not y:
|
||||
pass
|
||||
|
||||
if "123" is x < 3:
|
||||
pass
|
||||
|
||||
14
resources/test/fixtures/F821_3.py
vendored
Normal file
14
resources/test/fixtures/F821_3.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Test (edge case): accesses of object named `dict`."""
|
||||
|
||||
# OK
|
||||
dict = {"parameters": {}}
|
||||
dict["parameters"]["userId"] = "userId"
|
||||
dict["parameters"]["itemId"] = "itemId"
|
||||
del dict["parameters"]["itemId"]
|
||||
|
||||
# F821 Undefined name `key`
|
||||
# F821 Undefined name `value`
|
||||
x: dict["key", "value"]
|
||||
|
||||
# OK
|
||||
x: dict[str, str]
|
||||
8
resources/test/fixtures/N803.py
vendored
8
resources/test/fixtures/N803.py
vendored
@@ -1,7 +1,7 @@
|
||||
def func(a, A):
|
||||
return a, A
|
||||
def func(_, a, A):
|
||||
return _, a, A
|
||||
|
||||
|
||||
class Class:
|
||||
def method(self, a, A):
|
||||
return a, A
|
||||
def method(self, _, a, A):
|
||||
return _, a, A
|
||||
|
||||
36
resources/test/fixtures/N804.py
vendored
36
resources/test/fixtures/N804.py
vendored
@@ -1,19 +1,43 @@
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class Class:
|
||||
@classmethod
|
||||
def bad_class_method(this):
|
||||
def bad_method(this):
|
||||
pass
|
||||
|
||||
if False:
|
||||
|
||||
def extra_bad_method(this):
|
||||
pass
|
||||
|
||||
def good_method(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def good_class_method(cls):
|
||||
pass
|
||||
|
||||
def method(self):
|
||||
def class_method(cls):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def static_method(x):
|
||||
return x
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
...
|
||||
|
||||
def __init_subclass__(self, default_name, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
class MetaClass(ABCMeta):
|
||||
def bad_method(self):
|
||||
pass
|
||||
|
||||
def good_method(cls):
|
||||
pass
|
||||
|
||||
|
||||
def func(x):
|
||||
return x
|
||||
|
||||
21
resources/test/fixtures/N805.py
vendored
21
resources/test/fixtures/N805.py
vendored
@@ -1,11 +1,11 @@
|
||||
import random
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class Class:
|
||||
def bad_method(this):
|
||||
pass
|
||||
|
||||
if random.random(0, 2) == 0:
|
||||
if False:
|
||||
|
||||
def extra_bad_method(this):
|
||||
pass
|
||||
@@ -21,6 +21,23 @@ class Class:
|
||||
def static_method(x):
|
||||
return x
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
...
|
||||
|
||||
def __init_subclass__(self, default_name, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
class MetaClass(ABCMeta):
|
||||
def bad_method(self):
|
||||
pass
|
||||
|
||||
def good_method(cls):
|
||||
pass
|
||||
|
||||
|
||||
def func(x):
|
||||
return x
|
||||
|
||||
6
resources/test/fixtures/RUF001.py
vendored
Normal file
6
resources/test/fixtures/RUF001.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
x = "𝐁ad string"
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a comment with an unusual parenthesis: )"""
|
||||
...
|
||||
6
resources/test/fixtures/RUF002.py
vendored
Normal file
6
resources/test/fixtures/RUF002.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
x = "𝐁ad string"
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a comment with an unusual parenthesis: )"""
|
||||
...
|
||||
3
resources/test/fixtures/U009_0.py
vendored
Normal file
3
resources/test/fixtures/U009_0.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# coding=utf8
|
||||
|
||||
print('Hello world')
|
||||
4
resources/test/fixtures/U009_1.py
vendored
Normal file
4
resources/test/fixtures/U009_1.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
print('Hello world')
|
||||
5
resources/test/fixtures/U009_2.py
vendored
Normal file
5
resources/test/fixtures/U009_2.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
# A coding comment is only valid in the first two lines, so this one is not checked.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
print('Hello world')
|
||||
4
resources/test/fixtures/U009_3.py
vendored
Normal file
4
resources/test/fixtures/U009_3.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: something-else -*-
|
||||
|
||||
print('Hello world')
|
||||
35
resources/test/fixtures/W605_1.py
vendored
Normal file
35
resources/test/fixtures/W605_1.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
#: W605:1:10
|
||||
regex = '\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = '''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
regex = r'''
|
||||
\.png$
|
||||
'''
|
||||
regex = r'''
|
||||
\\.png$
|
||||
'''
|
||||
s = '\\'
|
||||
regex = '\w' # noqa
|
||||
regex = '''
|
||||
\w
|
||||
''' # noqa
|
||||
16
ruff_dev/Cargo.toml
Normal file
16
ruff_dev/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.104"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.60" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
codegen = { version = "0.2.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
ruff = { path = ".." }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
@@ -1,22 +1,39 @@
|
||||
//! Generate the CheckCodePrefix enum.
|
||||
//! Generate the `CheckCodePrefix` enum.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::CheckCode;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn main() {
|
||||
const FILE: &str = "src/checks_gen.rs";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
/// Write the generated source code to stdout (rather than to
|
||||
/// `src/checks_gen.rs`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
// Build up a map from prefix to matching CheckCodes.
|
||||
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
|
||||
for check_code in CheckCode::iter() {
|
||||
let as_ref = check_code.as_ref().to_string();
|
||||
for i in 1..=as_ref.len() {
|
||||
let as_ref: String = check_code.as_ref().to_string();
|
||||
let prefix_len = as_ref
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphabetic())
|
||||
.count();
|
||||
for i in prefix_len..=as_ref.len() {
|
||||
let prefix = as_ref[..i].to_string();
|
||||
let entry = prefix_to_codes
|
||||
.entry(prefix)
|
||||
.or_insert_with(|| Default::default());
|
||||
let entry = prefix_to_codes.entry(prefix).or_default();
|
||||
entry.insert(check_code.clone());
|
||||
}
|
||||
}
|
||||
@@ -31,10 +48,12 @@ fn main() {
|
||||
.derive("Debug")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
.derive("PartialOrd")
|
||||
.derive("Ord")
|
||||
.derive("Clone")
|
||||
.derive("Serialize")
|
||||
.derive("Deserialize");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
gen = gen.push_variant(Variant::new(prefix.to_string()));
|
||||
}
|
||||
|
||||
@@ -78,12 +97,13 @@ fn main() {
|
||||
.ret(Type::new("PrefixSpecificity"))
|
||||
.vis("pub")
|
||||
.line("match self {");
|
||||
for (prefix, _) in &prefix_to_codes {
|
||||
let specificity = match prefix.len() {
|
||||
4 => "Explicit",
|
||||
3 => "Tens",
|
||||
2 => "Hundreds",
|
||||
1 => "Category",
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
|
||||
let specificity = match num_numeric {
|
||||
3 => "Explicit",
|
||||
2 => "Tens",
|
||||
1 => "Hundreds",
|
||||
0 => "Category",
|
||||
_ => panic!("Invalid prefix: {}", prefix),
|
||||
};
|
||||
gen = gen.line(format!(
|
||||
@@ -93,12 +113,28 @@ fn main() {
|
||||
}
|
||||
gen.line("}");
|
||||
|
||||
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
|
||||
println!();
|
||||
println!("use serde::{{Serialize, Deserialize}};");
|
||||
println!("use strum_macros::EnumString;");
|
||||
println!();
|
||||
println!("use crate::checks::CheckCode;");
|
||||
println!();
|
||||
println!("{}", scope.to_string());
|
||||
// Construct the output contents.
|
||||
let mut output = String::new();
|
||||
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}};");
|
||||
output.push('\n');
|
||||
output.push_str("use strum_macros::EnumString;");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str("use crate::checks::CheckCode;");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str(&scope.to_string());
|
||||
|
||||
// Write the output to `src/checks_gen.rs` (or stdout).
|
||||
if cli.dry_run {
|
||||
println!("{}", output);
|
||||
} else {
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
|
||||
write!(f, "{}", output)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
93
ruff_dev/src/generate_rules_table.rs
Normal file
93
ruff_dev/src/generate_rules_table.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
//! Generate a Markdown-compatible table of supported lint rules.
|
||||
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use ruff::checks::{CheckCategory, CheckCode};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
|
||||
const END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `README.md`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
// Generate the table string.
|
||||
let mut output = String::new();
|
||||
for check_category in CheckCategory::iter() {
|
||||
output.push_str(&format!("### {}", check_category.title()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
if let Some(url) = check_category.url() {
|
||||
output.push_str(&format!(
|
||||
"For more, see [{}]({}) on PyPI.",
|
||||
check_category.title(),
|
||||
url
|
||||
));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output.push_str("| Code | Name | Message | Fix |");
|
||||
output.push('\n');
|
||||
output.push_str("| ---- | ---- | ------- | --- |");
|
||||
output.push('\n');
|
||||
|
||||
for check_code in CheckCode::iter() {
|
||||
if check_code.category() == check_category {
|
||||
let check_kind = check_code.kind();
|
||||
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
|
||||
output.push_str(&format!(
|
||||
"| {} | {} | {} | {} |",
|
||||
check_kind.code().as_ref(),
|
||||
check_kind.as_ref(),
|
||||
check_kind.summary().replace('|', r"\|"),
|
||||
fix_token
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{}", output);
|
||||
} else {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(BEGIN_PRAGMA)
|
||||
.expect("Unable to find begin pragma.");
|
||||
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(END_PRAGMA)
|
||||
.expect("Unable to find end pragma.");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
write!(f, "{}\n\n", prefix)?;
|
||||
write!(f, "{}", output)?;
|
||||
write!(f, "{}", suffix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
//! Run round-trip source code generation on a given Python file.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::Args;
|
||||
use ruff::code_gen::SourceGenerator;
|
||||
use ruff::fs;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Python file to round-trip.
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
let mut generator = SourceGenerator::new();
|
||||
generator.unparse_suite(&python_ast)?;
|
||||
println!("{}", generator.generate()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
5
ruff_dev/src/lib.rs
Normal file
5
ruff_dev/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_rules_table;
|
||||
pub mod generate_source_code;
|
||||
pub mod print_ast;
|
||||
pub mod print_tokens;
|
||||
39
ruff_dev/src/main.rs
Normal file
39
ruff_dev/src/main.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_tokens,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Generate the `CheckCodePrefix` enum.
|
||||
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
GenerateRulesTable(generate_rules_table::Cli),
|
||||
/// Run round-trip source code generation on a given Python file.
|
||||
GenerateSourceCode(generate_source_code::Cli),
|
||||
/// Print the AST for a given Python file.
|
||||
PrintAST(print_ast::Cli),
|
||||
/// Print the token stream for a given Python file.
|
||||
PrintTokens(print_tokens::Cli),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
|
||||
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
|
||||
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
|
||||
Commands::PrintAST(args) => print_ast::main(args)?,
|
||||
Commands::PrintTokens(args) => print_tokens::main(args)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
//! Print the AST for a given Python file.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use ruff::fs;
|
||||
use clap::Args;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Python file for which to generate the AST.
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
|
||||
|
||||
println!("{:#?}", python_ast);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,25 +1,23 @@
|
||||
//! Print the token stream for a given Python file.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use ruff::fs;
|
||||
use clap::Args;
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Python file for which to generate the AST.
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let contents = fs::read_file(&cli.file)?;
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let contents = fs::read_to_string(&cli.file)?;
|
||||
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
|
||||
println!("{:#?}", tok);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
1.64.0
|
||||
1.65.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
fn id() -> usize {
|
||||
@@ -30,24 +31,33 @@ pub struct FunctionScope {
|
||||
pub uses_locals: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ScopeKind {
|
||||
Class,
|
||||
Function(FunctionScope),
|
||||
Generator,
|
||||
Module,
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ClassScope<'a> {
|
||||
pub name: &'a str,
|
||||
pub bases: &'a [Expr],
|
||||
pub keywords: &'a [Keyword],
|
||||
pub decorator_list: &'a [Expr],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Scope {
|
||||
pub enum ScopeKind<'a> {
|
||||
Class(ClassScope<'a>),
|
||||
Function(FunctionScope),
|
||||
Generator,
|
||||
Module,
|
||||
Arg,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Scope<'a> {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind,
|
||||
pub kind: ScopeKind<'a>,
|
||||
pub import_starred: bool,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(kind: ScopeKind) -> Self {
|
||||
impl<'a> Scope<'a> {
|
||||
pub fn new(kind: ScopeKind<'a>) -> Self {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ropey::RopeBuilder;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{Fix, Patch};
|
||||
use crate::checks::Check;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
|
||||
// higher levels in the execution flow.
|
||||
@@ -36,24 +40,28 @@ impl From<bool> for Mode {
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(checks: &mut [Check], contents: &str) -> Option<String> {
|
||||
pub fn fix_file<'a>(
|
||||
checks: &'a mut [Check],
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
if checks.iter().all(|check| check.fix.is_none()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(apply_fixes(
|
||||
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
|
||||
contents,
|
||||
locator,
|
||||
))
|
||||
}
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
|
||||
let mut output: String = Default::default();
|
||||
let mut last_pos: Location = Default::default();
|
||||
let mut applied: BTreeSet<&Patch> = Default::default();
|
||||
fn apply_fixes<'a>(
|
||||
fixes: impl Iterator<Item = &'a mut Fix>,
|
||||
locator: &'a SourceCodeLocator<'a>,
|
||||
) -> Cow<'a, str> {
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::new();
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
@@ -69,44 +77,27 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
|
||||
continue;
|
||||
}
|
||||
|
||||
if fix.patch.location.row() > last_pos.row() {
|
||||
if last_pos.row() > 0 || last_pos.column() > 0 {
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column()..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..fix.patch.location.row() - 1] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
output.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column()]);
|
||||
output.push_str(&fix.patch.content);
|
||||
} else {
|
||||
output.push_str(
|
||||
&lines[last_pos.row() - 1][last_pos.column()..fix.patch.location.column()],
|
||||
);
|
||||
output.push_str(&fix.patch.content);
|
||||
}
|
||||
last_pos = fix.patch.end_location;
|
||||
// Add all contents from `last_pos` to `fix.patch.location`.
|
||||
let slice = locator.slice_source_code_range(&Range {
|
||||
location: last_pos,
|
||||
end_location: fix.patch.location,
|
||||
});
|
||||
output.append(&slice);
|
||||
|
||||
// Add the patch itself.
|
||||
output.append(&fix.patch.content);
|
||||
|
||||
// Track that the fix was applied.
|
||||
last_pos = fix.patch.end_location;
|
||||
applied.insert(&fix.patch);
|
||||
fix.applied = true;
|
||||
}
|
||||
|
||||
if last_pos.row() > 0
|
||||
&& (last_pos.row() - 1) < lines.len()
|
||||
&& (last_pos.row() > 0 || last_pos.column() > 0)
|
||||
{
|
||||
output.push_str(&lines[last_pos.row() - 1][last_pos.column()..]);
|
||||
output.push('\n');
|
||||
}
|
||||
if last_pos.row() < lines.len() {
|
||||
for line in &lines[last_pos.row()..] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
// Add the remaining content.
|
||||
let slice = locator.slice_source_code_at(&last_pos);
|
||||
output.append(&slice);
|
||||
|
||||
output
|
||||
Cow::from(output.finish())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -116,11 +107,13 @@ mod tests {
|
||||
|
||||
use crate::autofix::fixer::apply_fixes;
|
||||
use crate::autofix::{Fix, Patch};
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
#[test]
|
||||
fn empty_file() -> Result<()> {
|
||||
let mut fixes = vec![];
|
||||
let actual = apply_fixes(fixes.iter_mut(), "");
|
||||
let locator = SourceCodeLocator::new("");
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
let expected = "";
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
@@ -138,12 +131,12 @@ mod tests {
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A(Bar):
|
||||
...
|
||||
@@ -164,12 +157,12 @@ mod tests {
|
||||
},
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
@@ -200,12 +193,12 @@ mod tests {
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object, object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
@@ -236,12 +229,12 @@ mod tests {
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
let actual = apply_fixes(
|
||||
fixes.iter_mut(),
|
||||
let locator = SourceCodeLocator::new(
|
||||
"class A(object):
|
||||
...
|
||||
",
|
||||
);
|
||||
let actual = apply_fixes(fixes.iter_mut(), &locator);
|
||||
|
||||
let expected = "class A:
|
||||
...
|
||||
|
||||
143
src/check_ast.rs
143
src/check_ast.rs
@@ -15,8 +15,8 @@ use crate::ast::helpers::{extract_handler_names, match_name_or_attr_from_module}
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingContext, BindingKind, CheckLocator, FunctionScope, ImportKind, Range, Scope,
|
||||
ScopeKind,
|
||||
Binding, BindingContext, BindingKind, CheckLocator, ClassScope, FunctionScope, ImportKind,
|
||||
Range, Scope, ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{helpers, operations, visitor};
|
||||
@@ -66,7 +66,7 @@ pub struct Checker<'a> {
|
||||
// at various points in time.
|
||||
pub(crate) parents: Vec<&'a Stmt>,
|
||||
pub(crate) parent_stack: Vec<usize>,
|
||||
scopes: Vec<Scope>,
|
||||
scopes: Vec<Scope<'a>>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
deferred_string_annotations: Vec<(Range, &'a str)>,
|
||||
@@ -79,6 +79,7 @@ pub struct Checker<'a> {
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
in_subscript: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
@@ -118,6 +119,7 @@ impl<'a> Checker<'a> {
|
||||
in_f_string: Default::default(),
|
||||
in_annotation: Default::default(),
|
||||
in_literal: Default::default(),
|
||||
in_subscript: Default::default(),
|
||||
seen_import_boundary: Default::default(),
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: Default::default(),
|
||||
@@ -238,6 +240,7 @@ where
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
@@ -245,6 +248,7 @@ where
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E743) {
|
||||
@@ -270,6 +274,7 @@ where
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::invalid_first_argument_name_for_class_method(
|
||||
self.current_scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
@@ -282,6 +287,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::N805) {
|
||||
if let Some(check) = pep8_naming::checks::invalid_first_argument_name_for_method(
|
||||
self.current_scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
&self.settings.pep8_naming,
|
||||
@@ -292,12 +298,16 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N807) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::dunder_function_name(stmt, self.current_scope(), name)
|
||||
pep8_naming::checks::dunder_function_name(self.current_scope(), stmt, name)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B018) {
|
||||
flake8_bugbear::plugins::useless_expression(self, body);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
|
||||
|
||||
// Visit the decorators and arguments, but avoid the body, which will be
|
||||
@@ -352,7 +362,7 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::F706) {
|
||||
if let Some(scope_index) = self.scope_stack.last().cloned() {
|
||||
match self.scopes[scope_index].kind {
|
||||
ScopeKind::Class | ScopeKind::Module => {
|
||||
ScopeKind::Class(_) | ScopeKind::Module => {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -368,7 +378,7 @@ where
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
..
|
||||
body,
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::U004) {
|
||||
pyupgrade::plugins::useless_object_inheritance(
|
||||
@@ -399,6 +409,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B018) {
|
||||
flake8_bugbear::plugins::useless_expression(self, body);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
@@ -414,7 +428,12 @@ where
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr)
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Class))
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
})))
|
||||
}
|
||||
StmtKind::Import { names } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
@@ -717,9 +736,12 @@ where
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F901) {
|
||||
if let Some(expr) = exc {
|
||||
if let Some(check) = pyflakes::checks::raise_not_implemented(expr) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
pyflakes::plugins::raise_not_implemented(self, expr);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B016) {
|
||||
if let Some(exc) = exc {
|
||||
flake8_bugbear::plugins::cannot_raise_literal(self, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -776,6 +798,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::U001) {
|
||||
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B003) {
|
||||
flake8_bugbear::plugins::assignment_to_os_environ(self, targets);
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -790,6 +815,11 @@ where
|
||||
}
|
||||
}
|
||||
StmtKind::Delete { .. } => {}
|
||||
StmtKind::Expr { value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B015) {
|
||||
flake8_bugbear::plugins::useless_comparison(self, value)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1076,9 +1106,12 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
|
||||
expr,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
@@ -1229,7 +1262,7 @@ where
|
||||
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } | ExprKind::Await { .. } => {
|
||||
let scope = self.current_scope();
|
||||
if self.settings.enabled.contains(&CheckCode::F704) {
|
||||
if matches!(scope.kind, ScopeKind::Class | ScopeKind::Module) {
|
||||
if matches!(scope.kind, ScopeKind::Class(_) | ScopeKind::Module) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::YieldOutsideFunction,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
@@ -1297,12 +1330,13 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F632) {
|
||||
self.checks.extend(pyflakes::checks::is_literal(
|
||||
pyflakes::plugins::invalid_literal_comparison(
|
||||
self,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E721) {
|
||||
@@ -1363,6 +1397,8 @@ where
|
||||
expr,
|
||||
elt,
|
||||
generators,
|
||||
self.locator,
|
||||
self.patch(),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
@@ -1485,36 +1521,56 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
Some(subscript) => match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
// Ex) Annotated[int, "Hello, world!"]
|
||||
SubscriptKind::PEP593AnnotatedSubscript => {
|
||||
// First argument is a type (including forward references); the rest are
|
||||
// arbitrary Python objects.
|
||||
self.visit_expr(value);
|
||||
if let ExprKind::Tuple { elts, ctx } = &slice.node {
|
||||
if let Some(expr) = elts.first() {
|
||||
self.visit_expr(expr);
|
||||
self.in_annotation = false;
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.in_annotation = true;
|
||||
// Only allow annotations in `ExprContext::Load`. If we have, e.g.,
|
||||
// `obj["foo"]["bar"]`, we need to avoid treating the `obj["foo"]`
|
||||
// portion as an annotation, despite having `ExprContext::Load`. Thus, we track
|
||||
// the `ExprContext` at the top-level.
|
||||
let prev_in_subscript = self.in_subscript;
|
||||
if self.in_subscript {
|
||||
visitor::walk_expr(self, expr);
|
||||
} else if matches!(ctx, ExprContext::Store | ExprContext::Del) {
|
||||
self.in_subscript = true;
|
||||
visitor::walk_expr(self, expr);
|
||||
} else {
|
||||
self.in_subscript = true;
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
Some(subscript) => {
|
||||
match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
} else {
|
||||
error!("Found non-ExprKind::Tuple argument to PEP 593 Annotation.")
|
||||
// Ex) Annotated[int, "Hello, world!"]
|
||||
SubscriptKind::PEP593AnnotatedSubscript => {
|
||||
// First argument is a type (including forward references); the
|
||||
// rest are arbitrary Python
|
||||
// objects.
|
||||
self.visit_expr(value);
|
||||
if let ExprKind::Tuple { elts, ctx } = &slice.node {
|
||||
if let Some(expr) = elts.first() {
|
||||
self.visit_expr(expr);
|
||||
self.in_annotation = false;
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.in_annotation = true;
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"Found non-ExprKind::Tuple argument to PEP 593 \
|
||||
Annotation."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => visitor::walk_expr(self, expr),
|
||||
None => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
self.in_subscript = prev_in_subscript;
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
@@ -1621,6 +1677,9 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B006) {
|
||||
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B008) {
|
||||
flake8_bugbear::plugins::function_call_argument_default(self, arguments)
|
||||
}
|
||||
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them
|
||||
// upstream.
|
||||
@@ -1664,7 +1723,7 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N803) {
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::invalid_argument_name(Range::from_located(arg), &arg.node.arg)
|
||||
pep8_naming::checks::invalid_argument_name(&arg.node.arg, Range::from_located(arg))
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
@@ -1738,7 +1797,7 @@ impl<'a> Checker<'a> {
|
||||
.expect("Attempted to pop without scope.");
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, scope: Scope) {
|
||||
fn push_scope(&mut self, scope: Scope<'a>) {
|
||||
self.scope_stack.push(self.scopes.len());
|
||||
self.scopes.push(scope);
|
||||
}
|
||||
@@ -1843,7 +1902,7 @@ impl<'a> Checker<'a> {
|
||||
let mut import_starred = false;
|
||||
for scope_index in self.scope_stack.iter().rev() {
|
||||
let scope = &mut self.scopes[*scope_index];
|
||||
if matches!(scope.kind, ScopeKind::Class) {
|
||||
if matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
if id == "__class__" {
|
||||
return;
|
||||
} else if !first_iter && !in_generator {
|
||||
@@ -2380,7 +2439,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
|
||||
if is_attribute && matches!(self.current_scope().kind, ScopeKind::Class) {
|
||||
if is_attribute && matches!(self.current_scope().kind, ScopeKind::Class(_)) {
|
||||
if self.settings.enabled.contains(&CheckCode::A003) {
|
||||
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
|
||||
name,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -11,6 +13,10 @@ use crate::noqa;
|
||||
use crate::noqa::Directive;
|
||||
use crate::settings::Settings;
|
||||
|
||||
// Regex from PEP263
|
||||
static CODING_COMMENT_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").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 {
|
||||
@@ -52,6 +58,27 @@ pub fn check_lines(
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
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),
|
||||
},
|
||||
);
|
||||
if autofix.patch() {
|
||||
check.amend(Fix::deletion(
|
||||
Location::new(lineno + 1, 0),
|
||||
Location::new(lineno + 1, line_length + 1),
|
||||
));
|
||||
}
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_noqa {
|
||||
noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
|
||||
@@ -2,37 +2,54 @@
|
||||
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::flake8_quotes::docstring_detection::StateMachine;
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{flake8_quotes, pycodestyle, Settings};
|
||||
use crate::{flake8_quotes, pycodestyle, rules, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
checks: &mut Vec<Check>,
|
||||
locator: &SourceCodeLocator,
|
||||
tokens: &[LexResult],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|
||||
|| settings.enabled.contains(&CheckCode::RUF002);
|
||||
let enforce_quotes = settings.enabled.contains(&CheckCode::Q000)
|
||||
| settings.enabled.contains(&CheckCode::Q001)
|
||||
| settings.enabled.contains(&CheckCode::Q002)
|
||||
| settings.enabled.contains(&CheckCode::Q003);
|
||||
|| settings.enabled.contains(&CheckCode::Q001)
|
||||
|| settings.enabled.contains(&CheckCode::Q002)
|
||||
|| settings.enabled.contains(&CheckCode::Q003);
|
||||
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
|
||||
|
||||
let mut state_machine: StateMachine = Default::default();
|
||||
for (start, tok, end) in tokens.iter().flatten() {
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
|
||||
state_machine.consume(tok)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// RUF001, RUF002
|
||||
if enforce_ambiguous_unicode_character {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
checks.extend(pycodestyle::checks::invalid_escape_sequence(
|
||||
locator, start, end,
|
||||
));
|
||||
for check in rules::checks::ambiguous_unicode_character(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
is_docstring,
|
||||
autofix.patch(),
|
||||
) {
|
||||
if settings.enabled.contains(check.kind.code()) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-quotes
|
||||
if enforce_quotes {
|
||||
let is_docstring = state_machine.consume(tok);
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
if let Some(check) = flake8_quotes::checks::quotes(
|
||||
locator,
|
||||
@@ -47,5 +64,14 @@ pub fn check_tokens(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W605
|
||||
if enforce_invalid_escape_sequence {
|
||||
if matches!(tok, Tok::String { .. }) {
|
||||
checks.extend(pycodestyle::checks::invalid_escape_sequence(
|
||||
locator, start, end,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
160
src/checks.rs
160
src/checks.rs
@@ -78,12 +78,17 @@ pub enum CheckCode {
|
||||
A003,
|
||||
// flake8-bugbear
|
||||
B002,
|
||||
B003,
|
||||
B006,
|
||||
B007,
|
||||
B008,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
B016,
|
||||
B017,
|
||||
B018,
|
||||
B025,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
@@ -119,6 +124,7 @@ pub enum CheckCode {
|
||||
U006,
|
||||
U007,
|
||||
U008,
|
||||
U009,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -180,6 +186,9 @@ pub enum CheckCode {
|
||||
N816,
|
||||
N817,
|
||||
N818,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
@@ -187,8 +196,7 @@ pub enum CheckCode {
|
||||
#[derive(EnumIter, Debug, PartialEq, Eq)]
|
||||
pub enum CheckCategory {
|
||||
Pyflakes,
|
||||
PycodestyleError,
|
||||
PycodestyleWarning,
|
||||
Pycodestyle,
|
||||
Pydocstyle,
|
||||
Pyupgrade,
|
||||
PEP8Naming,
|
||||
@@ -197,14 +205,14 @@ pub enum CheckCategory {
|
||||
Flake8Builtins,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Ruff,
|
||||
Meta,
|
||||
}
|
||||
|
||||
impl CheckCategory {
|
||||
pub fn title(&self) -> &'static str {
|
||||
match self {
|
||||
CheckCategory::PycodestyleError => "pycodestyle (error)",
|
||||
CheckCategory::PycodestyleWarning => "pycodestyle (warning)",
|
||||
CheckCategory::Pycodestyle => "pycodestyle",
|
||||
CheckCategory::Pyflakes => "Pyflakes",
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
@@ -214,9 +222,33 @@ impl CheckCategory {
|
||||
CheckCategory::Pyupgrade => "pyupgrade",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
CheckCategory::Ruff => "Ruff-specific rules",
|
||||
CheckCategory::Meta => "Meta rules",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
|
||||
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
|
||||
CheckCategory::Flake8Builtins => {
|
||||
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
|
||||
}
|
||||
CheckCategory::Flake8Bugbear => {
|
||||
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
|
||||
}
|
||||
CheckCategory::Flake8Comprehensions => {
|
||||
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
|
||||
}
|
||||
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
|
||||
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
|
||||
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
|
||||
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
|
||||
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
|
||||
CheckCategory::Ruff => None,
|
||||
CheckCategory::Meta => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
@@ -288,12 +320,17 @@ pub enum CheckKind {
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-bugbear
|
||||
UnaryPrefixIncrement,
|
||||
AssignmentToOsEnviron,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault,
|
||||
DoNotAssertFalse,
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
UselessComparison,
|
||||
CannotRaiseLiteral,
|
||||
NoAssertRaisesException,
|
||||
UselessExpression,
|
||||
DuplicateTryBlockException(String),
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
@@ -329,6 +366,7 @@ pub enum CheckKind {
|
||||
UsePEP585Annotation(String),
|
||||
UsePEP604Annotation,
|
||||
SuperCallWithParameters,
|
||||
PEP3120UnnecessaryCodingComment,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -390,6 +428,9 @@ pub enum CheckKind {
|
||||
MixedCaseVariableInGlobalScope(String),
|
||||
CamelcaseImportedAsAcronym(String, String),
|
||||
ErrorSuffixOnExceptionName(String),
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
// Meta
|
||||
UnusedNOQA(Option<Vec<String>>),
|
||||
}
|
||||
@@ -399,12 +440,16 @@ impl CheckCode {
|
||||
/// physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
|
||||
CheckCode::W605
|
||||
| CheckCode::Q000
|
||||
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
|
||||
&LintSource::Lines
|
||||
}
|
||||
CheckCode::Q000
|
||||
| CheckCode::Q001
|
||||
| CheckCode::Q002
|
||||
| CheckCode::Q003 => &LintSource::Tokens,
|
||||
| CheckCode::Q003
|
||||
| CheckCode::W605
|
||||
| CheckCode::RUF001
|
||||
| CheckCode::RUF002 => &LintSource::Tokens,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
@@ -468,14 +513,19 @@ impl CheckCode {
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-bugbear
|
||||
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
|
||||
CheckCode::B003 => CheckKind::AssignmentToOsEnviron,
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
}
|
||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||
CheckCode::B015 => CheckKind::UselessComparison,
|
||||
CheckCode::B016 => CheckKind::CannotRaiseLiteral,
|
||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||
CheckCode::B018 => CheckKind::UselessExpression,
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
@@ -527,6 +577,7 @@ impl CheckCode {
|
||||
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
|
||||
CheckCode::U007 => CheckKind::UsePEP604Annotation,
|
||||
CheckCode::U008 => CheckKind::SuperCallWithParameters,
|
||||
CheckCode::U009 => CheckKind::PEP3120UnnecessaryCodingComment,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -604,6 +655,9 @@ impl CheckCode {
|
||||
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
|
||||
}
|
||||
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
@@ -611,22 +665,22 @@ impl CheckCode {
|
||||
|
||||
pub fn category(&self) -> CheckCategory {
|
||||
match self {
|
||||
CheckCode::E402 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E501 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E711 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E712 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E713 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E714 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E721 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E722 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E731 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E741 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E742 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E743 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E902 => CheckCategory::PycodestyleError,
|
||||
CheckCode::E999 => CheckCategory::PycodestyleError,
|
||||
CheckCode::W292 => CheckCategory::PycodestyleWarning,
|
||||
CheckCode::W605 => CheckCategory::PycodestyleWarning,
|
||||
CheckCode::E402 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E501 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E711 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E712 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E713 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E714 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E721 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E722 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E731 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E741 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E742 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E743 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E902 => CheckCategory::Pycodestyle,
|
||||
CheckCode::E999 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::F401 => CheckCategory::Pyflakes,
|
||||
CheckCode::F402 => CheckCategory::Pyflakes,
|
||||
CheckCode::F403 => CheckCategory::Pyflakes,
|
||||
@@ -659,12 +713,17 @@ impl CheckCode {
|
||||
CheckCode::A002 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
||||
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B003 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B006 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B015 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B016 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B018 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -696,6 +755,7 @@ impl CheckCode {
|
||||
CheckCode::U006 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U007 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U008 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U009 => CheckCategory::Pyupgrade,
|
||||
CheckCode::D100 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D101 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D102 => CheckCategory::Pydocstyle,
|
||||
@@ -755,6 +815,8 @@ impl CheckCode {
|
||||
CheckCode::N816 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::M001 => CheckCategory::Meta,
|
||||
}
|
||||
}
|
||||
@@ -816,12 +878,17 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-bugbear
|
||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
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::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
@@ -857,6 +924,7 @@ impl CheckKind {
|
||||
CheckKind::UsePEP604Annotation => &CheckCode::U007,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::U008,
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::U009,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
@@ -918,6 +986,9 @@ impl CheckKind {
|
||||
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
|
||||
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
|
||||
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
}
|
||||
@@ -1078,6 +1149,9 @@ impl CheckKind {
|
||||
increment. Writing `++n` is equivalent to \
|
||||
`+(+(n))`, which equals `n`. You meant `n += 1`."
|
||||
.to_string(),
|
||||
CheckKind::AssignmentToOsEnviron => {
|
||||
"Assigning to `os.environ` doesn't clear the environment.".to_string()
|
||||
}
|
||||
CheckKind::MutableArgumentDefault => {
|
||||
"Do not use mutable data structures for argument defaults.".to_string()
|
||||
}
|
||||
@@ -1085,6 +1159,9 @@ impl CheckKind {
|
||||
"Loop control variable `{name}` not used within the loop body. If this is \
|
||||
intended, start the name with an underscore."
|
||||
),
|
||||
CheckKind::FunctionCallArgumentDefault => {
|
||||
"Do not perform function calls in argument defaults.".to_string()
|
||||
}
|
||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||
calls), raise `AssertionError()`"
|
||||
.to_string(),
|
||||
@@ -1094,6 +1171,13 @@ impl CheckKind {
|
||||
`except ({name},):`."
|
||||
)
|
||||
}
|
||||
CheckKind::UselessComparison => "Pointless comparison. This comparison does nothing \
|
||||
but waste CPU instructions. Either prepend `assert` \
|
||||
or remove it."
|
||||
.to_string(),
|
||||
CheckKind::CannotRaiseLiteral => "Cannot raise a literal. Did you intend to return it \
|
||||
or raise an Exception?"
|
||||
.to_string(),
|
||||
CheckKind::DuplicateHandlerException(names) => {
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
@@ -1110,6 +1194,9 @@ impl CheckKind {
|
||||
`assertRaisesRegex`, or use the context manager form of `assertRaises`."
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::UselessExpression => {
|
||||
"Found useless expression. Either assign it to a variable or remove it.".to_string()
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
@@ -1398,6 +1485,22 @@ impl CheckKind {
|
||||
CheckKind::ErrorSuffixOnExceptionName(name) => {
|
||||
format!("Exception name `{name}` should be named with an Error suffix")
|
||||
}
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => {
|
||||
"utf-8 encoding declaration is unnecessary".to_string()
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
"String contains ambiguous unicode character '{confusable}' (did you mean \
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(confusable, representant) => {
|
||||
format!(
|
||||
"Docstring contains ambiguous unicode character '{confusable}' (did you mean \
|
||||
'{representant}'?)"
|
||||
)
|
||||
}
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
@@ -1439,7 +1542,9 @@ impl CheckKind {
|
||||
pub fn fixable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::BlankLineAfterLastSection(_)
|
||||
CheckKind::AmbiguousUnicodeCharacterString(_, _)
|
||||
| CheckKind::AmbiguousUnicodeCharacterDocstring(_, _)
|
||||
| CheckKind::BlankLineAfterLastSection(_)
|
||||
| CheckKind::BlankLineAfterSection(_)
|
||||
| CheckKind::BlankLineAfterSummary
|
||||
| CheckKind::BlankLineBeforeSection(_)
|
||||
@@ -1461,6 +1566,7 @@ impl CheckKind {
|
||||
| CheckKind::OneBlankLineBeforeClass(_)
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::RaiseNotImplemented
|
||||
| CheckKind::SectionNameEndsInColon(_)
|
||||
| CheckKind::SectionNotOverIndented(_)
|
||||
| CheckKind::SectionUnderlineAfterName(_)
|
||||
@@ -1470,11 +1576,13 @@ impl CheckKind {
|
||||
| CheckKind::TypeOfPrimitive(_)
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnnecessaryCollectionCall(_)
|
||||
| CheckKind::UnnecessaryComprehension(_)
|
||||
| CheckKind::UnnecessaryGeneratorDict
|
||||
| CheckKind::UnnecessaryGeneratorList
|
||||
| CheckKind::UnnecessaryGeneratorSet
|
||||
| CheckKind::UnnecessaryListCall
|
||||
| CheckKind::UnnecessaryListComprehensionSet
|
||||
| CheckKind::UnnecessaryLiteralDict(_)
|
||||
| CheckKind::UnnecessaryLiteralSet(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinListCall(_)
|
||||
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
|
||||
@@ -1485,6 +1593,8 @@ impl CheckKind {
|
||||
| CheckKind::UsePEP604Annotation
|
||||
| CheckKind::UselessMetaclassType
|
||||
| CheckKind::UselessObjectInheritance(_)
|
||||
| CheckKind::PEP3120UnnecessaryCodingComment
|
||||
| CheckKind::IsLiteral
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! File automatically generated by examples/generate_check_code_prefix.rs.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumString};
|
||||
use strum_macros::EnumString;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[derive(AsRefStr, EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
#[derive(EnumString, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
|
||||
pub enum CheckCodePrefix {
|
||||
A,
|
||||
A0,
|
||||
@@ -17,13 +17,18 @@ pub enum CheckCodePrefix {
|
||||
B0,
|
||||
B00,
|
||||
B002,
|
||||
B003,
|
||||
B006,
|
||||
B007,
|
||||
B008,
|
||||
B01,
|
||||
B011,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
B016,
|
||||
B017,
|
||||
B018,
|
||||
B02,
|
||||
B025,
|
||||
C,
|
||||
@@ -204,6 +209,11 @@ pub enum CheckCodePrefix {
|
||||
Q001,
|
||||
Q002,
|
||||
Q003,
|
||||
RUF,
|
||||
RUF0,
|
||||
RUF00,
|
||||
RUF001,
|
||||
RUF002,
|
||||
T,
|
||||
T2,
|
||||
T20,
|
||||
@@ -220,6 +230,7 @@ pub enum CheckCodePrefix {
|
||||
U006,
|
||||
U007,
|
||||
U008,
|
||||
U009,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -248,40 +259,62 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::A003 => vec![CheckCode::A003],
|
||||
CheckCodePrefix::B => vec![
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B025,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B025,
|
||||
],
|
||||
CheckCodePrefix::B00 => vec![CheckCode::B002, CheckCode::B006, CheckCode::B007],
|
||||
CheckCodePrefix::B00 => vec![
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
CheckCode::B006,
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
],
|
||||
CheckCodePrefix::B002 => vec![CheckCode::B002],
|
||||
CheckCodePrefix::B003 => vec![CheckCode::B003],
|
||||
CheckCodePrefix::B006 => vec![CheckCode::B006],
|
||||
CheckCodePrefix::B007 => vec![CheckCode::B007],
|
||||
CheckCodePrefix::B01 => {
|
||||
vec![
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B017,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::B008 => vec![CheckCode::B008],
|
||||
CheckCodePrefix::B01 => vec![
|
||||
CheckCode::B011,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
],
|
||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||
CheckCodePrefix::B015 => vec![CheckCode::B015],
|
||||
CheckCodePrefix::B016 => vec![CheckCode::B016],
|
||||
CheckCodePrefix::B017 => vec![CheckCode::B017],
|
||||
CheckCodePrefix::B018 => vec![CheckCode::B018],
|
||||
CheckCodePrefix::B02 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::C => vec![
|
||||
@@ -584,14 +617,12 @@ impl CheckCodePrefix {
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
],
|
||||
CheckCodePrefix::E71 => {
|
||||
vec![
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::E71 => vec![
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
],
|
||||
CheckCodePrefix::E711 => vec![CheckCode::E711],
|
||||
CheckCodePrefix::E712 => vec![CheckCode::E712],
|
||||
CheckCodePrefix::E713 => vec![CheckCode::E713],
|
||||
@@ -684,14 +715,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F62 => vec![CheckCode::F621, CheckCode::F622],
|
||||
CheckCodePrefix::F621 => vec![CheckCode::F621],
|
||||
CheckCodePrefix::F622 => vec![CheckCode::F622],
|
||||
CheckCodePrefix::F63 => {
|
||||
vec![
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::F63 => vec![
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
],
|
||||
CheckCodePrefix::F631 => vec![CheckCode::F631],
|
||||
CheckCodePrefix::F632 => vec![CheckCode::F632],
|
||||
CheckCodePrefix::F633 => vec![CheckCode::F633],
|
||||
@@ -808,34 +837,33 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::N816 => vec![CheckCode::N816],
|
||||
CheckCodePrefix::N817 => vec![CheckCode::N817],
|
||||
CheckCodePrefix::N818 => vec![CheckCode::N818],
|
||||
CheckCodePrefix::Q => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q0 => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q00 => {
|
||||
vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::Q => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q0 => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q00 => vec![
|
||||
CheckCode::Q000,
|
||||
CheckCode::Q001,
|
||||
CheckCode::Q002,
|
||||
CheckCode::Q003,
|
||||
],
|
||||
CheckCodePrefix::Q000 => vec![CheckCode::Q000],
|
||||
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
|
||||
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
|
||||
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
|
||||
CheckCodePrefix::RUF => vec![CheckCode::RUF001, CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
|
||||
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
|
||||
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
|
||||
@@ -850,6 +878,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U006,
|
||||
CheckCode::U007,
|
||||
CheckCode::U008,
|
||||
CheckCode::U009,
|
||||
],
|
||||
CheckCodePrefix::U0 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -860,6 +889,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U006,
|
||||
CheckCode::U007,
|
||||
CheckCode::U008,
|
||||
CheckCode::U009,
|
||||
],
|
||||
CheckCodePrefix::U00 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -870,6 +900,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U006,
|
||||
CheckCode::U007,
|
||||
CheckCode::U008,
|
||||
CheckCode::U009,
|
||||
],
|
||||
CheckCodePrefix::U001 => vec![CheckCode::U001],
|
||||
CheckCodePrefix::U002 => vec![CheckCode::U002],
|
||||
@@ -879,6 +910,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U006 => vec![CheckCode::U006],
|
||||
CheckCodePrefix::U007 => vec![CheckCode::U007],
|
||||
CheckCodePrefix::U008 => vec![CheckCode::U008],
|
||||
CheckCodePrefix::U009 => vec![CheckCode::U009],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -903,13 +935,18 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::B00 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B003 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B006 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B007 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B015 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B016 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B017 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B018 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B02 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C => PrefixSpecificity::Category,
|
||||
@@ -1090,6 +1127,11 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::Q001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::Q002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::Q003 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::RUF0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::RUF00 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::T => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
|
||||
@@ -1106,6 +1148,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U006 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U007 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U008 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U009 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::W => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::W2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::W29 => PrefixSpecificity::Tens,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::Module;
|
||||
use rustpython_ast::Located;
|
||||
use libcst_native::{Expr, Module, SmallStatement, Statement};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
pub fn match_tree<'a, T>(
|
||||
locator: &'a SourceCodeLocator,
|
||||
located: &'a Located<T>,
|
||||
) -> Result<Module<'a>> {
|
||||
match libcst_native::parse_module(
|
||||
locator.slice_source_code_range(&Range::from_located(located)),
|
||||
None,
|
||||
) {
|
||||
pub fn match_module(module_text: &str) -> Result<Module> {
|
||||
match libcst_native::parse_module(module_text, None) {
|
||||
Ok(module) => Ok(module),
|
||||
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
|
||||
Err(_) => Err(anyhow::anyhow!("Failed to extract CST from source.")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: SmallStatement::Expr"))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Statement::Simple"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,15 @@ pub fn leading_space(line: &str) -> String {
|
||||
}
|
||||
|
||||
/// Extract the leading indentation from a docstring.
|
||||
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> &'a str {
|
||||
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> String {
|
||||
let range = Range::from_located(docstring);
|
||||
checker.locator.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range {
|
||||
location: Location::new(range.location.row(), 0),
|
||||
end_location: Location::new(range.location.row(), range.location.column()),
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Replace any non-whitespace characters from an indentation string.
|
||||
|
||||
24
src/flake8_bugbear/plugins/assignment_to_os_environ.rs
Normal file
24
src/flake8_bugbear/plugins/assignment_to_os_environ.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B003
|
||||
pub fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr]) {
|
||||
if targets.len() == 1 {
|
||||
let target = &targets[0];
|
||||
if let ExprKind::Attribute { value, attr, .. } = &target.node {
|
||||
if attr == "environ" {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "os" {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::AssignmentToOsEnviron,
|
||||
checker.locate_check(Range::from_located(target)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/flake8_bugbear/plugins/cannot_raise_literal.rs
Normal file
15
src/flake8_bugbear/plugins/cannot_raise_literal.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B016
|
||||
pub fn cannot_raise_literal(checker: &mut Checker, expr: &Expr) {
|
||||
if let ExprKind::Constant { .. } = &expr.node {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CannotRaiseLiteral,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
}
|
||||
96
src/flake8_bugbear/plugins/function_call_argument_default.rs
Normal file
96
src/flake8_bugbear/plugins/function_call_argument_default.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use rustpython_ast::{Arguments, Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::compose_call_path;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bugbear::plugins::mutable_argument_default::is_mutable_func;
|
||||
|
||||
const IMMUTABLE_FUNCS: [&str; 11] = [
|
||||
"tuple",
|
||||
"frozenset",
|
||||
"operator.attrgetter",
|
||||
"operator.itemgetter",
|
||||
"operator.methodcaller",
|
||||
"attrgetter",
|
||||
"itemgetter",
|
||||
"methodcaller",
|
||||
"types.MappingProxyType",
|
||||
"MappingProxyType",
|
||||
"re.compile",
|
||||
];
|
||||
|
||||
fn is_immutable_func(expr: &Expr) -> bool {
|
||||
compose_call_path(expr).map_or_else(|| false, |func| IMMUTABLE_FUNCS.contains(&func.as_str()))
|
||||
}
|
||||
|
||||
struct ArgumentDefaultVisitor {
|
||||
checks: Vec<(CheckKind, Range)>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for ArgumentDefaultVisitor
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if !is_mutable_func(func)
|
||||
&& !is_immutable_func(func)
|
||||
&& !is_nan_or_infinity(func, args)
|
||||
{
|
||||
self.checks.push((
|
||||
CheckKind::FunctionCallArgumentDefault,
|
||||
Range::from_located(expr),
|
||||
))
|
||||
}
|
||||
visitor::walk_expr(self, expr)
|
||||
}
|
||||
ExprKind::Lambda { .. } => {}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_nan_or_infinity(expr: &Expr, args: &[Expr]) -> bool {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if id == "float" {
|
||||
if let Some(arg) = args.first() {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &arg.node
|
||||
{
|
||||
let lowercased = value.to_lowercase();
|
||||
return lowercased == "nan"
|
||||
|| lowercased == "+nan"
|
||||
|| lowercased == "-nan"
|
||||
|| lowercased == "inf"
|
||||
|| lowercased == "+inf"
|
||||
|| lowercased == "-inf"
|
||||
|| lowercased == "infinity"
|
||||
|| lowercased == "+infinity"
|
||||
|| lowercased == "-infinity";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// B008
|
||||
pub fn function_call_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
let mut visitor = ArgumentDefaultVisitor { checks: vec![] };
|
||||
for expr in arguments
|
||||
.defaults
|
||||
.iter()
|
||||
.chain(arguments.kw_defaults.iter())
|
||||
{
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
for (check, range) in visitor.checks {
|
||||
checker.add_check(Check::new(check, checker.locate_check(range)));
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
pub use assert_false::assert_false;
|
||||
pub use assert_raises_exception::assert_raises_exception;
|
||||
pub use assignment_to_os_environ::assignment_to_os_environ;
|
||||
pub use cannot_raise_literal::cannot_raise_literal;
|
||||
pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exceptions};
|
||||
pub use function_call_argument_default::function_call_argument_default;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use unary_prefix_increment::unary_prefix_increment;
|
||||
pub use unused_loop_control_variable::unused_loop_control_variable;
|
||||
pub use useless_comparison::useless_comparison;
|
||||
pub use useless_expression::useless_expression;
|
||||
|
||||
mod assert_false;
|
||||
mod assert_raises_exception;
|
||||
mod assignment_to_os_environ;
|
||||
mod cannot_raise_literal;
|
||||
mod duplicate_exceptions;
|
||||
mod function_call_argument_default;
|
||||
mod mutable_argument_default;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod unary_prefix_increment;
|
||||
mod unused_loop_control_variable;
|
||||
mod useless_comparison;
|
||||
mod useless_expression;
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
use rustpython_ast::{Arguments, ExprKind};
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn is_mutable_func(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Name { id, .. }
|
||||
if id == "dict"
|
||||
|| id == "list"
|
||||
|| id == "set"
|
||||
|| id == "Counter"
|
||||
|| id == "OrderedDict"
|
||||
|| id == "defaultdict"
|
||||
|| id == "deque" =>
|
||||
{
|
||||
true
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. }
|
||||
if (attr == "Counter"
|
||||
|| attr == "OrderedDict"
|
||||
|| attr == "defaultdict"
|
||||
|| attr == "deque") =>
|
||||
{
|
||||
matches!(&value.node, ExprKind::Name { id, .. } if id == "collections")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// B006
|
||||
pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
for expr in arguments
|
||||
@@ -23,39 +48,14 @@ pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
ExprKind::Call { func, .. } => match &func.node {
|
||||
ExprKind::Name { id, .. }
|
||||
if id == "dict"
|
||||
|| id == "list"
|
||||
|| id == "set"
|
||||
|| id == "Counter"
|
||||
|| id == "OrderedDict"
|
||||
|| id == "defaultdict"
|
||||
|| id == "deque" =>
|
||||
{
|
||||
ExprKind::Call { func, .. } => {
|
||||
if is_mutable_func(func) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. }
|
||||
if (attr == "Counter"
|
||||
|| attr == "OrderedDict"
|
||||
|| attr == "defaultdict"
|
||||
|| attr == "deque") =>
|
||||
{
|
||||
match &value.node {
|
||||
ExprKind::Name { id, .. } if id == "collections" => {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/flake8_bugbear/plugins/useless_comparison.rs
Normal file
14
src/flake8_bugbear/plugins/useless_comparison.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn useless_comparison(checker: &mut Checker, expr: &Expr) {
|
||||
if let ExprKind::Compare { left, .. } = &expr.node {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UselessComparison,
|
||||
checker.locate_check(Range::from_located(left)),
|
||||
));
|
||||
}
|
||||
}
|
||||
31
src/flake8_bugbear/plugins/useless_expression.rs
Normal file
31
src/flake8_bugbear/plugins/useless_expression.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
// B018
|
||||
pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) {
|
||||
for stmt in body {
|
||||
if let StmtKind::Expr { value } = &stmt.node {
|
||||
match &value.node {
|
||||
ExprKind::List { .. } | ExprKind::Dict { .. } | ExprKind::Set { .. } => {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UselessExpression,
|
||||
checker.locate_check(Range::from_located(value)),
|
||||
));
|
||||
}
|
||||
ExprKind::Constant { value: val, .. } => match &val {
|
||||
Constant::Str { .. } | Constant::Ellipsis => {}
|
||||
_ => {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UselessExpression,
|
||||
checker.locate_check(Range::from_located(value)),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,9 +197,12 @@ pub fn unnecessary_literal_set(
|
||||
|
||||
/// C406 (`dict([(1, 2)])`)
|
||||
pub fn unnecessary_literal_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
|
||||
@@ -208,18 +211,24 @@ pub fn unnecessary_literal_dict(
|
||||
ExprKind::List { elts, .. } => ("list", elts),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if let Some(elt) = elts.first() {
|
||||
// dict((1, 2), ...)) or dict([(1, 2), ...])
|
||||
if !matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2) {
|
||||
return None;
|
||||
}
|
||||
// Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`.
|
||||
if !elts
|
||||
.iter()
|
||||
.all(|elt| matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Check::new(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
|
||||
location,
|
||||
))
|
||||
);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_literal_dict(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C408
|
||||
@@ -250,12 +259,9 @@ pub fn unnecessary_collection_call(
|
||||
location,
|
||||
);
|
||||
if fix {
|
||||
// TODO(charlie): Support fixing `dict(a=1)`.
|
||||
if keywords.is_empty() {
|
||||
match fixes::fix_unnecessary_collection_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
match fixes::fix_unnecessary_collection_call(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
@@ -446,6 +452,8 @@ pub fn unnecessary_comprehension(
|
||||
expr: &Expr,
|
||||
elt: &Expr,
|
||||
generators: &[Comprehension],
|
||||
locator: &SourceCodeLocator,
|
||||
fix: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if generators.len() != 1 {
|
||||
@@ -465,10 +473,17 @@ pub fn unnecessary_comprehension(
|
||||
ExprKind::SetComp { .. } => "set",
|
||||
_ => return None,
|
||||
};
|
||||
Some(Check::new(
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnnecessaryComprehension(expr_kind.to_string()),
|
||||
location,
|
||||
))
|
||||
);
|
||||
if fix {
|
||||
match fixes::fix_unnecessary_comprehension(locator, expr) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to generate fix: {}", e),
|
||||
}
|
||||
}
|
||||
Some(check)
|
||||
}
|
||||
|
||||
/// C417
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::{
|
||||
Arg, Call, Codegen, Dict, DictComp, Element, Expr, Expression, LeftCurlyBrace, LeftParen,
|
||||
LeftSquareBracket, List, ListComp, Module, ParenthesizableWhitespace, RightCurlyBrace,
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleWhitespace, SmallStatement, Statement,
|
||||
Tuple,
|
||||
Arg, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression, LeftCurlyBrace,
|
||||
LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace, RightCurlyBrace,
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace, Tuple,
|
||||
};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::cst::matchers::match_tree;
|
||||
use crate::cst::matchers::{match_expr, match_module};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
|
||||
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
|
||||
Ok(expr)
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::Expr."
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Statement::Simple."))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_call<'a, 'b>(expr: &'a mut Expr<'b>) -> Result<&'a mut Call<'b>> {
|
||||
if let Expression::Call(call) = &mut expr.value {
|
||||
Ok(call)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Expression::Call."))
|
||||
Err(anyhow::anyhow!("Expected node to be: Expression::Call"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +22,7 @@ fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
|
||||
if let Some(arg) = call.args.first() {
|
||||
Ok(arg)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected node to be: Arg."))
|
||||
Err(anyhow::anyhow!("Expected node to be: Arg"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +32,8 @@ pub fn fix_unnecessary_generator_list(
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -55,7 +42,7 @@ pub fn fix_unnecessary_generator_list(
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
};
|
||||
|
||||
@@ -88,7 +75,8 @@ pub fn fix_unnecessary_generator_set(
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -97,7 +85,7 @@ pub fn fix_unnecessary_generator_set(
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
};
|
||||
|
||||
@@ -130,7 +118,8 @@ pub fn fix_unnecessary_generator_dict(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -140,26 +129,26 @@ pub fn fix_unnecessary_generator_dict(
|
||||
generator_exp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::GeneratorExp."
|
||||
"Expected node to be: Expression::GeneratorExp"
|
||||
));
|
||||
};
|
||||
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
|
||||
tuple
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple."));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
|
||||
};
|
||||
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
|
||||
value
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the first element."
|
||||
"Expected tuple to contain a key as the first element"
|
||||
));
|
||||
};
|
||||
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
|
||||
value
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected tuple to contain a key as the second element."
|
||||
"Expected tuple to contain a key as the second element"
|
||||
));
|
||||
};
|
||||
|
||||
@@ -196,7 +185,8 @@ pub fn fix_unnecessary_list_comprehension_set(
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(ListComp)))) ->
|
||||
// Expr(SetComp)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -204,9 +194,7 @@ pub fn fix_unnecessary_list_comprehension_set(
|
||||
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
|
||||
list_comp
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::ListComp."
|
||||
));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
|
||||
};
|
||||
|
||||
body.value = Expression::SetComp(Box::new(SetComp {
|
||||
@@ -238,7 +226,8 @@ pub fn fix_unnecessary_literal_set(
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(Set)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let mut call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -248,7 +237,7 @@ pub fn fix_unnecessary_literal_set(
|
||||
Expression::List(inner) => &inner.elements,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
"Expected node to be: Expression::Tuple | Expression::List"
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -279,21 +268,98 @@ pub fn fix_unnecessary_literal_set(
|
||||
))
|
||||
}
|
||||
|
||||
/// (C406) Convert `dict([(1, 2)])` to `{1: 2}`.
|
||||
pub fn fix_unnecessary_literal_dict(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(Dict)))
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let elements = match &arg.value {
|
||||
Expression::Tuple(inner) => &inner.elements,
|
||||
Expression::List(inner) => &inner.elements,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List"
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let elements: Vec<DictElement> = elements
|
||||
.iter()
|
||||
.map(|element| {
|
||||
if let Element::Simple {
|
||||
value: Expression::Tuple(tuple),
|
||||
comma,
|
||||
} = element
|
||||
{
|
||||
if let Some(Element::Simple { value: key, .. }) = tuple.elements.get(0) {
|
||||
if let Some(Element::Simple { value, .. }) = tuple.elements.get(1) {
|
||||
return Ok(DictElement::Simple {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
comma: comma.clone(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
|
||||
SimpleWhitespace(" "),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(anyhow::anyhow!(
|
||||
"Expected each argument to be a tuple of length two"
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<DictElement>>>()?;
|
||||
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements,
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: arg.whitespace_after_arg.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C408)
|
||||
pub fn fix_unnecessary_collection_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
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 {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Name."));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Expression::Name"));
|
||||
};
|
||||
|
||||
// Arena allocator used to create formatted strings of sufficient lifetime,
|
||||
// below.
|
||||
let mut arena: Vec<String> = vec![];
|
||||
|
||||
match name.value {
|
||||
"tuple" => {
|
||||
body.value = Expression::Tuple(Box::new(Tuple {
|
||||
@@ -312,17 +378,67 @@ pub fn fix_unnecessary_collection_call(
|
||||
}));
|
||||
}
|
||||
"dict" => {
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements: Default::default(),
|
||||
lbrace: Default::default(),
|
||||
rbrace: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
if call.args.is_empty() {
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements: Default::default(),
|
||||
lbrace: Default::default(),
|
||||
rbrace: Default::default(),
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
} else {
|
||||
// Quote each argument.
|
||||
for arg in &call.args {
|
||||
let quoted = format!(
|
||||
"\"{}\"",
|
||||
arg.keyword
|
||||
.as_ref()
|
||||
.expect("Expected dictionary argument to be kwarg")
|
||||
.value
|
||||
);
|
||||
arena.push(quoted);
|
||||
}
|
||||
|
||||
let elements = call
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| DictElement::Simple {
|
||||
key: Expression::SimpleString(Box::new(SimpleString {
|
||||
value: &arena[i],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
})),
|
||||
value: arg.value.clone(),
|
||||
comma: arg.comma.clone(),
|
||||
whitespace_before_colon: Default::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
|
||||
SimpleWhitespace(" "),
|
||||
),
|
||||
})
|
||||
.collect();
|
||||
|
||||
body.value = Expression::Dict(Box::new(Dict {
|
||||
elements,
|
||||
lbrace: LeftCurlyBrace {
|
||||
whitespace_after: call.whitespace_before_args.clone(),
|
||||
},
|
||||
rbrace: RightCurlyBrace {
|
||||
whitespace_before: call
|
||||
.args
|
||||
.last()
|
||||
.expect("Arguments should be non-empty")
|
||||
.whitespace_after_arg
|
||||
.clone(),
|
||||
},
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!("Expected function name to be one of: \
|
||||
'tuple', 'list', 'dict'."
|
||||
'tuple', 'list', 'dict'"
|
||||
.to_string()));
|
||||
}
|
||||
};
|
||||
@@ -342,7 +458,8 @@ pub fn fix_unnecessary_literal_within_tuple_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -352,12 +469,12 @@ pub fn fix_unnecessary_literal_within_tuple_call(
|
||||
&inner
|
||||
.lpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
|
||||
.whitespace_after,
|
||||
&inner
|
||||
.rpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
|
||||
.whitespace_before,
|
||||
),
|
||||
Expression::List(inner) => (
|
||||
@@ -367,7 +484,7 @@ pub fn fix_unnecessary_literal_within_tuple_call(
|
||||
),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
"Expected node to be: Expression::Tuple | Expression::List"
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -397,7 +514,8 @@ pub fn fix_unnecessary_literal_within_list_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -407,12 +525,12 @@ pub fn fix_unnecessary_literal_within_list_call(
|
||||
&inner
|
||||
.lpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
|
||||
.whitespace_after,
|
||||
&inner
|
||||
.rpar
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses."))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
|
||||
.whitespace_before,
|
||||
),
|
||||
Expression::List(inner) => (
|
||||
@@ -422,7 +540,7 @@ pub fn fix_unnecessary_literal_within_list_call(
|
||||
),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Tuple | Expression::List."
|
||||
"Expected node to be: Expression::Tuple | Expression::List"
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -449,13 +567,14 @@ pub fn fix_unnecessary_literal_within_list_call(
|
||||
))
|
||||
}
|
||||
|
||||
/// (C411) Convert `list([i for i in x])` to `[i for i in x]`.
|
||||
/// (C411) Convert `list([i * i for i in x])` to `[i * i for i in x]`.
|
||||
pub fn fix_unnecessary_list_call(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
|
||||
let mut tree = match_tree(locator, expr)?;
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
@@ -471,3 +590,74 @@ pub fn fix_unnecessary_list_call(
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C416) Convert `[i for i in x]` to `list(x)`.
|
||||
pub fn fix_unnecessary_comprehension(
|
||||
locator: &SourceCodeLocator,
|
||||
expr: &rustpython_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
|
||||
match &body.value {
|
||||
Expression::ListComp(inner) => {
|
||||
body.value = Expression::Call(Box::new(Call {
|
||||
func: Box::new(Expression::Name(Box::new(Name {
|
||||
value: "list",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}))),
|
||||
args: vec![Arg {
|
||||
value: inner.for_in.iter.clone(),
|
||||
keyword: Default::default(),
|
||||
equal: Default::default(),
|
||||
comma: Default::default(),
|
||||
star: Default::default(),
|
||||
whitespace_after_star: Default::default(),
|
||||
whitespace_after_arg: Default::default(),
|
||||
}],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_after_func: Default::default(),
|
||||
whitespace_before_args: Default::default(),
|
||||
}))
|
||||
}
|
||||
Expression::SetComp(inner) => {
|
||||
body.value = Expression::Call(Box::new(Call {
|
||||
func: Box::new(Expression::Name(Box::new(Name {
|
||||
value: "set",
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
}))),
|
||||
args: vec![Arg {
|
||||
value: inner.for_in.iter.clone(),
|
||||
keyword: Default::default(),
|
||||
equal: Default::default(),
|
||||
comma: Default::default(),
|
||||
star: Default::default(),
|
||||
whitespace_after_star: Default::default(),
|
||||
whitespace_after_arg: Default::default(),
|
||||
}],
|
||||
lpar: Default::default(),
|
||||
rpar: Default::default(),
|
||||
whitespace_after_func: Default::default(),
|
||||
whitespace_before_args: Default::default(),
|
||||
}))
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::ListComp | Expression:SetComp"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -148,13 +148,22 @@ mod tests {
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::linter::tokenize;
|
||||
use crate::{flake8_quotes, fs, linter, noqa, Settings};
|
||||
use crate::{flake8_quotes, fs, linter, noqa, Settings, SourceCodeLocator};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)
|
||||
linter::check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
|
||||
#[test_case(Path::new("doubles.py"))]
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod checks;
|
||||
pub mod docstring_detection;
|
||||
pub mod settings;
|
||||
|
||||
10
src/fs.rs
10
src/fs.rs
@@ -118,7 +118,7 @@ pub fn iter_python_files<'a>(
|
||||
}
|
||||
|
||||
/// Create tree set with codes matching the pattern/code pairs.
|
||||
pub fn ignores_from_path<'a>(
|
||||
pub(crate) fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [PerFileIgnore],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
@@ -138,7 +138,7 @@ pub fn ignores_from_path<'a>(
|
||||
|
||||
/// Convert any path to an absolute path (based on the current working
|
||||
/// directory).
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize() {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
@@ -146,7 +146,7 @@ pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
}
|
||||
|
||||
/// Convert any path to an absolute path (based on the specified project root).
|
||||
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize_from(project_root) {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
@@ -154,7 +154,7 @@ pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
}
|
||||
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
pub fn relativize_path(path: &Path) -> Cow<str> {
|
||||
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
|
||||
if let Ok(path) = path.strip_prefix(path_dedot::CWD.deref()) {
|
||||
return path.to_string_lossy();
|
||||
}
|
||||
@@ -162,7 +162,7 @@ pub fn relativize_path(path: &Path) -> Cow<str> {
|
||||
}
|
||||
|
||||
/// Read a file's contents from disk.
|
||||
pub fn read_file(path: &Path) -> Result<String> {
|
||||
pub(crate) fn read_file(path: &Path) -> Result<String> {
|
||||
let file = File::open(path)?;
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
let mut contents = String::new();
|
||||
|
||||
1
src/lex/mod.rs
Normal file
1
src/lex/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod docstring_detection;
|
||||
@@ -11,6 +11,7 @@ use crate::autofix::fixer::Mode;
|
||||
use crate::checks::Check;
|
||||
use crate::linter::{check_path, tokenize};
|
||||
use crate::settings::configuration::Configuration;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
mod ast;
|
||||
pub mod autofix;
|
||||
@@ -30,6 +31,7 @@ mod flake8_comprehensions;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
pub mod fs;
|
||||
mod lex;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
@@ -41,6 +43,7 @@ mod pydocstyle;
|
||||
mod pyflakes;
|
||||
mod python;
|
||||
mod pyupgrade;
|
||||
mod rules;
|
||||
pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
pub mod visibility;
|
||||
@@ -65,6 +68,9 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
@@ -73,6 +79,7 @@ pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
path,
|
||||
contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
&settings,
|
||||
&if autofix { Mode::Generate } else { Mode::None },
|
||||
|
||||
@@ -54,6 +54,7 @@ pub(crate) fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
locator: &SourceCodeLocator,
|
||||
noqa_line_for: &[usize],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
@@ -61,16 +62,13 @@ pub(crate) fn check_path(
|
||||
// Aggregate all checks.
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(contents);
|
||||
|
||||
// Run the token-based checks.
|
||||
if settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Tokens))
|
||||
{
|
||||
check_tokens(&mut checks, &locator, &tokens, settings);
|
||||
check_tokens(&mut checks, locator, &tokens, settings, autofix);
|
||||
}
|
||||
|
||||
// Run the AST-based checks.
|
||||
@@ -81,7 +79,7 @@ pub(crate) fn check_path(
|
||||
{
|
||||
match parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
checks.extend(check_ast(&python_ast, &locator, settings, autofix, path))
|
||||
checks.extend(check_ast(&python_ast, locator, settings, autofix, path))
|
||||
}
|
||||
Err(parse_error) => {
|
||||
if settings.enabled.contains(&CheckCode::E999) {
|
||||
@@ -123,19 +121,29 @@ pub fn lint_stdin(
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(stdin);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(stdin);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(path, stdin, tokens, &noqa_line_for, settings, autofix)?;
|
||||
let mut checks = check_path(
|
||||
path,
|
||||
stdin,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
|
||||
// Apply autofix, write results to stdout.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
let output = match fix_file(&mut checks, stdin) {
|
||||
None => stdin.to_string(),
|
||||
Some(content) => content,
|
||||
};
|
||||
io::stdout().write_all(output.as_bytes())?;
|
||||
match fix_file(&mut checks, &locator) {
|
||||
None => io::stdout().write_all(stdin.as_bytes()),
|
||||
Some(contents) => io::stdout().write_all(contents.as_bytes()),
|
||||
}?;
|
||||
}
|
||||
|
||||
// Convert to messages.
|
||||
@@ -167,16 +175,27 @@ pub fn lint_path(
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
|
||||
let mut checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)?;
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
if let Some(fixed_contents) = fix_file(&mut checks, &contents) {
|
||||
write(path, fixed_contents)?;
|
||||
if let Some(fixed_contents) = fix_file(&mut checks, &locator) {
|
||||
write(path, fixed_contents.as_ref())?;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,6 +217,9 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
|
||||
// Initialize the SourceCodeLocator (which computes offsets lazily).
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
@@ -206,6 +228,7 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
&fixer::Mode::None,
|
||||
@@ -243,25 +266,44 @@ mod tests {
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::linter::tokenize;
|
||||
use crate::{fs, linter, noqa, settings, Settings};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{fs, linter, noqa, settings};
|
||||
|
||||
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
|
||||
fn check_path(
|
||||
path: &Path,
|
||||
settings: &settings::Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let locator = SourceCodeLocator::new(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)
|
||||
linter::check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
autofix,
|
||||
)
|
||||
}
|
||||
|
||||
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
|
||||
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
|
||||
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
||||
#[test_case(CheckCode::B002, Path::new("B002.py"); "B002")]
|
||||
#[test_case(CheckCode::B003, Path::new("B003.py"); "B003")]
|
||||
#[test_case(CheckCode::B006, Path::new("B006_B008.py"); "B006")]
|
||||
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
|
||||
#[test_case(CheckCode::B008, Path::new("B006_B008.py"); "B008")]
|
||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||
#[test_case(CheckCode::B015, Path::new("B015.py"); "B015")]
|
||||
#[test_case(CheckCode::B016, Path::new("B016.py"); "B016")]
|
||||
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
||||
#[test_case(CheckCode::B018, Path::new("B018.py"); "B018")]
|
||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
||||
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
||||
@@ -366,6 +408,7 @@ mod tests {
|
||||
#[test_case(CheckCode::F821, Path::new("F821_0.py"); "F821_0")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_1.py"); "F821_1")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_2.py"); "F821_2")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_3.py"); "F821_3")]
|
||||
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
|
||||
@@ -396,10 +439,17 @@ mod tests {
|
||||
#[test_case(CheckCode::U006, Path::new("U006.py"); "U006")]
|
||||
#[test_case(CheckCode::U007, Path::new("U007.py"); "U007")]
|
||||
#[test_case(CheckCode::U008, Path::new("U008.py"); "U008")]
|
||||
#[test_case(CheckCode::U009, Path::new("U009_0.py"); "U009_0")]
|
||||
#[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::W292, Path::new("W292_0.py"); "W292_0")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_1.py"); "W292_1")]
|
||||
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
|
||||
#[test_case(CheckCode::W605, Path::new("W605.py"); "W605")]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_0.py"); "W605_0")]
|
||||
#[test_case(CheckCode::W605, Path::new("W605_1.py"); "W605_1")]
|
||||
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
|
||||
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = check_path(
|
||||
|
||||
34
src/noqa.rs
34
src/noqa.rs
@@ -1,4 +1,3 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -46,38 +45,21 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
let mut noqa_line_for: Vec<usize> = vec![];
|
||||
|
||||
let mut min_line = usize::MAX;
|
||||
let mut max_line = usize::MIN;
|
||||
let mut in_string = false;
|
||||
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Newline) {
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, start.row());
|
||||
|
||||
// For now, we only care about preserving noqa directives across multi-line
|
||||
// strings.
|
||||
if in_string {
|
||||
for i in (noqa_line_for.len())..(min_line - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
|
||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||
// string. By definition, we can't have multiple multi-line strings on
|
||||
// the same line, so we don't need to verify that we haven't already
|
||||
// traversed past the current line.
|
||||
if matches!(tok, Tok::String { .. }) && end.row() > start.row() {
|
||||
for i in (noqa_line_for.len())..(start.row() - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
|
||||
min_line = usize::MAX;
|
||||
max_line = usize::MIN;
|
||||
} else {
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, end.row());
|
||||
noqa_line_for.extend(vec![end.row(); (end.row() + 1) - start.row()]);
|
||||
}
|
||||
in_string = matches!(tok, Tok::String { .. });
|
||||
}
|
||||
|
||||
noqa_line_for
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::types::{FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pep8_naming::helpers;
|
||||
use crate::pep8_naming::helpers::FunctionType;
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
|
||||
/// N801
|
||||
@@ -25,7 +26,7 @@ pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
|
||||
|
||||
/// N802
|
||||
pub fn invalid_function_name(func_def: &Stmt, name: &str, settings: &Settings) -> Option<Check> {
|
||||
if !is_lower(name)
|
||||
if name.to_lowercase() != name
|
||||
&& !settings
|
||||
.ignore_names
|
||||
.iter()
|
||||
@@ -40,8 +41,8 @@ pub fn invalid_function_name(func_def: &Stmt, name: &str, settings: &Settings) -
|
||||
}
|
||||
|
||||
/// N803
|
||||
pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
|
||||
if !is_lower(name) {
|
||||
pub fn invalid_argument_name(name: &str, location: Range) -> Option<Check> {
|
||||
if name.to_lowercase() != name {
|
||||
return Some(Check::new(
|
||||
CheckKind::InvalidArgumentName(name.to_string()),
|
||||
location,
|
||||
@@ -53,21 +54,15 @@ pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
|
||||
/// N804
|
||||
pub fn invalid_first_argument_name_for_class_method(
|
||||
scope: &Scope,
|
||||
name: &str,
|
||||
decorator_list: &[Expr],
|
||||
args: &Arguments,
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
settings.classmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
if matches!(
|
||||
helpers::function_type(scope, name, decorator_list, settings),
|
||||
FunctionType::ClassMethod
|
||||
) {
|
||||
if let Some(arg) = args.args.first() {
|
||||
if arg.node.arg != "cls" {
|
||||
return Some(Check::new(
|
||||
@@ -83,31 +78,22 @@ pub fn invalid_first_argument_name_for_class_method(
|
||||
/// N805
|
||||
pub fn invalid_first_argument_name_for_method(
|
||||
scope: &Scope,
|
||||
name: &str,
|
||||
decorator_list: &[Expr],
|
||||
args: &Arguments,
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
settings.classmethod_decorators.contains(id)
|
||||
|| settings.staticmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(arg) = args.args.first() {
|
||||
if arg.node.arg != "self" {
|
||||
return Some(Check::new(
|
||||
CheckKind::InvalidFirstArgumentNameForMethod,
|
||||
Range::from_located(arg),
|
||||
));
|
||||
if matches!(
|
||||
helpers::function_type(scope, name, decorator_list, settings),
|
||||
FunctionType::Method
|
||||
) {
|
||||
if let Some(arg) = args.args.first() {
|
||||
if arg.node.arg != "self" {
|
||||
return Some(Check::new(
|
||||
CheckKind::InvalidFirstArgumentNameForMethod,
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -128,18 +114,16 @@ pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str
|
||||
}
|
||||
|
||||
/// N807
|
||||
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
|
||||
if matches!(scope.kind, ScopeKind::Class) {
|
||||
pub fn dunder_function_name(scope: &Scope, stmt: &Stmt, name: &str) -> Option<Check> {
|
||||
if matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if name.starts_with("__") && name.ends_with("__") {
|
||||
return Some(Check::new(
|
||||
CheckKind::DunderFunctionName,
|
||||
Range::from_located(func_def),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -149,7 +133,7 @@ pub fn constant_imported_as_non_constant(
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_upper(name) && !is_upper(asname) {
|
||||
if helpers::is_upper(name) && !helpers::is_upper(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
@@ -164,7 +148,7 @@ pub fn lowercase_imported_as_non_lowercase(
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_lower(name) && asname.to_lowercase() != asname {
|
||||
if !helpers::is_upper(name) && helpers::is_lower(name) && asname.to_lowercase() != asname {
|
||||
return Some(Check::new(
|
||||
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
@@ -179,7 +163,7 @@ pub fn camelcase_imported_as_lowercase(
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_lower(asname) {
|
||||
if helpers::is_camelcase(name) && helpers::is_lower(asname) {
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
@@ -194,7 +178,11 @@ pub fn camelcase_imported_as_constant(
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
|
||||
if helpers::is_camelcase(name)
|
||||
&& !helpers::is_lower(asname)
|
||||
&& helpers::is_upper(asname)
|
||||
&& !helpers::is_acronym(name, asname)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
@@ -205,10 +193,10 @@ pub fn camelcase_imported_as_constant(
|
||||
|
||||
/// N815
|
||||
pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class) {
|
||||
if !matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
return None;
|
||||
}
|
||||
if is_mixed_case(name) {
|
||||
if helpers::is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInClassScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
@@ -226,7 +214,7 @@ pub fn mixed_case_variable_in_global_scope(
|
||||
if !matches!(scope.kind, ScopeKind::Module) {
|
||||
return None;
|
||||
}
|
||||
if is_mixed_case(name) {
|
||||
if helpers::is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
@@ -241,7 +229,11 @@ pub fn camelcase_imported_as_acronym(
|
||||
name: &str,
|
||||
asname: &str,
|
||||
) -> Option<Check> {
|
||||
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
|
||||
if helpers::is_camelcase(name)
|
||||
&& !helpers::is_lower(asname)
|
||||
&& helpers::is_upper(asname)
|
||||
&& helpers::is_acronym(name, asname)
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
|
||||
Range::from_located(import_from),
|
||||
@@ -272,101 +264,3 @@ pub fn error_suffix_on_exception_name(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_lower(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
if c.is_uppercase() {
|
||||
return false;
|
||||
} else if !cased && c.is_lowercase() {
|
||||
cased = true;
|
||||
}
|
||||
}
|
||||
cased
|
||||
}
|
||||
|
||||
fn is_upper(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
if c.is_lowercase() {
|
||||
return false;
|
||||
} else if (!cased) && c.is_uppercase() {
|
||||
cased = true;
|
||||
}
|
||||
}
|
||||
cased
|
||||
}
|
||||
|
||||
fn is_camelcase(name: &str) -> bool {
|
||||
!is_lower(name) && !is_upper(name) && !name.contains('_')
|
||||
}
|
||||
|
||||
fn is_mixed_case(name: &str) -> bool {
|
||||
!is_lower(name)
|
||||
&& name
|
||||
.strip_prefix('_')
|
||||
.unwrap_or(name)
|
||||
.chars()
|
||||
.next()
|
||||
.map_or_else(|| false, |c| c.is_lowercase())
|
||||
}
|
||||
|
||||
fn is_acronym(name: &str, asname: &str) -> bool {
|
||||
name.chars().filter(|c| c.is_uppercase()).join("") == asname
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_acronym, is_camelcase, is_lower, is_mixed_case, is_upper};
|
||||
|
||||
#[test]
|
||||
fn test_is_lower() -> () {
|
||||
assert!(is_lower("abc"));
|
||||
assert!(is_lower("a_b_c"));
|
||||
assert!(is_lower("a2c"));
|
||||
assert!(!is_lower("aBc"));
|
||||
assert!(!is_lower("ABC"));
|
||||
assert!(!is_lower(""));
|
||||
assert!(!is_lower("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_upper() -> () {
|
||||
assert!(is_upper("ABC"));
|
||||
assert!(is_upper("A_B_C"));
|
||||
assert!(is_upper("A2C"));
|
||||
assert!(!is_upper("aBc"));
|
||||
assert!(!is_upper("abc"));
|
||||
assert!(!is_upper(""));
|
||||
assert!(!is_upper("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_camelcase() -> () {
|
||||
assert!(is_camelcase("Camel"));
|
||||
assert!(is_camelcase("CamelCase"));
|
||||
assert!(!is_camelcase("camel"));
|
||||
assert!(!is_camelcase("camel_case"));
|
||||
assert!(!is_camelcase("CAMEL"));
|
||||
assert!(!is_camelcase("CAMEL_CASE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_mixed_case() -> () {
|
||||
assert!(is_mixed_case("mixedCase"));
|
||||
assert!(is_mixed_case("mixed_Case"));
|
||||
assert!(is_mixed_case("_mixed_Case"));
|
||||
assert!(!is_mixed_case("mixed_case"));
|
||||
assert!(!is_mixed_case("MIXED_CASE"));
|
||||
assert!(!is_mixed_case(""));
|
||||
assert!(!is_mixed_case("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_acronym() -> () {
|
||||
assert!(is_acronym("AB", "AB"));
|
||||
assert!(is_acronym("AbcDef", "AD"));
|
||||
assert!(!is_acronym("AbcDef", "Ad"));
|
||||
assert!(!is_acronym("AbcDef", "AB"));
|
||||
}
|
||||
}
|
||||
|
||||
160
src/pep8_naming/helpers.rs
Normal file
160
src/pep8_naming/helpers.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::types::{Scope, ScopeKind};
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
|
||||
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
|
||||
const METACLASS_BASES: [&str; 2] = ["type", "ABCMeta"];
|
||||
|
||||
pub enum FunctionType {
|
||||
Function,
|
||||
Method,
|
||||
ClassMethod,
|
||||
StaticMethod,
|
||||
}
|
||||
|
||||
/// Classify a function based on its scope, name, and decorators.
|
||||
pub fn function_type(
|
||||
scope: &Scope,
|
||||
name: &str,
|
||||
decorator_list: &[Expr],
|
||||
settings: &Settings,
|
||||
) -> FunctionType {
|
||||
if let ScopeKind::Class(scope) = &scope.kind {
|
||||
// Special-case class method, like `__new__`.
|
||||
if CLASS_METHODS.contains(&name)
|
||||
// The class itself extends a known metaclass, so all methods are class methods.
|
||||
|| scope.bases.iter().any(|expr| {
|
||||
METACLASS_BASES
|
||||
.iter()
|
||||
.any(|target| match_name_or_attr(expr, target))
|
||||
})
|
||||
// The method is decorated with a class method decorator (like `@classmethod`).
|
||||
|| decorator_list.iter().any(|expr| {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
settings.classmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
FunctionType::ClassMethod
|
||||
} else if decorator_list.iter().any(|expr| {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
settings.staticmethod_decorators.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
// The method is decorated with a static method decorator (like
|
||||
// `@staticmethod`).
|
||||
FunctionType::StaticMethod
|
||||
} else {
|
||||
// It's an instance method.
|
||||
FunctionType::Method
|
||||
}
|
||||
} else {
|
||||
FunctionType::Function
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_lower(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
if c.is_uppercase() {
|
||||
return false;
|
||||
} else if !cased && c.is_lowercase() {
|
||||
cased = true;
|
||||
}
|
||||
}
|
||||
cased
|
||||
}
|
||||
|
||||
pub fn is_upper(s: &str) -> bool {
|
||||
let mut cased = false;
|
||||
for c in s.chars() {
|
||||
if c.is_lowercase() {
|
||||
return false;
|
||||
} else if !cased && c.is_uppercase() {
|
||||
cased = true;
|
||||
}
|
||||
}
|
||||
cased
|
||||
}
|
||||
|
||||
pub fn is_camelcase(name: &str) -> bool {
|
||||
!is_lower(name) && !is_upper(name) && !name.contains('_')
|
||||
}
|
||||
|
||||
pub fn is_mixed_case(name: &str) -> bool {
|
||||
!is_lower(name)
|
||||
&& name
|
||||
.strip_prefix('_')
|
||||
.unwrap_or(name)
|
||||
.chars()
|
||||
.next()
|
||||
.map_or_else(|| false, |c| c.is_lowercase())
|
||||
}
|
||||
|
||||
pub fn is_acronym(name: &str, asname: &str) -> bool {
|
||||
name.chars().filter(|c| c.is_uppercase()).join("") == asname
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::pep8_naming::helpers::{
|
||||
is_acronym, is_camelcase, is_lower, is_mixed_case, is_upper,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_is_lower() -> () {
|
||||
assert!(is_lower("abc"));
|
||||
assert!(is_lower("a_b_c"));
|
||||
assert!(is_lower("a2c"));
|
||||
assert!(!is_lower("aBc"));
|
||||
assert!(!is_lower("ABC"));
|
||||
assert!(!is_lower(""));
|
||||
assert!(!is_lower("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_upper() -> () {
|
||||
assert!(is_upper("ABC"));
|
||||
assert!(is_upper("A_B_C"));
|
||||
assert!(is_upper("A2C"));
|
||||
assert!(!is_upper("aBc"));
|
||||
assert!(!is_upper("abc"));
|
||||
assert!(!is_upper(""));
|
||||
assert!(!is_upper("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_camelcase() -> () {
|
||||
assert!(is_camelcase("Camel"));
|
||||
assert!(is_camelcase("CamelCase"));
|
||||
assert!(!is_camelcase("camel"));
|
||||
assert!(!is_camelcase("camel_case"));
|
||||
assert!(!is_camelcase("CAMEL"));
|
||||
assert!(!is_camelcase("CAMEL_CASE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_mixed_case() -> () {
|
||||
assert!(is_mixed_case("mixedCase"));
|
||||
assert!(is_mixed_case("mixed_Case"));
|
||||
assert!(is_mixed_case("_mixed_Case"));
|
||||
assert!(!is_mixed_case("mixed_case"));
|
||||
assert!(!is_mixed_case("MIXED_CASE"));
|
||||
assert!(!is_mixed_case(""));
|
||||
assert!(!is_mixed_case("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_acronym() -> () {
|
||||
assert!(is_acronym("AB", "AB"));
|
||||
assert!(is_acronym("AbcDef", "AD"));
|
||||
assert!(!is_acronym("AbcDef", "Ad"));
|
||||
assert!(!is_acronym("AbcDef", "AB"));
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod checks;
|
||||
mod helpers;
|
||||
pub mod settings;
|
||||
|
||||
@@ -271,26 +271,32 @@ pub fn invalid_escape_sequence(
|
||||
});
|
||||
|
||||
// Determine whether the string is single- or triple-quoted.
|
||||
let quote = extract_quote(text);
|
||||
let quote = extract_quote(&text);
|
||||
let quote_pos = text.find(quote).unwrap();
|
||||
let prefix = text[..quote_pos].to_lowercase();
|
||||
let body = &text[(quote_pos + quote.len())..(text.len() - quote.len())];
|
||||
|
||||
if !prefix.contains('r') {
|
||||
let mut col_offset = 0;
|
||||
let mut row_offset = 0;
|
||||
let mut in_escape = false;
|
||||
let mut chars = body.chars();
|
||||
let mut current = chars.next();
|
||||
let mut next = chars.next();
|
||||
while let (Some(current_char), Some(next_char)) = (current, next) {
|
||||
// If we see an escaped backslash, avoid treating the character _after_ the
|
||||
// escaped backslash as itself an escaped character.
|
||||
if in_escape {
|
||||
in_escape = false;
|
||||
} else {
|
||||
in_escape = current_char == '\\' && next_char == '\\';
|
||||
if current_char == '\\' && !VALID_ESCAPE_SEQUENCES.contains(&next_char) {
|
||||
for (row_offset, line) in body.lines().enumerate() {
|
||||
let chars: Vec<char> = line.chars().collect();
|
||||
for col_offset in 0..chars.len() {
|
||||
if chars[col_offset] == '\\' {
|
||||
// If the previous character was also a backslash, skip.
|
||||
if col_offset > 0 && chars[col_offset - 1] == '\\' {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're at the end of the line, skip.
|
||||
if col_offset == chars.len() - 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the next character is a valid escape sequence, skip.
|
||||
let next_char = chars[col_offset + 1];
|
||||
if VALID_ESCAPE_SEQUENCES.contains(&next_char) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the location of the escape sequence by offsetting the location of the
|
||||
// string token by the characters we've seen thus far.
|
||||
let location = if row_offset == 0 {
|
||||
@@ -299,9 +305,9 @@ pub fn invalid_escape_sequence(
|
||||
start.column() + prefix.len() + quote.len() + col_offset,
|
||||
)
|
||||
} else {
|
||||
Location::new(start.row() + row_offset, col_offset + 1)
|
||||
Location::new(start.row() + row_offset, col_offset)
|
||||
};
|
||||
let end_location = Location::new(location.row(), location.column() + 1);
|
||||
let end_location = Location::new(location.row(), location.column() + 2);
|
||||
checks.push(Check::new(
|
||||
CheckKind::InvalidEscapeSequence(next_char),
|
||||
Range {
|
||||
@@ -311,17 +317,6 @@ pub fn invalid_escape_sequence(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Track the offset from the start position as we iterate over the body.
|
||||
if current_char == '\n' {
|
||||
col_offset = 0;
|
||||
row_offset += 1;
|
||||
} else {
|
||||
col_offset += 1;
|
||||
}
|
||||
|
||||
current = next;
|
||||
next = chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
|
||||
.count();
|
||||
|
||||
// Avoid D202 violations for blank lines followed by inner functions or classes.
|
||||
if blank_lines_after == 1 && INNER_FUNCTION_OR_CLASS_REGEX.is_match(after) {
|
||||
if blank_lines_after == 1 && INNER_FUNCTION_OR_CLASS_REGEX.is_match(&after) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
|
||||
return;
|
||||
}
|
||||
|
||||
let docstring_indent = helpers::indentation(checker, docstring).to_string();
|
||||
let docstring_indent = helpers::indentation(checker, docstring);
|
||||
let mut has_seen_tab = docstring_indent.contains('\t');
|
||||
let mut is_over_indented = true;
|
||||
let mut over_indented_lines = vec![];
|
||||
@@ -537,7 +537,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
|
||||
// Insert a newline just before the end-quote(s).
|
||||
let content = format!(
|
||||
"\n{}",
|
||||
helpers::clean(helpers::indentation(checker, docstring))
|
||||
helpers::clean(&helpers::indentation(checker, docstring))
|
||||
);
|
||||
check.amend(Fix::insertion(
|
||||
content,
|
||||
@@ -916,7 +916,7 @@ fn blanks_and_section_underline(
|
||||
// Add a dashed line (of the appropriate length) under the section header.
|
||||
let content = format!(
|
||||
"{}{}\n",
|
||||
helpers::clean(helpers::indentation(checker, docstring)),
|
||||
helpers::clean(&helpers::indentation(checker, docstring)),
|
||||
"-".repeat(context.section_name.len())
|
||||
);
|
||||
check.amend(Fix::insertion(
|
||||
@@ -950,7 +950,7 @@ fn blanks_and_section_underline(
|
||||
// Add a dashed line (of the appropriate length) under the section header.
|
||||
let content = format!(
|
||||
"{}{}\n",
|
||||
helpers::clean(helpers::indentation(checker, docstring)),
|
||||
helpers::clean(&helpers::indentation(checker, docstring)),
|
||||
"-".repeat(context.section_name.len())
|
||||
);
|
||||
check.amend(Fix::insertion(
|
||||
@@ -1026,7 +1026,7 @@ fn blanks_and_section_underline(
|
||||
// Replace the existing underline with a line of the appropriate length.
|
||||
let content = format!(
|
||||
"{}{}\n",
|
||||
helpers::clean(helpers::indentation(checker, docstring)),
|
||||
helpers::clean(&helpers::indentation(checker, docstring)),
|
||||
"-".repeat(context.section_name.len())
|
||||
);
|
||||
check.amend(Fix::replacement(
|
||||
@@ -1054,7 +1054,7 @@ fn blanks_and_section_underline(
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D215) {
|
||||
let leading_space = helpers::leading_space(non_empty_line);
|
||||
let indentation = helpers::indentation(checker, docstring).to_string();
|
||||
let indentation = helpers::indentation(checker, docstring);
|
||||
if leading_space.len() > indentation.len() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
|
||||
@@ -1195,7 +1195,7 @@ fn common_section(
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D214) {
|
||||
let leading_space = helpers::leading_space(context.line);
|
||||
let indentation = helpers::indentation(checker, docstring).to_string();
|
||||
let indentation = helpers::indentation(checker, docstring);
|
||||
if leading_space.len() > indentation.len() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::izip;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt,
|
||||
StmtKind,
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
||||
};
|
||||
|
||||
use crate::ast::types::{BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// F634
|
||||
pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::IfTuple, location));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// F631
|
||||
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
@@ -30,6 +18,16 @@ pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// F634
|
||||
pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::IfTuple, location));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// F841
|
||||
pub fn unused_variables(
|
||||
scope: &Scope,
|
||||
@@ -78,33 +76,6 @@ pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
|
||||
None
|
||||
}
|
||||
|
||||
/// F901
|
||||
pub fn raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(
|
||||
CheckKind::RaiseNotImplemented,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(
|
||||
CheckKind::RaiseNotImplemented,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// F831
|
||||
pub fn duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
@@ -193,45 +164,6 @@ pub fn repeated_keys(
|
||||
checks
|
||||
}
|
||||
|
||||
fn is_constant(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Constant { .. } => true,
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_singleton(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
expr.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None | Constant::Bool(_) | Constant::Ellipsis,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// F632
|
||||
pub fn is_literal(left: &Expr, ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
let mut left = left;
|
||||
for (op, right) in izip!(ops, comparators) {
|
||||
if matches!(op, Cmpop::Is | Cmpop::IsNot)
|
||||
&& (is_constant_non_singleton(left) || is_constant_non_singleton(right))
|
||||
{
|
||||
checks.push(Check::new(CheckKind::IsLiteral, location));
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// F621, F622
|
||||
pub fn starred_expressions(
|
||||
elts: &[Expr],
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use libcst_native::{Codegen, ImportNames, NameOrAttribute, SmallStatement, Statement};
|
||||
use libcst_native::{
|
||||
Codegen, CompOp, Comparison, ComparisonTarget, Expr, Expression, ImportNames, NameOrAttribute,
|
||||
SmallStatement, Statement,
|
||||
};
|
||||
use rustpython_ast::Stmt;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::{helpers, Fix};
|
||||
use crate::cst::helpers::compose_module_path;
|
||||
use crate::cst::matchers::match_tree;
|
||||
use crate::cst::matchers::{match_expr, match_module};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// Generate a Fix to remove any unused imports from an `import` statement.
|
||||
@@ -15,18 +19,19 @@ pub fn remove_unused_imports(
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, stmt)?;
|
||||
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 {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
|
||||
};
|
||||
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::ImportFrom."
|
||||
"Expected node to be: SmallStatement::ImportFrom"
|
||||
));
|
||||
};
|
||||
let aliases = &mut body.names;
|
||||
@@ -72,25 +77,26 @@ pub fn remove_unused_import_froms(
|
||||
parent: Option<&Stmt>,
|
||||
deleted: &[&Stmt],
|
||||
) -> Result<Fix> {
|
||||
let mut tree = match_tree(locator, stmt)?;
|
||||
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 {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
|
||||
};
|
||||
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
|
||||
body
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected node to be: SmallStatement::ImportFrom."
|
||||
"Expected node to be: SmallStatement::ImportFrom"
|
||||
));
|
||||
};
|
||||
|
||||
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
|
||||
aliases
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
|
||||
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
|
||||
};
|
||||
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
@@ -135,3 +141,66 @@ pub fn remove_unused_import_froms(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_comparison<'a, 'b>(expr: &'a mut Expr<'b>) -> Result<&'a mut Comparison<'b>> {
|
||||
if let Expression::Comparison(comparison) = &mut expr.value {
|
||||
Ok(comparison)
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Expected node to be: Expression::Comparison"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a Fix to replace invalid is/is not comparisons with equal/not equal
|
||||
pub fn fix_invalid_literal_comparison(locator: &SourceCodeLocator, location: Range) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&location);
|
||||
let mut tree = match_module(&module_text)?;
|
||||
let mut expr = match_expr(&mut tree)?;
|
||||
let cmp = match_comparison(expr)?;
|
||||
let target = cmp
|
||||
.comparisons
|
||||
.get(0)
|
||||
.ok_or_else(|| anyhow::anyhow!("Expected one ComparisonTarget"))?;
|
||||
|
||||
let new_operator = match &target.operator {
|
||||
CompOp::Is {
|
||||
whitespace_before: b,
|
||||
whitespace_after: a,
|
||||
} => Ok(CompOp::Equal {
|
||||
whitespace_before: b.clone(),
|
||||
whitespace_after: a.clone(),
|
||||
}),
|
||||
CompOp::IsNot {
|
||||
whitespace_before: b,
|
||||
whitespace_after: a,
|
||||
whitespace_between: _,
|
||||
} => Ok(CompOp::NotEqual {
|
||||
whitespace_before: b.clone(),
|
||||
whitespace_after: a.clone(),
|
||||
}),
|
||||
op => Err(anyhow::anyhow!(
|
||||
"Unexpected operator: {:?}. Expected Is or IsNot.",
|
||||
op
|
||||
)),
|
||||
}?;
|
||||
|
||||
expr.value = Expression::Comparison(Box::new(Comparison {
|
||||
left: cmp.left.clone(),
|
||||
comparisons: vec![ComparisonTarget {
|
||||
operator: new_operator,
|
||||
comparator: target.comparator.clone(),
|
||||
}],
|
||||
lpar: cmp.lpar.clone(),
|
||||
rpar: cmp.rpar.clone(),
|
||||
}));
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
location.location,
|
||||
location.end_location,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::pyflakes::checks;
|
||||
|
||||
/// F631
|
||||
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) = checks::assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::pyflakes::checks;
|
||||
|
||||
/// F634
|
||||
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) = checks::if_tuple(test, checker.locate_check(Range::from_located(stmt))) {
|
||||
checker.add_check(check);
|
||||
|
||||
62
src/pyflakes/plugins/invalid_literal_comparisons.rs
Normal file
62
src/pyflakes/plugins/invalid_literal_comparisons.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use itertools::izip;
|
||||
use log::error;
|
||||
use rustpython_ast::{Cmpop, Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pyflakes::fixes::fix_invalid_literal_comparison;
|
||||
|
||||
fn is_singleton(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
expr.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::None | Constant::Bool(_) | Constant::Ellipsis,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn is_constant(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Constant { .. } => true,
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// F632
|
||||
pub fn invalid_literal_comparison(
|
||||
checker: &mut Checker,
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
location: Range,
|
||||
) {
|
||||
let mut left = left;
|
||||
for (op, right) in izip!(ops, comparators) {
|
||||
if matches!(op, Cmpop::Is | Cmpop::IsNot)
|
||||
&& (is_constant_non_singleton(left) || is_constant_non_singleton(right))
|
||||
{
|
||||
let mut check = Check::new(CheckKind::IsLiteral, location);
|
||||
if checker.patch() {
|
||||
match fix_invalid_literal_comparison(
|
||||
checker.locator,
|
||||
Range {
|
||||
location: left.location,
|
||||
end_location: right.end_location.unwrap(),
|
||||
},
|
||||
) {
|
||||
Ok(fix) => check.amend(fix),
|
||||
Err(e) => error!("Failed to fix invalid comparison: {}", e),
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use crate::ast::types::{Binding, BindingKind, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// F633
|
||||
pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &left.node {
|
||||
if id == "print" {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub use assert_tuple::assert_tuple;
|
||||
pub use if_tuple::if_tuple;
|
||||
pub use invalid_literal_comparisons::invalid_literal_comparison;
|
||||
pub use invalid_print_syntax::invalid_print_syntax;
|
||||
pub use raise_not_implemented::raise_not_implemented;
|
||||
|
||||
mod assert_tuple;
|
||||
mod if_tuple;
|
||||
mod invalid_literal_comparisons;
|
||||
mod invalid_print_syntax;
|
||||
mod raise_not_implemented;
|
||||
|
||||
43
src/pyflakes/plugins/raise_not_implemented.rs
Normal file
43
src/pyflakes/plugins/raise_not_implemented.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn match_not_implemented(expr: &Expr) -> Option<&Expr> {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "NotImplemented" {
|
||||
return Some(func);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == "NotImplemented" {
|
||||
return Some(expr);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// F901
|
||||
pub fn raise_not_implemented(checker: &mut Checker, expr: &Expr) {
|
||||
if let Some(expr) = match_not_implemented(expr) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::RaiseNotImplemented,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
"NotImplementedError".to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,14 @@ pub fn remove_class_def_base(
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Fix> {
|
||||
let content = locator.slice_source_code_at(stmt_at);
|
||||
let contents = locator.slice_source_code_at(stmt_at);
|
||||
|
||||
// Case 1: `object` is the only base.
|
||||
if bases.len() == 1 && keywords.is_empty() {
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
let mut count: usize = 0;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
if count == 0 {
|
||||
fix_start = Some(helpers::to_absolute(&start, stmt_at));
|
||||
@@ -56,7 +56,7 @@ pub fn remove_class_def_base(
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
let mut seen_comma = false;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
let start = helpers::to_absolute(&start, stmt_at);
|
||||
if seen_comma {
|
||||
if matches!(tok, Tok::Newline) {
|
||||
@@ -83,7 +83,7 @@ pub fn remove_class_def_base(
|
||||
// isn't a comma.
|
||||
let mut fix_start: Option<Location> = None;
|
||||
let mut fix_end: Option<Location> = None;
|
||||
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
|
||||
for (start, tok, end) in lexer::make_tokenizer(&contents).flatten() {
|
||||
let start = helpers::to_absolute(&start, stmt_at);
|
||||
let end = helpers::to_absolute(&end, stmt_at);
|
||||
if start == expr_at {
|
||||
@@ -106,7 +106,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) {
|
||||
let mut tree = match libcst_native::parse_module(&contents, None) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
1657
src/rules/checks.rs
Normal file
1657
src/rules/checks.rs
Normal file
File diff suppressed because it is too large
Load Diff
3
src/rules/mod.rs
Normal file
3
src/rules/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Module for Ruff-specific rules.
|
||||
|
||||
pub mod checks;
|
||||
13
src/snapshots/ruff__linter__tests__B003_B003.py.snap
Normal file
13
src/snapshots/ruff__linter__tests__B003_B003.py.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: AssignmentToOsEnviron
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
125
src/snapshots/ruff__linter__tests__B008_B006_B008.py.snap
Normal file
125
src/snapshots/ruff__linter__tests__B008_B006_B008.py.snap
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 85
|
||||
column: 60
|
||||
end_location:
|
||||
row: 85
|
||||
column: 68
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 89
|
||||
column: 63
|
||||
end_location:
|
||||
row: 89
|
||||
column: 71
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 93
|
||||
column: 59
|
||||
end_location:
|
||||
row: 93
|
||||
column: 67
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 109
|
||||
column: 38
|
||||
end_location:
|
||||
row: 109
|
||||
column: 49
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 113
|
||||
column: 11
|
||||
end_location:
|
||||
row: 113
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 113
|
||||
column: 31
|
||||
end_location:
|
||||
row: 113
|
||||
column: 51
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 117
|
||||
column: 29
|
||||
end_location:
|
||||
row: 117
|
||||
column: 44
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 155
|
||||
column: 33
|
||||
end_location:
|
||||
row: 155
|
||||
column: 47
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 160
|
||||
column: 29
|
||||
end_location:
|
||||
row: 160
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 164
|
||||
column: 44
|
||||
end_location:
|
||||
row: 164
|
||||
column: 57
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 170
|
||||
column: 20
|
||||
end_location:
|
||||
row: 170
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 170
|
||||
column: 30
|
||||
end_location:
|
||||
row: 170
|
||||
column: 47
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 176
|
||||
column: 21
|
||||
end_location:
|
||||
row: 176
|
||||
column: 62
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 181
|
||||
column: 18
|
||||
end_location:
|
||||
row: 181
|
||||
column: 59
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
location:
|
||||
row: 181
|
||||
column: 36
|
||||
end_location:
|
||||
row: 181
|
||||
column: 53
|
||||
fix: ~
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__B015_B015.py.snap
Normal file
37
src/snapshots/ruff__linter__tests__B015_B015.py.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UselessComparison
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: UselessComparison
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: UselessComparison
|
||||
location:
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 17
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: UselessComparison
|
||||
location:
|
||||
row: 24
|
||||
column: 4
|
||||
end_location:
|
||||
row: 24
|
||||
column: 5
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__B016_B016.py.snap
Normal file
29
src/snapshots/ruff__linter__tests__B016_B016.py.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: CannotRaiseLiteral
|
||||
location:
|
||||
row: 6
|
||||
column: 6
|
||||
end_location:
|
||||
row: 6
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind: CannotRaiseLiteral
|
||||
location:
|
||||
row: 7
|
||||
column: 6
|
||||
end_location:
|
||||
row: 7
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: CannotRaiseLiteral
|
||||
location:
|
||||
row: 8
|
||||
column: 6
|
||||
end_location:
|
||||
row: 8
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
197
src/snapshots/ruff__linter__tests__B018_B018.py.snap
Normal file
197
src/snapshots/ruff__linter__tests__B018_B018.py.snap
Normal file
@@ -0,0 +1,197 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 12
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 13
|
||||
column: 4
|
||||
end_location:
|
||||
row: 13
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
end_location:
|
||||
row: 14
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 15
|
||||
column: 4
|
||||
end_location:
|
||||
row: 15
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 16
|
||||
column: 4
|
||||
end_location:
|
||||
row: 16
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 17
|
||||
column: 4
|
||||
end_location:
|
||||
row: 17
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 18
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 19
|
||||
column: 4
|
||||
end_location:
|
||||
row: 19
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 20
|
||||
column: 4
|
||||
end_location:
|
||||
row: 20
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 24
|
||||
column: 4
|
||||
end_location:
|
||||
row: 24
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 27
|
||||
column: 4
|
||||
end_location:
|
||||
row: 27
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 39
|
||||
column: 4
|
||||
end_location:
|
||||
row: 39
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 40
|
||||
column: 4
|
||||
end_location:
|
||||
row: 40
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 41
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 42
|
||||
column: 4
|
||||
end_location:
|
||||
row: 42
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 43
|
||||
column: 4
|
||||
end_location:
|
||||
row: 43
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 44
|
||||
column: 4
|
||||
end_location:
|
||||
row: 44
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 45
|
||||
column: 4
|
||||
end_location:
|
||||
row: 45
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 46
|
||||
column: 4
|
||||
end_location:
|
||||
row: 46
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 47
|
||||
column: 4
|
||||
end_location:
|
||||
row: 47
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 48
|
||||
column: 4
|
||||
end_location:
|
||||
row: 48
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 52
|
||||
column: 4
|
||||
end_location:
|
||||
row: 52
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: UselessExpression
|
||||
location:
|
||||
row: 55
|
||||
column: 4
|
||||
end_location:
|
||||
row: 55
|
||||
column: 5
|
||||
fix: ~
|
||||
|
||||
@@ -10,7 +10,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 19
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{1: 2}"
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 19
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: tuple
|
||||
location:
|
||||
@@ -19,7 +28,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 20
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{1: 2,}"
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 20
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: list
|
||||
location:
|
||||
@@ -28,7 +46,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{}"
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: tuple
|
||||
location:
|
||||
@@ -37,5 +64,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{}"
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -64,5 +64,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "{\"a\": 1}"
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -10,7 +10,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: list(x)
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
applied: false
|
||||
- kind:
|
||||
UnnecessaryComprehension: set
|
||||
location:
|
||||
@@ -19,5 +28,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: set(x)
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -13,4 +13,37 @@ expression: checks
|
||||
row: 5
|
||||
column: 123
|
||||
fix: ~
|
||||
- kind:
|
||||
LineTooLong:
|
||||
- 127
|
||||
- 88
|
||||
location:
|
||||
row: 25
|
||||
column: 0
|
||||
end_location:
|
||||
row: 25
|
||||
column: 127
|
||||
fix: ~
|
||||
- kind:
|
||||
LineTooLong:
|
||||
- 132
|
||||
- 88
|
||||
location:
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 40
|
||||
column: 132
|
||||
fix: ~
|
||||
- kind:
|
||||
LineTooLong:
|
||||
- 105
|
||||
- 88
|
||||
location:
|
||||
row: 43
|
||||
column: 0
|
||||
end_location:
|
||||
row: 43
|
||||
column: 105
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -9,7 +9,16 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: "x == \"abc\""
|
||||
location:
|
||||
row: 1
|
||||
column: 3
|
||||
end_location:
|
||||
row: 1
|
||||
column: 13
|
||||
applied: false
|
||||
- kind: IsLiteral
|
||||
location:
|
||||
row: 4
|
||||
@@ -17,5 +26,31 @@ expression: checks
|
||||
end_location:
|
||||
row: 4
|
||||
column: 15
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: 123 != y
|
||||
location:
|
||||
row: 4
|
||||
column: 3
|
||||
end_location:
|
||||
row: 4
|
||||
column: 15
|
||||
applied: false
|
||||
- kind: IsLiteral
|
||||
location:
|
||||
row: 7
|
||||
column: 9
|
||||
end_location:
|
||||
row: 7
|
||||
column: 17
|
||||
fix:
|
||||
patch:
|
||||
content: "\"123\" == x"
|
||||
location:
|
||||
row: 7
|
||||
column: 3
|
||||
end_location:
|
||||
row: 7
|
||||
column: 13
|
||||
applied: false
|
||||
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__F821_F821_3.py.snap
Normal file
23
src/snapshots/ruff__linter__tests__F821_F821_3.py.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: key
|
||||
location:
|
||||
row: 11
|
||||
column: 8
|
||||
end_location:
|
||||
row: 11
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: value
|
||||
location:
|
||||
row: 11
|
||||
column: 15
|
||||
end_location:
|
||||
row: 11
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
@@ -8,8 +8,17 @@ expression: checks
|
||||
column: 10
|
||||
end_location:
|
||||
row: 2
|
||||
column: 26
|
||||
fix: ~
|
||||
column: 24
|
||||
fix:
|
||||
patch:
|
||||
content: NotImplementedError
|
||||
location:
|
||||
row: 2
|
||||
column: 10
|
||||
end_location:
|
||||
row: 2
|
||||
column: 24
|
||||
applied: false
|
||||
- kind: RaiseNotImplemented
|
||||
location:
|
||||
row: 6
|
||||
@@ -17,5 +26,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 6
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
patch:
|
||||
content: NotImplementedError
|
||||
location:
|
||||
row: 6
|
||||
column: 10
|
||||
end_location:
|
||||
row: 6
|
||||
column: 24
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@ expression: checks
|
||||
InvalidArgumentName: A
|
||||
location:
|
||||
row: 1
|
||||
column: 12
|
||||
column: 15
|
||||
end_location:
|
||||
row: 1
|
||||
column: 13
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
InvalidArgumentName: A
|
||||
location:
|
||||
row: 6
|
||||
column: 24
|
||||
column: 27
|
||||
end_location:
|
||||
row: 6
|
||||
column: 25
|
||||
column: 28
|
||||
fix: ~
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user