Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4819e19ba2 | ||
|
|
622b8adb79 | ||
|
|
558d9fcbe3 | ||
|
|
83f18193c2 | ||
|
|
46e6a1b3be | ||
|
|
4d0d433af9 | ||
|
|
11f7532e72 | ||
|
|
417764d309 | ||
|
|
1e36c109c6 | ||
|
|
3960016d55 | ||
|
|
75d669fa86 | ||
|
|
20989e12ba | ||
|
|
5a1b6c32eb | ||
|
|
46bdcb9080 | ||
|
|
d16a7252af | ||
|
|
ca6551eb37 | ||
|
|
43a4f5749e | ||
|
|
6fef4db433 | ||
|
|
7470d6832f | ||
|
|
63ba0bfeef | ||
|
|
91666fcaf6 | ||
|
|
643e27221d | ||
|
|
c7349b69c1 | ||
|
|
7f84753f3c | ||
|
|
e2ec62cf33 | ||
|
|
1d5592d937 | ||
|
|
886def13bd | ||
|
|
949e4d4077 | ||
|
|
c8cb2eead2 | ||
|
|
02ae494a0e | ||
|
|
dce86e065b | ||
|
|
d77979429c | ||
|
|
a3a15d2eb2 | ||
|
|
5af95428ff | ||
|
|
6338cad4e6 | ||
|
|
485881877f | ||
|
|
b8f517c70e | ||
|
|
9f601c2abd | ||
|
|
c0ce0b0c48 | ||
|
|
e5b16973a9 | ||
|
|
de9ceb2fe1 | ||
|
|
38b19b78b7 | ||
|
|
7043e15b57 | ||
|
|
9594079235 | ||
|
|
732f208e47 | ||
|
|
32e62d9209 | ||
|
|
d9e4b0cdc1 | ||
|
|
36fcfad56a | ||
|
|
65d29d9734 | ||
|
|
1e171ce0e8 | ||
|
|
2bdc500c61 | ||
|
|
f453e429b6 | ||
|
|
73874f4788 | ||
|
|
8846dcdf6a | ||
|
|
d827e6e36a | ||
|
|
71d9b2ac5f | ||
|
|
401b53cc45 | ||
|
|
aa9c1e255c | ||
|
|
f7fc702b2c | ||
|
|
50ca0d7d0a | ||
|
|
65e0284698 | ||
|
|
e4f571ea61 | ||
|
|
4ed88dd245 | ||
|
|
09b926fd59 | ||
|
|
a4869e4974 | ||
|
|
f53c4fc221 | ||
|
|
3892a49a97 | ||
|
|
27cc7e236c | ||
|
|
fa0954fe47 | ||
|
|
a0b50d7ebc | ||
|
|
afe7a04211 | ||
|
|
14806c62ca | ||
|
|
0d0c8730fa | ||
|
|
cf6a23b83c | ||
|
|
9e0daac561 | ||
|
|
f2fd7335ce | ||
|
|
b8f878df5e | ||
|
|
9bdb922c75 | ||
|
|
edecc1bba6 | ||
|
|
8e903153f6 | ||
|
|
3937885f37 | ||
|
|
24de97d951 |
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# Check http://editorconfig.org for more information
|
||||
# This is the main config file for this project:
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py}]
|
||||
indent_size = 4
|
||||
@@ -1,5 +1,10 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.39
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.40
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.10.1
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
- id: lint
|
||||
name: ruff lint
|
||||
description: Run ruff to lint Python files.
|
||||
entry: ruff
|
||||
language: python
|
||||
types_or: [python]
|
||||
pass_filenames: true
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
charlie.r.marsh@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
105
CONTRIBUTING.md
Normal file
105
CONTRIBUTING.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Contributing 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
|
||||
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
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ruff locally with:
|
||||
|
||||
```shell
|
||||
cargo run resources/test/fixtures --no-cache
|
||||
```
|
||||
|
||||
Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes
|
||||
both the lint and test validation checks:
|
||||
|
||||
```shell
|
||||
cargo fmt # Auto-formatting...
|
||||
cargo clippy # Linting...
|
||||
cargo test # Testing...
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
|
||||
will save you time and expedite the merge process.
|
||||
|
||||
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
There are three 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).
|
||||
3. Add a test fixture.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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 satisified 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:
|
||||
|
||||
```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(())
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Example: Adding a new configuration option
|
||||
|
||||
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.
|
||||
|
||||
Ultimately, these two sources of configuration are merged into the `Settings` struct defined
|
||||
in `src/settings.rs`, which is then threaded through the codebase.
|
||||
|
||||
To add a new configuration option, you'll likely want to _both_ add a CLI option to `src/main.rs`
|
||||
_and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern-match against an
|
||||
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
|
||||
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
|
||||
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,
|
||||
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
|
||||
117
Cargo.lock
generated
117
Cargo.lock
generated
@@ -37,6 +37,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.60"
|
||||
@@ -359,6 +365,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chic"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.21"
|
||||
@@ -382,26 +397,24 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.16"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9"
|
||||
checksum = "dd03107d0f87139c1774a15f3db2165b0652b5460c58c27e561f89c20c599eaf"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.15"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4"
|
||||
checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
@@ -412,9 +425,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
@@ -1064,9 +1077,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -1155,6 +1168,30 @@ version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"itertools",
|
||||
"libcst_derive",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"peg",
|
||||
"regex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -1381,9 +1418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.1"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -1432,6 +1469,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.0.13"
|
||||
@@ -1450,6 +1493,33 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
@@ -1801,7 +1871,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.39"
|
||||
version = "0.0.50"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1817,6 +1887,7 @@ dependencies = [
|
||||
"glob",
|
||||
"insta",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
"notify",
|
||||
"once_cell",
|
||||
@@ -1846,7 +1917,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -1855,7 +1926,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -1872,7 +1943,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=7d21c6923a506e79cc041708d83cef925efd33f4#7d21c6923a506e79cc041708d83cef925efd33f4"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2187,26 +2258,20 @@ dependencies = [
|
||||
"phf_codegen 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.39"
|
||||
version = "0.0.50"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -11,22 +11,23 @@ anyhow = { version = "1.0.60" }
|
||||
bincode = { version = "1.3.3" }
|
||||
cacache = { version = "10.0.1" }
|
||||
chrono = { version = "0.4.21" }
|
||||
clap = { version = "3.2.16", features = ["derive"] }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
clearscreen = { version = "1.0.10" }
|
||||
colored = { version = "2.0.0" }
|
||||
common-path = { version = "1.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = "0.3.0"
|
||||
itertools = "0.10.3"
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" }
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "4.0.17" }
|
||||
once_cell = { version = "1.13.1" }
|
||||
path-absolutize = "3.0.13"
|
||||
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
|
||||
367
README.md
367
README.md
@@ -1,7 +1,9 @@
|
||||
# ruff
|
||||
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://pypi.python.org/pypi/ruff)
|
||||
[](https://github.com/charliermarsh/ruff/actions)
|
||||
[](https://badge.fury.io/py/ruff)
|
||||
|
||||
An extremely fast Python linter, written in Rust.
|
||||
|
||||
@@ -18,11 +20,9 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.10 compatibility
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
|
||||
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired `--fix` support
|
||||
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired `--fix` support
|
||||
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support
|
||||
|
||||
_ruff is a proof-of-concept and not yet intended for production use. It supports only a small subset
|
||||
of the Flake8 rules, and may crash on your codebase._
|
||||
- ⚖️ [Near-complete parity](#Parity-with-Flake8) with the built-in Flake8 rule set
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -52,14 +52,14 @@ You can run ruff in `--watch` mode to automatically re-run on-change:
|
||||
ruff path/to/code/ --watch
|
||||
```
|
||||
|
||||
ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on system):
|
||||
ruff also works with [pre-commit](https://pre-commit.com):
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.39
|
||||
hooks:
|
||||
- id: lint
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.48
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -80,87 +80,139 @@ select = [
|
||||
Alternatively, on the command-line:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/ --select F401 F403
|
||||
ruff path/to/code/ --select F401 --select F403
|
||||
```
|
||||
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.39)
|
||||
An extremely fast Python linter.
|
||||
ruff: An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
ruff [OPTIONS] <FILES>...
|
||||
Usage: ruff [OPTIONS] <FILES>...
|
||||
|
||||
ARGS:
|
||||
<FILES>...
|
||||
Arguments:
|
||||
<FILES>...
|
||||
|
||||
OPTIONS:
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
--exclude <EXCLUDE>...
|
||||
List of paths, used to exclude files and/or directories from checks
|
||||
--extend-exclude <EXTEND_EXCLUDE>...
|
||||
Like --exclude, but adds additional files and directories on top of the excluded ones
|
||||
-f, --fix
|
||||
Attempt to automatically fix lint errors
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [default: text] [possible values: text,
|
||||
json]
|
||||
-h, --help
|
||||
Print help information
|
||||
--ignore <IGNORE>...
|
||||
List of error codes to ignore
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
-q, --quiet
|
||||
Disable all logging (but still exit with status code "1" upon detecting errors)
|
||||
--select <SELECT>...
|
||||
List of error codes to enable
|
||||
-v, --verbose
|
||||
Enable verbose logging
|
||||
-w, --watch
|
||||
Run in watch mode by re-running whenever files change
|
||||
Options:
|
||||
-v, --verbose
|
||||
Enable verbose logging
|
||||
-q, --quiet
|
||||
Disable all logging (but still exit with status code "1" upon detecting errors)
|
||||
-e, --exit-zero
|
||||
Exit with status code "0", even upon detecting errors
|
||||
-w, --watch
|
||||
Run in watch mode by re-running whenever files change
|
||||
-f, --fix
|
||||
Attempt to automatically fix lint errors
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--select <SELECT>
|
||||
List of error codes to enable
|
||||
--extend-select <EXTEND_SELECT>
|
||||
Like --select, but adds additional error codes on top of the selected ones
|
||||
--ignore <IGNORE>
|
||||
List of error codes to ignore
|
||||
--extend-ignore <EXTEND_IGNORE>
|
||||
Like --ignore, but adds additional error codes on top of the ignored ones
|
||||
--exclude <EXCLUDE>
|
||||
List of paths, used to exclude files and/or directories from checks
|
||||
--extend-exclude <EXTEND_EXCLUDE>
|
||||
Like --exclude, but adds additional files and directories on top of the excluded ones
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [default: text] [possible values: text, json]
|
||||
--show-files
|
||||
See the files ruff will be run against with the current settings
|
||||
--show-settings
|
||||
See ruff's settings
|
||||
--add-noqa
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
Regular expression matching the name of dummy variables
|
||||
-h, --help
|
||||
Print help information
|
||||
-V, --version
|
||||
Print version information
|
||||
```
|
||||
|
||||
### Excluding files
|
||||
|
||||
Exclusions are based on globs, and can be either:
|
||||
|
||||
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
|
||||
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
|
||||
`foo_*.py` ).
|
||||
- Relative patterns, like `./directory/foo.py` (to exclude that specific file) or `./directory/*.py`
|
||||
(to exclude any Python files in `./directory`). Note that these paths are relative to the
|
||||
directory from which you execute `ruff`, and _not_ the directory of the `pyproject.toml`.
|
||||
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
|
||||
(to exclude any Python files in `directory`). Note that these paths are relative to the
|
||||
project root (e.g., the directory containing your `pyproject.toml`).
|
||||
|
||||
### Ignoring errors
|
||||
|
||||
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
|
||||
either on the command-line or in your `project.toml` file.
|
||||
|
||||
To ignore an error in-line, ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
|
||||
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
|
||||
|
||||
```python
|
||||
# Ignore F841.
|
||||
x = 1 # noqa: F841
|
||||
|
||||
# Ignore E741 and F841.
|
||||
i = 1 # noqa: E741, F841
|
||||
|
||||
# Ignore _all_ errors.
|
||||
x = 1 # noqa
|
||||
```
|
||||
|
||||
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
|
||||
will apply to the entire body, like so:
|
||||
|
||||
```python
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
```
|
||||
|
||||
ruff supports several (experimental) workflows to aid in `noqa` management.
|
||||
|
||||
First, ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
|
||||
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
|
||||
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
|
||||
directives.**
|
||||
|
||||
Second, ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
|
||||
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
|
||||
`noqa` directives.**
|
||||
|
||||
Third, ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
|
||||
migrating a new codebase to ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
|
||||
add `noqa` directives to all failing lines, with the appropriate error codes.**
|
||||
|
||||
### Compatibility with Black
|
||||
|
||||
ruff is intended to be compatible with [Black](https://github.com/psf/black), and should be
|
||||
compatible out-of-the-box as long as the `line-length` setting is consistent between the two.
|
||||
ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
|
||||
the `line-length` setting is consistent between the two.
|
||||
|
||||
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing
|
||||
lint rules that are obviated by Black (e.g., stylistic rules).
|
||||
stylistic lint rules that are obviated by autoformatting.
|
||||
|
||||
### Parity with Flake8
|
||||
|
||||
ruff's goal is to achieve feature-parity with Flake8 when used (1) without any plugins,
|
||||
(2) alongside Black, and (3) on Python 3 code. (Using Black obviates the need for many of Flake8's
|
||||
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
||||
ruff's goal is to achieve feature-parity with Flake8 when used (1) without plugins, (2) alongside
|
||||
Black, and (3) on Python 3 code.
|
||||
|
||||
Under those conditions, Flake8 implements about 60 rules, give or take. At time of writing, ruff
|
||||
implements 42 rules. (Note that these 42 rules likely cover a disproportionate share of errors:
|
||||
unused imports, undefined variables, etc.)
|
||||
|
||||
The unimplemented rules are tracked in #170, and include:
|
||||
|
||||
- 14 rules related to string `.format` calls.
|
||||
- 4 logical rules.
|
||||
- 1 rule related to parsing.
|
||||
**Under those conditions, ruff implements 44 out of 60 rules.** (ruff is missing: 14 rules related
|
||||
to string `.format` calls, 1 rule related to docstring parsing, and 1 rule related to redefined
|
||||
variables.)
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
1. Flake8 supports a wider range of `noqa` patterns, such as per-file ignores defined in `.flake8`.
|
||||
2. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
3. ruff does not yet support parenthesized context managers.
|
||||
1. Flake8 has a plugin architecture and supports writing custom lint rules.
|
||||
2. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
|
||||
pattern matching and parenthesized context managers.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -178,11 +230,13 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
|
||||
| E742 | AmbiguousClassName | ambiguous class name '...' |
|
||||
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
|
||||
| E902 | IOError | No such file or directory: `...` |
|
||||
| E902 | IOError | ... |
|
||||
| E999 | SyntaxError | SyntaxError: ... |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | `from ... import *` used; unable to detect undefined names |
|
||||
| F402 | ImportShadowedByLoopVar | import '...' from line 1 shadowed by loop variable |
|
||||
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names |
|
||||
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
|
||||
| F405 | ImportStarUsage | '...' may be undefined, or defined from star imports: ... |
|
||||
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
|
||||
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
@@ -206,8 +260,51 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| 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` |
|
||||
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin |
|
||||
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin |
|
||||
| A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin |
|
||||
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive |
|
||||
|
||||
## Integrations
|
||||
|
||||
### PyCharm
|
||||
|
||||
ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
|
||||
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
|
||||
add a new tool with the following configuration:
|
||||
|
||||

|
||||
|
||||
ruff should then appear as a runnable action:
|
||||
|
||||

|
||||
|
||||
### GitHub Actions
|
||||
|
||||
GitHub Actions has everything you need to run ruff out-of-the-box:
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
- name: Run ruff
|
||||
run: ruff .
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
@@ -243,29 +340,29 @@ Add this `pyproject.toml` to the CPython directory:
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
exclude = [
|
||||
"./resources/test/cpython/Lib/lib2to3/tests/data/bom.py",
|
||||
"./resources/test/cpython/Lib/lib2to3/tests/data/crlf.py",
|
||||
"./resources/test/cpython/Lib/lib2to3/tests/data/different_encoding.py",
|
||||
"./resources/test/cpython/Lib/lib2to3/tests/data/false_encoding.py",
|
||||
"./resources/test/cpython/Lib/lib2to3/tests/data/py2_test_grammar.py",
|
||||
"./resources/test/cpython/Lib/test/bad_coding2.py",
|
||||
"./resources/test/cpython/Lib/test/badsyntax_3131.py",
|
||||
"./resources/test/cpython/Lib/test/badsyntax_pep3120.py",
|
||||
"./resources/test/cpython/Lib/test/encoded_modules/module_iso_8859_1.py",
|
||||
"./resources/test/cpython/Lib/test/encoded_modules/module_koi8_r.py",
|
||||
"./resources/test/cpython/Lib/test/test_fstring.py",
|
||||
"./resources/test/cpython/Lib/test/test_grammar.py",
|
||||
"./resources/test/cpython/Lib/test/test_importlib/test_util.py",
|
||||
"./resources/test/cpython/Lib/test/test_named_expressions.py",
|
||||
"./resources/test/cpython/Lib/test/test_patma.py",
|
||||
"./resources/test/cpython/Lib/test/test_source_encoding.py",
|
||||
"./resources/test/cpython/Tools/c-analyzer/c_parser/parser/_delim.py",
|
||||
"./resources/test/cpython/Tools/i18n/pygettext.py",
|
||||
"./resources/test/cpython/Tools/test2to3/maintest.py",
|
||||
"./resources/test/cpython/Tools/test2to3/setup.py",
|
||||
"./resources/test/cpython/Tools/test2to3/test/test_foo.py",
|
||||
"./resources/test/cpython/Tools/test2to3/test2to3/hello.py",
|
||||
extend-exclude = [
|
||||
"Lib/lib2to3/tests/data/bom.py",
|
||||
"Lib/lib2to3/tests/data/crlf.py",
|
||||
"Lib/lib2to3/tests/data/different_encoding.py",
|
||||
"Lib/lib2to3/tests/data/false_encoding.py",
|
||||
"Lib/lib2to3/tests/data/py2_test_grammar.py",
|
||||
"Lib/test/bad_coding2.py",
|
||||
"Lib/test/badsyntax_3131.py",
|
||||
"Lib/test/badsyntax_pep3120.py",
|
||||
"Lib/test/encoded_modules/module_iso_8859_1.py",
|
||||
"Lib/test/encoded_modules/module_koi8_r.py",
|
||||
"Lib/test/test_fstring.py",
|
||||
"Lib/test/test_grammar.py",
|
||||
"Lib/test/test_importlib/test_util.py",
|
||||
"Lib/test/test_named_expressions.py",
|
||||
"Lib/test/test_patma.py",
|
||||
"Lib/test/test_source_encoding.py",
|
||||
"Tools/c-analyzer/c_parser/parser/_delim.py",
|
||||
"Tools/i18n/pygettext.py",
|
||||
"Tools/test2to3/maintest.py",
|
||||
"Tools/test2to3/setup.py",
|
||||
"Tools/test2to3/test/test_foo.py",
|
||||
"Tools/test2to3/test2to3/hello.py",
|
||||
]
|
||||
```
|
||||
|
||||
@@ -274,17 +371,21 @@ Next, to benchmark the release build:
|
||||
```shell
|
||||
cargo build --release
|
||||
|
||||
hyperfine --ignore-failure --warmup 1 \
|
||||
hyperfine --ignore-failure --warmup 10 --runs 100 \
|
||||
"./target/release/ruff ./resources/test/cpython/ --no-cache" \
|
||||
"./target/release/ruff ./resources/test/cpython/"
|
||||
|
||||
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
|
||||
Time (mean ± σ): 353.6 ms ± 7.6 ms [User: 2868.8 ms, System: 171.5 ms]
|
||||
Range (min … max): 344.4 ms … 367.3 ms 10 runs
|
||||
Time (mean ± σ): 297.4 ms ± 4.9 ms [User: 2460.0 ms, System: 67.2 ms]
|
||||
Range (min … max): 287.7 ms … 312.1 ms 100 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 2: ./target/release/ruff ./resources/test/cpython/
|
||||
Time (mean ± σ): 59.6 ms ± 2.5 ms [User: 36.4 ms, System: 345.6 ms]
|
||||
Range (min … max): 55.9 ms … 67.0 ms 48 runs
|
||||
Time (mean ± σ): 79.6 ms ± 7.3 ms [User: 59.7 ms, System: 356.1 ms]
|
||||
Range (min … max): 62.4 ms … 111.2 ms 100 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
```
|
||||
|
||||
To benchmark against the ecosystem's existing tools:
|
||||
@@ -296,11 +397,8 @@ hyperfine --ignore-failure --warmup 5 \
|
||||
"pyflakes resources/test/cpython" \
|
||||
"autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
|
||||
"pycodestyle resources/test/cpython" \
|
||||
"pycodestyle --select E501 resources/test/cpython" \
|
||||
"flake8 resources/test/cpython" \
|
||||
"flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython" \
|
||||
"python -m scripts.run_flake8 resources/test/cpython" \
|
||||
"python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501"
|
||||
"python -m scripts.run_flake8 resources/test/cpython"
|
||||
```
|
||||
|
||||
In order, these evaluate:
|
||||
@@ -310,68 +408,65 @@ In order, these evaluate:
|
||||
- PyFlakes
|
||||
- autoflake
|
||||
- pycodestyle
|
||||
- pycodestyle, limited to the checks supported by ruff
|
||||
- Flake8
|
||||
- Flake8, limited to the checks supported by ruff
|
||||
- Flake8, with a hack to enable multiprocessing on macOS
|
||||
- Flake8, with a hack to enable multiprocessing on macOS, limited to the checks supported by ruff
|
||||
|
||||
(You can `poetry install` from `./scripts` to create a working environment for the above.)
|
||||
|
||||
```shell
|
||||
Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
|
||||
Time (mean ± σ): 469.3 ms ± 16.3 ms [User: 2663.0 ms, System: 972.5 ms]
|
||||
Range (min … max): 445.2 ms … 494.8 ms 10 runs
|
||||
Time (mean ± σ): 297.9 ms ± 7.0 ms [User: 2436.6 ms, System: 65.9 ms]
|
||||
Range (min … max): 289.9 ms … 314.6 ms 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 2: pylint --recursive=y resources/test/cpython/
|
||||
Time (mean ± σ): 27.211 s ± 0.097 s [User: 26.405 s, System: 0.799 s]
|
||||
Range (min … max): 27.056 s … 27.349 s 10 runs
|
||||
Time (mean ± σ): 37.634 s ± 0.225 s [User: 36.728 s, System: 0.853 s]
|
||||
Range (min … max): 37.201 s … 38.106 s 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 3: pyflakes resources/test/cpython
|
||||
Time (mean ± σ): 27.309 s ± 0.033 s [User: 27.137 s, System: 0.169 s]
|
||||
Range (min … max): 27.267 s … 27.372 s 10 runs
|
||||
Time (mean ± σ): 40.950 s ± 0.449 s [User: 40.688 s, System: 0.229 s]
|
||||
Range (min … max): 40.348 s … 41.671 s 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 4: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
|
||||
Time (mean ± σ): 8.027 s ± 0.024 s [User: 74.255 s, System: 0.953 s]
|
||||
Range (min … max): 7.969 s … 8.052 s 10 runs
|
||||
Time (mean ± σ): 11.562 s ± 0.160 s [User: 107.022 s, System: 1.143 s]
|
||||
Range (min … max): 11.417 s … 11.917 s 10 runs
|
||||
|
||||
Benchmark 5: pycodestyle resources/test/cpython
|
||||
Time (mean ± σ): 41.666 s ± 0.266 s [User: 41.531 s, System: 0.132 s]
|
||||
Range (min … max): 41.295 s … 41.980 s 10 runs
|
||||
Time (mean ± σ): 67.428 s ± 0.985 s [User: 67.199 s, System: 0.203 s]
|
||||
Range (min … max): 65.313 s … 68.496 s 10 runs
|
||||
|
||||
Benchmark 6: pycodestyle --select E501 resources/test/cpython
|
||||
Time (mean ± σ): 14.547 s ± 0.077 s [User: 14.466 s, System: 0.079 s]
|
||||
Range (min … max): 14.429 s … 14.695 s 10 runs
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 7: flake8 resources/test/cpython
|
||||
Time (mean ± σ): 75.700 s ± 0.152 s [User: 75.254 s, System: 0.440 s]
|
||||
Range (min … max): 75.513 s … 76.014 s 10 runs
|
||||
Benchmark 6: flake8 resources/test/cpython
|
||||
Time (mean ± σ): 116.099 s ± 1.178 s [User: 115.217 s, System: 0.845 s]
|
||||
Range (min … max): 114.180 s … 117.724 s 10 runs
|
||||
|
||||
Benchmark 8: flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython
|
||||
Time (mean ± σ): 75.122 s ± 0.532 s [User: 74.677 s, System: 0.440 s]
|
||||
Range (min … max): 74.130 s … 75.606 s 10 runs
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 9: python -m scripts.run_flake8 resources/test/cpython
|
||||
Time (mean ± σ): 12.794 s ± 0.147 s [User: 90.792 s, System: 0.738 s]
|
||||
Range (min … max): 12.606 s … 13.030 s 10 runs
|
||||
|
||||
Benchmark 10: python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501
|
||||
Time (mean ± σ): 12.487 s ± 0.118 s [User: 90.052 s, System: 0.714 s]
|
||||
Range (min … max): 12.265 s … 12.665 s 10 runs
|
||||
Benchmark 7: python -m scripts.run_flake8 resources/test/cpython
|
||||
Time (mean ± σ): 20.477 s ± 0.349 s [User: 142.372 s, System: 1.504 s]
|
||||
Range (min … max): 20.107 s … 21.183 s 10 runs
|
||||
|
||||
Summary
|
||||
'./target/release/ruff ./resources/test/cpython/ --no-cache' ran
|
||||
17.10 ± 0.60 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
|
||||
26.60 ± 0.96 times faster than 'python -m scripts.run_flake8 resources/test/cpython --select=F831,F541,F634,F403,F706,F901,E501'
|
||||
27.26 ± 1.00 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
|
||||
30.99 ± 1.09 times faster than 'pycodestyle --select E501 resources/test/cpython'
|
||||
57.98 ± 2.03 times faster than 'pylint --recursive=y resources/test/cpython/'
|
||||
58.19 ± 2.02 times faster than 'pyflakes resources/test/cpython'
|
||||
88.77 ± 3.14 times faster than 'pycodestyle resources/test/cpython'
|
||||
160.06 ± 5.68 times faster than 'flake8 --select=F831,F541,F634,F403,F706,F901,E501 resources/test/cpython'
|
||||
161.29 ± 5.61 times faster than 'flake8 resources/test/cpython'
|
||||
38.81 ± 1.05 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
|
||||
68.74 ± 1.99 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
|
||||
126.33 ± 3.05 times faster than 'pylint --recursive=y resources/test/cpython/'
|
||||
137.46 ± 3.55 times faster than 'pyflakes resources/test/cpython'
|
||||
226.35 ± 6.23 times faster than 'pycodestyle resources/test/cpython'
|
||||
389.73 ± 9.92 times faster than 'flake8 resources/test/cpython'
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome and hugely appreciated. To get started, check out the
|
||||
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTORS.md).
|
||||
|
||||
@@ -1,56 +1,14 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use ruff::checks::{CheckKind, RejectedCmpop};
|
||||
use ruff::checks::{CheckCode, ALL_CHECK_CODES};
|
||||
|
||||
fn main() {
|
||||
let mut check_kinds: Vec<CheckKind> = vec![
|
||||
CheckKind::AmbiguousClassName("...".to_string()),
|
||||
CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckKind::AmbiguousVariableName("...".to_string()),
|
||||
CheckKind::AssertTuple,
|
||||
CheckKind::BreakOutsideLoop,
|
||||
CheckKind::ContinueOutsideLoop,
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
CheckKind::DoNotAssignLambda,
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
|
||||
CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckKind::IOError("...".to_string()),
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckKind::ImportStarUsage("...".to_string()),
|
||||
CheckKind::InvalidPrintSyntax,
|
||||
CheckKind::IsLiteral,
|
||||
CheckKind::LateFutureImport,
|
||||
CheckKind::LineTooLong(89, 88),
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
|
||||
CheckKind::NoAssertEquals,
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
CheckKind::NotInTest,
|
||||
CheckKind::NotIsTest,
|
||||
CheckKind::RaiseNotImplemented,
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
CheckKind::SyntaxError("...".to_string()),
|
||||
CheckKind::TooManyExpressionsInStarredAssignment,
|
||||
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
CheckKind::TwoStarredExpressions,
|
||||
CheckKind::TypeComparison,
|
||||
CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckKind::UndefinedName("...".to_string()),
|
||||
CheckKind::UnusedImport("...".to_string()),
|
||||
CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckKind::YieldOutsideFunction,
|
||||
];
|
||||
check_kinds.sort_by_key(|check_kind| check_kind.code());
|
||||
let mut check_codes: Vec<CheckCode> = ALL_CHECK_CODES.to_vec();
|
||||
check_codes.sort();
|
||||
|
||||
println!("| Code | Name | Message |");
|
||||
println!("| ---- | ----- | ------- |");
|
||||
for check_kind in check_kinds {
|
||||
for check_code in check_codes {
|
||||
let check_kind = check_code.kind();
|
||||
println!(
|
||||
"| {} | {} | {} |",
|
||||
check_kind.code().as_str(),
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use clap::Parser;
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use clap::Parser;
|
||||
use rustpython_parser::lexer;
|
||||
|
||||
use ruff::fs;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
|
||||
#[arg(required = true)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
|
||||
27
resources/test/fixtures/A001.py
vendored
Normal file
27
resources/test/fixtures/A001.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import some as sum
|
||||
from some import other as int
|
||||
|
||||
print = 1
|
||||
copyright: 'annotation' = 2
|
||||
(complex := 3)
|
||||
float = object = 4
|
||||
min, max = 5, 6
|
||||
|
||||
def bytes():
|
||||
pass
|
||||
|
||||
class slice:
|
||||
pass
|
||||
|
||||
try:
|
||||
...
|
||||
except ImportError as ValueError:
|
||||
...
|
||||
|
||||
for memoryview, *bytearray in []:
|
||||
pass
|
||||
|
||||
with open('file') as str, open('file2') as (all, any):
|
||||
pass
|
||||
|
||||
[0 for sum in ()]
|
||||
9
resources/test/fixtures/A002.py
vendored
Normal file
9
resources/test/fixtures/A002.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
def func1(str, /, type, *complex, Exception, **getattr):
|
||||
pass
|
||||
|
||||
|
||||
async def func2(bytes):
|
||||
pass
|
||||
|
||||
|
||||
map([], lambda float: ...)
|
||||
8
resources/test/fixtures/A003.py
vendored
Normal file
8
resources/test/fixtures/A003.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
class MyClass:
|
||||
ImportError = 4
|
||||
|
||||
def __init__(self):
|
||||
self.float = 5 # is fine
|
||||
|
||||
def str(self):
|
||||
pass
|
||||
4
resources/test/fixtures/E402.py
vendored
4
resources/test/fixtures/E402.py
vendored
@@ -1,4 +1,8 @@
|
||||
"""Top-level docstring."""
|
||||
|
||||
__all__ = ["y"]
|
||||
__version__: str = "0.1.0"
|
||||
|
||||
import a
|
||||
|
||||
try:
|
||||
|
||||
10
resources/test/fixtures/E501.py
vendored
10
resources/test/fixtures/E501.py
vendored
@@ -4,3 +4,13 @@
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
_ = "---------------------------------------------------------------------------AAAAAAA"
|
||||
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
|
||||
|
||||
9
resources/test/fixtures/F402.py
vendored
Normal file
9
resources/test/fixtures/F402.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
import os.path as path
|
||||
|
||||
|
||||
for os in range(3):
|
||||
pass
|
||||
|
||||
for path in range(3):
|
||||
pass
|
||||
11
resources/test/fixtures/F405.py
vendored
Normal file
11
resources/test/fixtures/F405.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from mymodule import *
|
||||
|
||||
|
||||
def print_name():
|
||||
print(name)
|
||||
|
||||
|
||||
def print_name(name):
|
||||
print(name)
|
||||
|
||||
__all__ = ['a']
|
||||
7
resources/test/fixtures/F821.py
vendored
7
resources/test/fixtures/F821.py
vendored
@@ -82,3 +82,10 @@ class Ticket:
|
||||
def update_tomato():
|
||||
print(TOMATO)
|
||||
TOMATO = "cherry tomato"
|
||||
|
||||
|
||||
A = f'{B}'
|
||||
A = (
|
||||
f'B'
|
||||
f'{B}'
|
||||
)
|
||||
|
||||
17
resources/test/fixtures/F841.py
vendored
17
resources/test/fixtures/F841.py
vendored
@@ -10,15 +10,28 @@ except ValueError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def f():
|
||||
def f1():
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + y
|
||||
|
||||
|
||||
def g():
|
||||
def f2():
|
||||
foo = (1, 2)
|
||||
(a, b) = (1, 2)
|
||||
|
||||
bar = (1, 2)
|
||||
(c, d) = bar
|
||||
|
||||
(x, y) = baz = bar
|
||||
|
||||
|
||||
def f3():
|
||||
locals()
|
||||
x = 1
|
||||
|
||||
|
||||
def f4():
|
||||
_ = 1
|
||||
__ = 1
|
||||
_discarded = 1
|
||||
|
||||
57
resources/test/fixtures/M001.py
vendored
Normal file
57
resources/test/fixtures/M001.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
def f() -> None:
|
||||
# Valid
|
||||
a = 1 # noqa
|
||||
|
||||
# Valid
|
||||
b = 2 # noqa: F841
|
||||
|
||||
# Invalid
|
||||
c = 1 # noqa
|
||||
print(c)
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: E501
|
||||
|
||||
# Invalid
|
||||
d = 1 # noqa: F841, E501
|
||||
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
""" # noqa: E501, F841
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa: E501
|
||||
|
||||
# Invalid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
""" # noqa
|
||||
22
resources/test/fixtures/SPR001.py
vendored
Normal file
22
resources/test/fixtures/SPR001.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
class Parent:
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
def wrong(self):
|
||||
pass
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
def method(self):
|
||||
parent = super() # ok
|
||||
super().method() # ok
|
||||
Parent.method(self) # ok
|
||||
Parent.super(1, 2) # ok
|
||||
|
||||
def wrong(self):
|
||||
parent = super(Child, self) # wrong
|
||||
super(Child, self).method # wrong
|
||||
super(
|
||||
Child,
|
||||
self,
|
||||
).method() # wrong
|
||||
46
resources/test/fixtures/pyproject.toml
vendored
46
resources/test/fixtures/pyproject.toml
vendored
@@ -3,49 +3,5 @@ line-length = 88
|
||||
extend-exclude = [
|
||||
"excluded.py",
|
||||
"migrations",
|
||||
"./resources/test/fixtures/directory/also_excluded.py",
|
||||
]
|
||||
select = [
|
||||
"E402",
|
||||
"E501",
|
||||
"E711",
|
||||
"E712",
|
||||
"E713",
|
||||
"E714",
|
||||
"E721",
|
||||
"E722",
|
||||
"E731",
|
||||
"E741",
|
||||
"E742",
|
||||
"E743",
|
||||
"E902",
|
||||
"E999",
|
||||
"F401",
|
||||
"F403",
|
||||
"F404",
|
||||
"F406",
|
||||
"F407",
|
||||
"F541",
|
||||
"F601",
|
||||
"F602",
|
||||
"F621",
|
||||
"F622",
|
||||
"F631",
|
||||
"F632",
|
||||
"F633",
|
||||
"F634",
|
||||
"F701",
|
||||
"F702",
|
||||
"F704",
|
||||
"F706",
|
||||
"F707",
|
||||
"F722",
|
||||
"F821",
|
||||
"F822",
|
||||
"F823",
|
||||
"F831",
|
||||
"F841",
|
||||
"F901",
|
||||
"R001",
|
||||
"R002",
|
||||
"directory/also_excluded.py",
|
||||
]
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::izip;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
|
||||
Location, Stmt, StmtKind, Unaryop,
|
||||
};
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::{Binding, BindingKind, Scope};
|
||||
use crate::ast::types::{
|
||||
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
|
||||
};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
|
||||
use crate::python::builtins::BUILTINS;
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
pub fn check_if_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
pub fn check_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));
|
||||
@@ -22,7 +26,7 @@ pub fn check_if_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
}
|
||||
|
||||
/// Check AssertTuple compliance.
|
||||
pub fn check_assert_tuple(test: &Expr, location: Location) -> Option<Check> {
|
||||
pub fn check_assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::AssertTuple, location));
|
||||
@@ -37,6 +41,7 @@ pub fn check_not_tests(
|
||||
operand: &Expr,
|
||||
check_not_in: bool,
|
||||
check_not_is: bool,
|
||||
locator: &dyn CheckLocator,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -46,12 +51,18 @@ pub fn check_not_tests(
|
||||
match op {
|
||||
Cmpop::In => {
|
||||
if check_not_in {
|
||||
checks.push(Check::new(CheckKind::NotInTest, operand.location));
|
||||
checks.push(Check::new(
|
||||
CheckKind::NotInTest,
|
||||
locator.locate_check(Range::from_located(operand)),
|
||||
));
|
||||
}
|
||||
}
|
||||
Cmpop::Is => {
|
||||
if check_not_is {
|
||||
checks.push(Check::new(CheckKind::NotIsTest, operand.location));
|
||||
checks.push(Check::new(
|
||||
CheckKind::NotIsTest,
|
||||
locator.locate_check(Range::from_located(operand)),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -64,21 +75,31 @@ pub fn check_not_tests(
|
||||
}
|
||||
|
||||
/// Check UnusedVariable compliance.
|
||||
pub fn check_unused_variables(scope: &Scope) -> Vec<Check> {
|
||||
pub fn check_unused_variables(
|
||||
scope: &Scope,
|
||||
locator: &dyn CheckLocator,
|
||||
dummy_variable_rgx: &Regex,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(FunctionScope { uses_locals: true })
|
||||
) {
|
||||
return checks;
|
||||
}
|
||||
|
||||
for (name, binding) in scope.values.iter() {
|
||||
// TODO(charlie): Ignore if using `locals`.
|
||||
if binding.used.is_none()
|
||||
&& name != "_"
|
||||
&& matches!(binding.kind, BindingKind::Assignment)
|
||||
&& !dummy_variable_rgx.is_match(name)
|
||||
&& name != "__tracebackhide__"
|
||||
&& name != "__traceback_info__"
|
||||
&& name != "__traceback_supplement__"
|
||||
&& matches!(binding.kind, BindingKind::Assignment)
|
||||
{
|
||||
checks.push(Check::new(
|
||||
CheckKind::UnusedVariable(name.to_string()),
|
||||
binding.location,
|
||||
locator.locate_check(binding.location),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -87,7 +108,7 @@ pub fn check_unused_variables(scope: &Scope) -> Vec<Check> {
|
||||
}
|
||||
|
||||
/// Check DoNotAssignLambda compliance.
|
||||
pub fn check_do_not_assign_lambda(value: &Expr, location: Location) -> Option<Check> {
|
||||
pub fn check_do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Lambda { .. } = &value.node {
|
||||
Some(Check::new(CheckKind::DoNotAssignLambda, location))
|
||||
} else {
|
||||
@@ -100,7 +121,7 @@ fn is_ambiguous_name(name: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Check AmbiguousVariableName compliance.
|
||||
pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option<Check> {
|
||||
pub fn check_ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousVariableName(name.to_string()),
|
||||
@@ -112,7 +133,7 @@ pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option<C
|
||||
}
|
||||
|
||||
/// Check AmbiguousClassName compliance.
|
||||
pub fn check_ambiguous_class_name(name: &str, location: Location) -> Option<Check> {
|
||||
pub fn check_ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousClassName(name.to_string()),
|
||||
@@ -124,7 +145,7 @@ pub fn check_ambiguous_class_name(name: &str, location: Location) -> Option<Chec
|
||||
}
|
||||
|
||||
/// Check AmbiguousFunctionName compliance.
|
||||
pub fn check_ambiguous_function_name(name: &str, location: Location) -> Option<Check> {
|
||||
pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousFunctionName(name.to_string()),
|
||||
@@ -156,7 +177,7 @@ pub fn check_useless_object_inheritance(
|
||||
}) => {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UselessObjectInheritance(name.to_string()),
|
||||
expr.location,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_class_def_base(
|
||||
@@ -187,7 +208,7 @@ pub fn check_default_except_not_last(handlers: &Vec<Excepthandler>) -> Option<Ch
|
||||
if type_.is_none() && idx < handlers.len() - 1 {
|
||||
return Some(Check::new(
|
||||
CheckKind::DefaultExceptNotLast,
|
||||
handler.location,
|
||||
Range::from_located(handler),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -201,13 +222,19 @@ pub fn check_raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(CheckKind::RaiseNotImplemented, expr.location));
|
||||
return Some(Check::new(
|
||||
CheckKind::RaiseNotImplemented,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == "NotImplemented" {
|
||||
return Some(Check::new(CheckKind::RaiseNotImplemented, expr.location));
|
||||
return Some(Check::new(
|
||||
CheckKind::RaiseNotImplemented,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -239,7 +266,10 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
for arg in all_arguments {
|
||||
let ident = &arg.node.arg;
|
||||
if idents.contains(ident.as_str()) {
|
||||
checks.push(Check::new(CheckKind::DuplicateArgumentName, arg.location));
|
||||
checks.push(Check::new(
|
||||
CheckKind::DuplicateArgumentName,
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
idents.insert(ident);
|
||||
}
|
||||
@@ -253,12 +283,16 @@ pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check>
|
||||
if attr == "assertEquals" {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "self" {
|
||||
let mut check = Check::new(CheckKind::NoAssertEquals, expr.location);
|
||||
let mut check =
|
||||
Check::new(CheckKind::NoAssertEquals, Range::from_located(expr));
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "assertEqual".to_string(),
|
||||
start: Location::new(expr.location.row(), expr.location.column() + 1),
|
||||
end: Location::new(
|
||||
location: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1,
|
||||
),
|
||||
end_location: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1 + "assertEquals".len(),
|
||||
),
|
||||
@@ -292,6 +326,7 @@ pub fn check_repeated_keys(
|
||||
keys: &Vec<Expr>,
|
||||
check_repeated_literals: bool,
|
||||
check_repeated_variables: bool,
|
||||
locator: &dyn CheckLocator,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -306,7 +341,7 @@ pub fn check_repeated_keys(
|
||||
if check_repeated_literals && v1 == v2 {
|
||||
checks.push(Check::new(
|
||||
CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
k2.location,
|
||||
locator.locate_check(Range::from_located(k2)),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -314,7 +349,7 @@ pub fn check_repeated_keys(
|
||||
if check_repeated_variables && v1 == v2 {
|
||||
checks.push(Check::new(
|
||||
CheckKind::MultiValueRepeatedKeyVariable((*v2).to_string()),
|
||||
k2.location,
|
||||
locator.locate_check(Range::from_located(k2)),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -333,6 +368,7 @@ pub fn check_literal_comparisons(
|
||||
comparators: &Vec<Expr>,
|
||||
check_none_comparisons: bool,
|
||||
check_true_false_comparisons: bool,
|
||||
locator: &dyn CheckLocator,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -352,13 +388,13 @@ pub fn check_literal_comparisons(
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -372,13 +408,13 @@ pub fn check_literal_comparisons(
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -398,13 +434,13 @@ pub fn check_literal_comparisons(
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::NoneComparison(RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -418,13 +454,13 @@ pub fn check_literal_comparisons(
|
||||
if matches!(op, Cmpop::Eq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
if matches!(op, Cmpop::NotEq) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
|
||||
comparator.location,
|
||||
locator.locate_check(Range::from_located(comparator)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -461,7 +497,7 @@ pub fn check_is_literal(
|
||||
left: &Expr,
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
location: Location,
|
||||
location: Range,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -482,7 +518,7 @@ pub fn check_is_literal(
|
||||
pub fn check_type_comparison(
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
location: Location,
|
||||
location: Range,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
@@ -521,9 +557,9 @@ pub fn check_type_comparison(
|
||||
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
|
||||
pub fn check_starred_expressions(
|
||||
elts: &[Expr],
|
||||
location: Location,
|
||||
check_too_many_expressions: bool,
|
||||
check_two_starred_expressions: bool,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
let mut has_starred: bool = false;
|
||||
let mut starred_index: Option<usize> = None;
|
||||
@@ -556,6 +592,7 @@ pub fn check_break_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
locator: &dyn CheckLocator,
|
||||
) -> Option<Check> {
|
||||
let mut allowed: bool = false;
|
||||
let mut parent = stmt;
|
||||
@@ -582,7 +619,10 @@ pub fn check_break_outside_loop(
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
Some(Check::new(CheckKind::BreakOutsideLoop, stmt.location))
|
||||
Some(Check::new(
|
||||
CheckKind::BreakOutsideLoop,
|
||||
locator.locate_check(Range::from_located(stmt)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -593,6 +633,7 @@ pub fn check_continue_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
locator: &dyn CheckLocator,
|
||||
) -> Option<Check> {
|
||||
let mut allowed: bool = false;
|
||||
let mut parent = stmt;
|
||||
@@ -619,8 +660,64 @@ pub fn check_continue_outside_loop(
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
Some(Check::new(CheckKind::ContinueOutsideLoop, stmt.location))
|
||||
Some(Check::new(
|
||||
CheckKind::ContinueOutsideLoop,
|
||||
locator.locate_check(Range::from_located(stmt)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-builtins
|
||||
pub enum ShadowingType {
|
||||
Variable,
|
||||
Argument,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
/// Check builtin name shadowing
|
||||
pub fn check_builtin_shadowing(
|
||||
name: &str,
|
||||
location: Range,
|
||||
node_type: ShadowingType,
|
||||
) -> Option<Check> {
|
||||
if BUILTINS.contains(&name) {
|
||||
Some(Check::new(
|
||||
match node_type {
|
||||
ShadowingType::Variable => CheckKind::BuiltinVariableShadowing(name.to_string()),
|
||||
ShadowingType::Argument => CheckKind::BuiltinArgumentShadowing(name.to_string()),
|
||||
ShadowingType::Attribute => CheckKind::BuiltinAttributeShadowing(name.to_string()),
|
||||
},
|
||||
location,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
/// Check that `super()` has no args
|
||||
pub fn check_super_args(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
locator: &mut SourceCodeLocator,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "super" && !args.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::SuperCallWithParameters,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_super_arguments(locator, expr) {
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -101,11 +101,13 @@ pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
|
||||
for child in targets {
|
||||
match &child.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => {}
|
||||
_ => return false,
|
||||
}
|
||||
if !targets.iter().any(|child| {
|
||||
matches!(
|
||||
child.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
match &value.node {
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
|
||||
@@ -132,7 +134,7 @@ impl<'a> SourceCodeLocator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice_source_code(&mut self, location: &Location) -> &'a str {
|
||||
pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for i in self.content.lines() {
|
||||
@@ -145,4 +147,19 @@ impl<'a> SourceCodeLocator<'a> {
|
||||
let offset = self.offsets[location.row() - 1] + location.column() - 1;
|
||||
&self.content[offset..]
|
||||
}
|
||||
|
||||
pub fn slice_source_code_range(&mut self, start: &Location, end: &Location) -> &'a str {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for i in self.content.lines() {
|
||||
self.offsets.push(offset);
|
||||
offset += i.len();
|
||||
offset += 1;
|
||||
}
|
||||
self.initialized = true;
|
||||
}
|
||||
let start = self.offsets[start.row() - 1] + start.column() - 1;
|
||||
let end = self.offsets[end.row() - 1] + end.column() - 1;
|
||||
&self.content[start..end]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Keyword, Location};
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
|
||||
|
||||
fn relocate_keyword(keyword: &mut Keyword, location: Location) {
|
||||
keyword.location = location;
|
||||
use crate::ast::types::Range;
|
||||
|
||||
fn relocate_keyword(keyword: &mut Keyword, location: Range) {
|
||||
keyword.location = location.location;
|
||||
keyword.end_location = location.end_location;
|
||||
relocate_expr(&mut keyword.node.value, location);
|
||||
}
|
||||
|
||||
/// Change an expression's location (recursively) to match a desired, fixed location.
|
||||
pub fn relocate_expr(expr: &mut Expr, location: Location) {
|
||||
expr.location = location;
|
||||
pub fn relocate_expr(expr: &mut Expr, location: Range) {
|
||||
expr.location = location.location;
|
||||
expr.end_location = location.end_location;
|
||||
match &mut expr.node {
|
||||
ExprKind::BoolOp { values, .. } => {
|
||||
for expr in values {
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
fn id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Range {
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
||||
Range {
|
||||
location: located.location,
|
||||
end_location: located.end_location,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct FunctionScope {
|
||||
pub uses_locals: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Function(FunctionScope),
|
||||
Generator,
|
||||
Module,
|
||||
}
|
||||
@@ -20,6 +40,7 @@ pub enum ScopeKind {
|
||||
pub struct Scope {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind,
|
||||
pub import_starred: bool,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
}
|
||||
|
||||
@@ -28,6 +49,7 @@ impl Scope {
|
||||
Scope {
|
||||
id: id(),
|
||||
kind,
|
||||
import_starred: false,
|
||||
values: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -39,6 +61,7 @@ pub enum BindingKind {
|
||||
Argument,
|
||||
Assignment,
|
||||
Binding,
|
||||
LoopVar,
|
||||
Builtin,
|
||||
ClassDefinition,
|
||||
Definition,
|
||||
@@ -52,8 +75,12 @@ pub enum BindingKind {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Binding {
|
||||
pub kind: BindingKind,
|
||||
pub location: Location,
|
||||
/// Tuple of (scope index, location) indicating the scope and location at which the binding was
|
||||
pub location: Range,
|
||||
/// Tuple of (scope index, range) indicating the scope and range at which the binding was
|
||||
/// last used.
|
||||
pub used: Option<(usize, Location)>,
|
||||
pub used: Option<(usize, Range)>,
|
||||
}
|
||||
|
||||
pub trait CheckLocator {
|
||||
fn locate_check(&self, default: Range) -> Range;
|
||||
}
|
||||
|
||||
@@ -45,29 +45,29 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
|
||||
|
||||
for fix in fixes {
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
|
||||
if last_pos > fix.start {
|
||||
if last_pos > fix.location {
|
||||
continue;
|
||||
}
|
||||
|
||||
if fix.start.row() > last_pos.row() {
|
||||
if fix.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() - 1..]);
|
||||
output.push('\n');
|
||||
}
|
||||
for line in &lines[last_pos.row()..fix.start.row() - 1] {
|
||||
for line in &lines[last_pos.row()..fix.location.row() - 1] {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
output.push_str(&lines[fix.start.row() - 1][..fix.start.column() - 1]);
|
||||
output.push_str(&lines[fix.location.row() - 1][..fix.location.column() - 1]);
|
||||
output.push_str(&fix.content);
|
||||
} else {
|
||||
output.push_str(
|
||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.start.column() - 1],
|
||||
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.location.column() - 1],
|
||||
);
|
||||
output.push_str(&fix.content);
|
||||
}
|
||||
|
||||
last_pos = fix.end;
|
||||
last_pos = fix.end_location;
|
||||
fix.applied = true;
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ mod tests {
|
||||
fn apply_single_replacement() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
content: "Bar".to_string(),
|
||||
start: Location::new(1, 9),
|
||||
end: Location::new(1, 15),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 15),
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
@@ -130,8 +130,8 @@ mod tests {
|
||||
fn apply_single_removal() -> Result<()> {
|
||||
let mut fixes = vec![Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 16),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
applied: false,
|
||||
}];
|
||||
let actual = apply_fixes(
|
||||
@@ -155,14 +155,14 @@ mod tests {
|
||||
let mut fixes = vec![
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 17),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 17),
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 17),
|
||||
end: Location::new(1, 24),
|
||||
location: Location::new(1, 17),
|
||||
end_location: Location::new(1, 24),
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
@@ -187,14 +187,14 @@ mod tests {
|
||||
let mut fixes = vec![
|
||||
Fix {
|
||||
content: "".to_string(),
|
||||
start: Location::new(1, 8),
|
||||
end: Location::new(1, 16),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 16),
|
||||
applied: false,
|
||||
},
|
||||
Fix {
|
||||
content: "ignored".to_string(),
|
||||
start: Location::new(1, 10),
|
||||
end: Location::new(1, 12),
|
||||
location: Location::new(1, 10),
|
||||
end_location: Location::new(1, 12),
|
||||
applied: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use libcst_native::{Codegen, Expression, SmallStatement, Statement};
|
||||
use rustpython_parser::ast::{Expr, Keyword, Location};
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::token::Tok;
|
||||
@@ -25,7 +26,7 @@ pub fn remove_class_def_base(
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Option<Fix> {
|
||||
let content = locator.slice_source_code(stmt_at);
|
||||
let content = locator.slice_source_code_at(stmt_at);
|
||||
|
||||
// Case 1: `object` is the only base.
|
||||
if bases.len() == 1 && keywords.is_empty() {
|
||||
@@ -52,8 +53,8 @@ pub fn remove_class_def_base(
|
||||
return match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
location: start,
|
||||
end_location: end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
@@ -91,8 +92,8 @@ pub fn remove_class_def_base(
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
location: start,
|
||||
end_location: end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
@@ -116,11 +117,42 @@ pub fn remove_class_def_base(
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Some(Fix {
|
||||
content: "".to_string(),
|
||||
start,
|
||||
end,
|
||||
location: start,
|
||||
end_location: end,
|
||||
applied: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option<Fix> {
|
||||
let contents = locator.slice_source_code_range(&expr.location, &expr.end_location);
|
||||
|
||||
let mut tree = match libcst_native::parse_module(contents, None) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
|
||||
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
|
||||
if let Expression::Call(body) = &mut body.value {
|
||||
body.args = vec![];
|
||||
body.whitespace_before_args = Default::default();
|
||||
body.whitespace_after_func = Default::default();
|
||||
|
||||
let mut state = Default::default();
|
||||
tree.codegen(&mut state);
|
||||
|
||||
return Some(Fix {
|
||||
content: state.to_string(),
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::fs::{create_dir_all, File, Metadata};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@@ -8,6 +8,7 @@ use anyhow::Result;
|
||||
use cacache::Error::EntryNotFound;
|
||||
use filetime::FileTime;
|
||||
use log::error;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::autofix::fixer;
|
||||
@@ -79,7 +80,7 @@ fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String
|
||||
autofix.hash(&mut hasher);
|
||||
format!(
|
||||
"{}@{}@{}",
|
||||
path.canonicalize().unwrap().to_string_lossy(),
|
||||
path.absolutize().unwrap().to_string_lossy(),
|
||||
VERSION,
|
||||
hasher.finish()
|
||||
)
|
||||
@@ -90,6 +91,7 @@ pub fn init() -> Result<()> {
|
||||
if gitignore_path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
create_dir_all(cache_dir())?;
|
||||
let mut file = File::create(gitignore_path)?;
|
||||
file.write_all(b"*").map_err(|e| e.into())
|
||||
}
|
||||
|
||||
476
src/check_ast.rs
476
src/check_ast.rs
@@ -1,15 +1,19 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Location, Operator, Stmt, StmtKind, Suite,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{Binding, BindingKind, Scope, ScopeKind};
|
||||
use crate::ast::types::{
|
||||
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, operations, visitor};
|
||||
use crate::autofix::fixer;
|
||||
@@ -21,6 +25,8 @@ use crate::settings::Settings;
|
||||
|
||||
pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
struct Checker<'a> {
|
||||
// Input data.
|
||||
locator: SourceCodeLocator<'a>,
|
||||
@@ -36,13 +42,13 @@ struct Checker<'a> {
|
||||
scopes: Vec<Scope>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
deferred_string_annotations: Vec<(Location, &'a str)>,
|
||||
deferred_string_annotations: Vec<(Range, &'a str)>,
|
||||
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
// Derivative state.
|
||||
in_f_string: bool,
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
seen_non_import: bool,
|
||||
@@ -74,7 +80,7 @@ impl<'a> Checker<'a> {
|
||||
deferred_functions: vec![],
|
||||
deferred_lambdas: vec![],
|
||||
deferred_assignments: vec![],
|
||||
in_f_string: false,
|
||||
in_f_string: None,
|
||||
in_annotation: false,
|
||||
in_literal: false,
|
||||
seen_non_import: false,
|
||||
@@ -101,6 +107,36 @@ fn is_annotated_subscript(expr: &Expr) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type annotation.
|
||||
// This is what pycodestyle (as of 2.9.1) does.
|
||||
match node {
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
value: _,
|
||||
type_comment: _,
|
||||
} => {
|
||||
if targets.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
match &targets[0].node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation: _,
|
||||
value: _,
|
||||
simple: _,
|
||||
} => match &target.node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
@@ -157,10 +193,11 @@ where
|
||||
self.futures_allowed = false;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
node => {
|
||||
self.futures_allowed = false;
|
||||
|
||||
if !self.seen_non_import
|
||||
&& !is_assignment_to_a_dunder(node)
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
@@ -171,7 +208,6 @@ where
|
||||
// Pre-visit.
|
||||
match &stmt.node {
|
||||
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
|
||||
// TODO(charlie): Handle doctests.
|
||||
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
|
||||
|
||||
let current_scope =
|
||||
@@ -184,8 +220,8 @@ where
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: Some((global_scope_id, stmt.location)),
|
||||
location: stmt.location,
|
||||
used: Some((global_scope_id, Range::from_located(stmt))),
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -193,25 +229,34 @@ where
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
self.checks.extend(names.iter().filter_map(|name| {
|
||||
checks::check_ambiguous_variable_name(name, stmt.location)
|
||||
}));
|
||||
let location = self.locate_check(Range::from_located(stmt));
|
||||
self.checks.extend(
|
||||
names.iter().filter_map(|name| {
|
||||
checks::check_ambiguous_variable_name(name, location)
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
StmtKind::Break => {
|
||||
if self.settings.select.contains(&CheckCode::F701) {
|
||||
if let Some(check) =
|
||||
checks::check_break_outside_loop(stmt, &self.parents, &self.parent_stack)
|
||||
{
|
||||
if let Some(check) = checks::check_break_outside_loop(
|
||||
stmt,
|
||||
&self.parents,
|
||||
&self.parent_stack,
|
||||
self,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Continue => {
|
||||
if self.settings.select.contains(&CheckCode::F702) {
|
||||
if let Some(check) =
|
||||
checks::check_continue_outside_loop(stmt, &self.parents, &self.parent_stack)
|
||||
{
|
||||
if let Some(check) = checks::check_continue_outside_loop(
|
||||
stmt,
|
||||
&self.parents,
|
||||
&self.parent_stack,
|
||||
self,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -231,11 +276,16 @@ where
|
||||
..
|
||||
} => {
|
||||
if self.settings.select.contains(&CheckCode::E743) {
|
||||
if let Some(check) = checks::check_ambiguous_function_name(name, stmt.location)
|
||||
{
|
||||
if let Some(check) = checks::check_ambiguous_function_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
|
||||
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
@@ -278,7 +328,7 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::Definition,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -293,7 +343,7 @@ where
|
||||
ScopeKind::Class | ScopeKind::Module => {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ReturnOutsideFunction,
|
||||
stmt.location,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -325,11 +375,20 @@ where
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E742) {
|
||||
if let Some(check) = checks::check_ambiguous_class_name(name, stmt.location) {
|
||||
if let Some(check) = checks::check_ambiguous_class_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
false,
|
||||
);
|
||||
|
||||
for expr in bases {
|
||||
self.visit_expr(expr)
|
||||
}
|
||||
@@ -351,7 +410,7 @@ where
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
stmt.location,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -366,10 +425,14 @@ where
|
||||
alias.node.name.to_string(),
|
||||
),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
alias
|
||||
.node
|
||||
@@ -385,13 +448,17 @@ where
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom { names, module, .. } => {
|
||||
StmtKind::ImportFrom {
|
||||
names,
|
||||
module,
|
||||
level,
|
||||
} => {
|
||||
if self
|
||||
.settings
|
||||
.select
|
||||
@@ -401,7 +468,7 @@ where
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
stmt.location,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -422,9 +489,9 @@ where
|
||||
.last()
|
||||
.expect("No current scope found."))]
|
||||
.id,
|
||||
stmt.location,
|
||||
Range::from_located(stmt),
|
||||
)),
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -437,54 +504,68 @@ where
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
|
||||
stmt.location,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F404) && !self.futures_allowed
|
||||
{
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::LateFutureImport, stmt.location));
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::LateFutureImport,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
} else if alias.node.name == "*" {
|
||||
let module_name = format!(
|
||||
"{}{}",
|
||||
".".repeat(level.unwrap_or_default()),
|
||||
module.clone().unwrap_or_else(|| "module".to_string()),
|
||||
);
|
||||
|
||||
self.add_binding(
|
||||
name,
|
||||
module_name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::StarImportation,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
);
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F403) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarUsage(
|
||||
module.clone().unwrap_or_else(|| "module".to_string()),
|
||||
),
|
||||
stmt.location,
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F406) {
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if !matches!(scope.kind, ScopeKind::Module) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarNotPermitted(
|
||||
module.clone().unwrap_or_else(|| "module".to_string()),
|
||||
),
|
||||
stmt.location,
|
||||
CheckKind::ImportStarNotPermitted(module_name.to_string()),
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F403) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarUsed(module_name.to_string()),
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
|
||||
let scope = &mut self.scopes[*(self
|
||||
.scope_stack
|
||||
.last_mut()
|
||||
.expect("No current scope found."))];
|
||||
scope.import_starred = true;
|
||||
} else {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
}
|
||||
|
||||
let binding = Binding {
|
||||
kind: BindingKind::Importation(match module {
|
||||
None => name.clone(),
|
||||
Some(parent) => format!("{}.{}", parent, name),
|
||||
}),
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
};
|
||||
self.add_binding(name, binding)
|
||||
}
|
||||
@@ -504,14 +585,19 @@ where
|
||||
}
|
||||
StmtKind::If { test, .. } => {
|
||||
if self.settings.select.contains(&CheckCode::F634) {
|
||||
if let Some(check) = checks::check_if_tuple(test, stmt.location) {
|
||||
if let Some(check) =
|
||||
checks::check_if_tuple(test, self.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, .. } => {
|
||||
if self.settings.select.contains(CheckKind::AssertTuple.code()) {
|
||||
if let Some(check) = checks::check_assert_tuple(test, stmt.location) {
|
||||
if let Some(check) = checks::check_assert_tuple(
|
||||
test,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -525,7 +611,10 @@ where
|
||||
}
|
||||
StmtKind::Assign { value, .. } => {
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(value, stmt.location) {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(
|
||||
value,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -533,9 +622,10 @@ where
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
if self.settings.select.contains(&CheckCode::E731) {
|
||||
if let Some(value) = value {
|
||||
if let Some(check) =
|
||||
checks::check_do_not_assign_lambda(value, stmt.location)
|
||||
{
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(
|
||||
value,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -570,7 +660,7 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::ClassDefinition,
|
||||
used: None,
|
||||
location: stmt.location,
|
||||
location: Range::from_located(stmt),
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -590,7 +680,6 @@ where
|
||||
let prev_in_literal = self.in_literal;
|
||||
let prev_in_annotation = self.in_annotation;
|
||||
|
||||
// Important:
|
||||
if self.in_annotation && self.annotations_future_enabled {
|
||||
self.deferred_annotations.push((
|
||||
expr,
|
||||
@@ -616,9 +705,9 @@ where
|
||||
self.settings.select.contains(&CheckCode::F622);
|
||||
if let Some(check) = checks::check_starred_expressions(
|
||||
elts,
|
||||
expr.location,
|
||||
check_too_many_expressions,
|
||||
check_two_starred_expressions,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
@@ -628,24 +717,52 @@ where
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Store => {
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
checks::check_ambiguous_variable_name(id, expr.location)
|
||||
{
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
id,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(id, Range::from_located(expr), true);
|
||||
|
||||
let parent =
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||
self.handle_node_store(expr, parent);
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::Call { func, .. } => {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if self.settings.select.contains(&CheckCode::R002) {
|
||||
if let Some(check) = checks::check_assert_equals(func, self.autofix) {
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.select.contains(&CheckCode::SPR001) {
|
||||
if let Some(check) =
|
||||
checks::check_super_args(expr, func, args, &mut self.locator, self.autofix)
|
||||
{
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, ctx } = &func.node {
|
||||
if id == "locals" && matches!(ctx, ExprContext::Load) {
|
||||
let scope = &mut self.scopes[*(self
|
||||
.scope_stack
|
||||
.last_mut()
|
||||
.expect("No current scope found."))];
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(FunctionScope { uses_locals: false })
|
||||
) {
|
||||
scope.kind = ScopeKind::Function(FunctionScope { uses_locals: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, .. } => {
|
||||
let check_repeated_literals = self.settings.select.contains(&CheckCode::F601);
|
||||
@@ -655,6 +772,7 @@ where
|
||||
keys,
|
||||
check_repeated_literals,
|
||||
check_repeated_variables,
|
||||
self,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -667,12 +785,14 @@ where
|
||||
.contains(CheckKind::YieldOutsideFunction.code())
|
||||
&& matches!(scope.kind, ScopeKind::Class | ScopeKind::Module)
|
||||
{
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::YieldOutsideFunction, expr.location));
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::YieldOutsideFunction,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
}
|
||||
ExprKind::JoinedStr { values } => {
|
||||
if !self.in_f_string
|
||||
if self.in_f_string.is_none()
|
||||
&& self
|
||||
.settings
|
||||
.select
|
||||
@@ -683,10 +803,10 @@ where
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
expr.location,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
self.in_f_string = true;
|
||||
self.in_f_string = Some(Range::from_located(expr));
|
||||
}
|
||||
ExprKind::BinOp {
|
||||
left,
|
||||
@@ -703,8 +823,10 @@ where
|
||||
..
|
||||
}) = scope.values.get("print")
|
||||
{
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::InvalidPrintSyntax, left.location));
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::InvalidPrintSyntax,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,6 +841,7 @@ where
|
||||
operand,
|
||||
check_not_in,
|
||||
check_not_is,
|
||||
self,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -736,6 +859,7 @@ where
|
||||
comparators,
|
||||
check_none_comparisons,
|
||||
check_true_false_comparisons,
|
||||
self,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -744,7 +868,7 @@ where
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
expr.location,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -752,7 +876,7 @@ where
|
||||
self.checks.extend(checks::check_type_comparison(
|
||||
ops,
|
||||
comparators,
|
||||
expr.location,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -761,7 +885,7 @@ where
|
||||
..
|
||||
} if self.in_annotation && !self.in_literal => {
|
||||
self.deferred_string_annotations
|
||||
.push((expr.location, value));
|
||||
.push((Range::from_located(expr), value));
|
||||
}
|
||||
ExprKind::GeneratorExp { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
@@ -911,18 +1035,26 @@ where
|
||||
if self.settings.select.contains(&CheckCode::E722) && type_.is_none() {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
excepthandler.location,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) =
|
||||
checks::check_ambiguous_variable_name(name, excepthandler.location)
|
||||
{
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(excepthandler)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(
|
||||
name,
|
||||
Range::from_located(excepthandler),
|
||||
false,
|
||||
);
|
||||
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.contains_key(name) {
|
||||
@@ -931,6 +1063,7 @@ where
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
excepthandler.end_location,
|
||||
ExprKind::Name {
|
||||
id: name.to_string(),
|
||||
ctx: ExprContext::Store,
|
||||
@@ -949,6 +1082,7 @@ where
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
excepthandler.end_location,
|
||||
ExprKind::Name {
|
||||
id: name.to_string(),
|
||||
ctx: ExprContext::Store,
|
||||
@@ -968,7 +1102,7 @@ where
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UnusedVariable(name.to_string()),
|
||||
excepthandler.location,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1014,16 +1148,26 @@ where
|
||||
Binding {
|
||||
kind: BindingKind::Argument,
|
||||
used: None,
|
||||
location: arg.location,
|
||||
location: Range::from_located(arg),
|
||||
},
|
||||
);
|
||||
|
||||
if self.settings.select.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(&arg.node.arg, arg.location)
|
||||
{
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
&arg.node.arg,
|
||||
self.locate_check(Range::from_located(arg)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckLocator for Checker<'_> {
|
||||
fn locate_check(&self, default: Range) -> Range {
|
||||
self.in_f_string.unwrap_or(default)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1083,12 +1227,27 @@ impl<'a> Checker<'a> {
|
||||
// TODO(charlie): Don't treat annotations as assignments if there is an existing value.
|
||||
let binding = match scope.values.get(&name) {
|
||||
None => binding,
|
||||
Some(existing) => Binding {
|
||||
kind: binding.kind,
|
||||
location: binding.location,
|
||||
used: existing.used,
|
||||
},
|
||||
Some(existing) => {
|
||||
if self.settings.select.contains(&CheckCode::F402)
|
||||
&& matches!(existing.kind, BindingKind::Importation(_))
|
||||
&& matches!(binding.kind, BindingKind::LoopVar)
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportShadowedByLoopVar(
|
||||
name.clone(),
|
||||
existing.location.location.row(),
|
||||
),
|
||||
binding.location,
|
||||
));
|
||||
}
|
||||
Binding {
|
||||
kind: binding.kind,
|
||||
location: binding.location,
|
||||
used: existing.used,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.values.insert(name, binding);
|
||||
}
|
||||
|
||||
@@ -1099,6 +1258,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
let mut first_iter = true;
|
||||
let mut in_generator = false;
|
||||
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) {
|
||||
@@ -1109,12 +1269,34 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
if let Some(binding) = scope.values.get_mut(id) {
|
||||
binding.used = Some((scope_id, expr.location));
|
||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||
return;
|
||||
}
|
||||
|
||||
first_iter = false;
|
||||
in_generator = matches!(scope.kind, ScopeKind::Generator);
|
||||
import_starred = import_starred || scope.import_starred;
|
||||
}
|
||||
|
||||
if import_starred {
|
||||
if self.settings.select.contains(&CheckCode::F405) {
|
||||
let mut from_list = vec![];
|
||||
for scope_index in self.scope_stack.iter().rev() {
|
||||
let scope = &self.scopes[*scope_index];
|
||||
for (name, binding) in scope.values.iter() {
|
||||
if matches!(binding.kind, BindingKind::StarImportation) {
|
||||
from_list.push(name.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
from_list.sort();
|
||||
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarUsage(id.clone(), from_list.join(", ")),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F821) {
|
||||
@@ -1124,7 +1306,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UndefinedName(id.clone()),
|
||||
expr.location,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1136,17 +1318,17 @@ impl<'a> Checker<'a> {
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F823)
|
||||
&& matches!(current.kind, ScopeKind::Function)
|
||||
&& matches!(current.kind, ScopeKind::Function(_))
|
||||
&& !current.values.contains_key(id)
|
||||
{
|
||||
for scope in self.scopes.iter().rev().skip(1) {
|
||||
if matches!(scope.kind, ScopeKind::Function | ScopeKind::Module) {
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||
if let Some(binding) = scope.values.get(id) {
|
||||
if let Some((scope_id, location)) = binding.used {
|
||||
if scope_id == current.id {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UndefinedLocal(id.clone()),
|
||||
location,
|
||||
self.locate_check(location),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1161,7 +1343,7 @@ impl<'a> Checker<'a> {
|
||||
Binding {
|
||||
kind: BindingKind::Annotation,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
location: Range::from_located(expr),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -1171,14 +1353,25 @@ impl<'a> Checker<'a> {
|
||||
if matches!(
|
||||
parent.node,
|
||||
StmtKind::For { .. } | StmtKind::AsyncFor { .. }
|
||||
) || operations::is_unpacking_assignment(parent)
|
||||
{
|
||||
) {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::LoopVar,
|
||||
used: None,
|
||||
location: Range::from_located(expr),
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if operations::is_unpacking_assignment(parent) {
|
||||
self.add_binding(
|
||||
id.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Binding,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
location: Range::from_located(expr),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -1198,7 +1391,7 @@ impl<'a> Checker<'a> {
|
||||
Binding {
|
||||
kind: BindingKind::Export(extract_all_names(parent, current)),
|
||||
used: None,
|
||||
location: expr.location,
|
||||
location: Range::from_located(expr),
|
||||
},
|
||||
);
|
||||
return;
|
||||
@@ -1209,7 +1402,7 @@ impl<'a> Checker<'a> {
|
||||
Binding {
|
||||
kind: BindingKind::Assignment,
|
||||
used: None,
|
||||
location: expr.location,
|
||||
location: Range::from_located(expr),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1228,7 +1421,7 @@ impl<'a> Checker<'a> {
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UndefinedName(id.clone()),
|
||||
expr.location,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1253,7 +1446,7 @@ impl<'a> Checker<'a> {
|
||||
} else if self.settings.select.contains(&CheckCode::F722) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
|
||||
location,
|
||||
self.locate_check(location),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1266,7 +1459,7 @@ impl<'a> Checker<'a> {
|
||||
while let Some((stmt, scopes, parents)) = self.deferred_functions.pop() {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.push_scope(Scope::new(ScopeKind::Function));
|
||||
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, args, .. }
|
||||
@@ -1290,7 +1483,7 @@ impl<'a> Checker<'a> {
|
||||
while let Some((expr, scopes, parents)) = self.deferred_lambdas.pop() {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.push_scope(Scope::new(ScopeKind::Function));
|
||||
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
|
||||
|
||||
if let ExprKind::Lambda { args, body } = &expr.node {
|
||||
self.visit_arguments(args);
|
||||
@@ -1305,17 +1498,21 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
while let Some(index) = self.deferred_assignments.pop() {
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
self.checks
|
||||
.extend(checks::check_unused_variables(&self.scopes[index]));
|
||||
if self.settings.select.contains(&CheckCode::F841) {
|
||||
while let Some(index) = self.deferred_assignments.pop() {
|
||||
self.checks.extend(checks::check_unused_variables(
|
||||
&self.scopes[index],
|
||||
self,
|
||||
&self.settings.dummy_variable_rgx,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dead_scopes(&mut self) {
|
||||
if !self.settings.select.contains(&CheckCode::F822)
|
||||
&& !self.settings.select.contains(&CheckCode::F401)
|
||||
if !self.settings.select.contains(&CheckCode::F401)
|
||||
&& !self.settings.select.contains(&CheckCode::F405)
|
||||
&& !self.settings.select.contains(&CheckCode::F822)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1330,15 +1527,39 @@ impl<'a> Checker<'a> {
|
||||
});
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F822)
|
||||
&& !scope.import_starred
|
||||
&& !self.path.ends_with("__init__.py")
|
||||
{
|
||||
if let Some(binding) = all_binding {
|
||||
if let Some(all_binding) = all_binding {
|
||||
if let Some(names) = all_names {
|
||||
for name in names {
|
||||
if !scope.values.contains_key(name) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UndefinedExport(name.to_string()),
|
||||
binding.location,
|
||||
self.locate_check(all_binding.location),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F405) && scope.import_starred {
|
||||
if let Some(all_binding) = all_binding {
|
||||
if let Some(names) = all_names {
|
||||
let mut from_list = vec![];
|
||||
for (name, binding) in scope.values.iter() {
|
||||
if matches!(binding.kind, BindingKind::StarImportation) {
|
||||
from_list.push(name.as_str());
|
||||
}
|
||||
}
|
||||
from_list.sort();
|
||||
|
||||
for name in names {
|
||||
if !scope.values.contains_key(name) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarUsage(name.clone(), from_list.join(", ")),
|
||||
self.locate_check(all_binding.location),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1359,7 +1580,7 @@ impl<'a> Checker<'a> {
|
||||
| BindingKind::SubmoduleImportation(full_name) => {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UnusedImport(full_name.to_string()),
|
||||
binding.location,
|
||||
self.locate_check(binding.location),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -1369,6 +1590,43 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
|
||||
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
|
||||
// flake8-builtins
|
||||
if is_attribute && matches!(scope.kind, ScopeKind::Class) {
|
||||
if self.settings.select.contains(&CheckCode::A003) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Attribute,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
} else if self.settings.select.contains(&CheckCode::A001) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Variable,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) {
|
||||
if self.settings.select.contains(&CheckCode::A002) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Argument,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_ast(
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::noqa;
|
||||
use crate::noqa::Directive;
|
||||
use crate::settings::Settings;
|
||||
|
||||
/// Whether the given line is too long and should be reported.
|
||||
@@ -19,37 +25,205 @@ fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings) {
|
||||
pub fn check_lines(
|
||||
checks: &mut Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
|
||||
let enforce_noqa = settings.select.contains(&CheckCode::M001);
|
||||
|
||||
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
|
||||
|
||||
let mut line_checks = vec![];
|
||||
let mut ignored = vec![];
|
||||
for (row, line) in contents.lines().enumerate() {
|
||||
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
if enforce_noqa {
|
||||
noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
}
|
||||
|
||||
// Remove any ignored checks.
|
||||
// TODO(charlie): Only validate checks for the current line.
|
||||
for (index, check) in checks.iter().enumerate() {
|
||||
if check.location.row() == row + 1 && check.is_inline_ignored(line) {
|
||||
ignored.push(index);
|
||||
if check.location.row() == lineno + 1 {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce line length.
|
||||
if enforce_line_too_long {
|
||||
let line_length = line.len();
|
||||
let line_length = line.chars().count();
|
||||
if should_enforce_line_length(line, line_length, settings.line_length) {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::LineTooLong(line_length, settings.line_length),
|
||||
Location::new(row + 1, settings.line_length + 1),
|
||||
Range {
|
||||
location: Location::new(lineno + 1, 1),
|
||||
end_location: Location::new(lineno + 1, line_length + 1),
|
||||
},
|
||||
);
|
||||
if !check.is_inline_ignored(line) {
|
||||
line_checks.push(check);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => line_checks.push(check),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used.
|
||||
if enforce_noqa {
|
||||
for (row, (directive, matches)) in noqa_directives {
|
||||
match directive {
|
||||
Directive::All(start, end) => {
|
||||
if matches.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(None),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
},
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "".to_string(),
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(
|
||||
row + 1,
|
||||
lines[row].chars().count() + 1,
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
Directive::Codes(start, end, codes) => {
|
||||
let mut invalid_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
for code in codes {
|
||||
if !matches.contains(&code) {
|
||||
invalid_codes.push(code);
|
||||
} else {
|
||||
valid_codes.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_codes.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes.join(", "))),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
},
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if valid_codes.is_empty() {
|
||||
check.amend(Fix {
|
||||
content: "".to_string(),
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(
|
||||
row + 1,
|
||||
lines[row].chars().count() + 1,
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
} else {
|
||||
check.amend(Fix {
|
||||
content: format!(" # noqa: {}", valid_codes.join(", ")),
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(
|
||||
row + 1,
|
||||
lines[row].chars().count() + 1,
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
Directive::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignored.sort();
|
||||
for index in ignored.iter().rev() {
|
||||
checks.swap_remove(*index);
|
||||
}
|
||||
checks.extend(line_checks);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::settings;
|
||||
|
||||
use super::check_lines;
|
||||
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let noqa_line_for: Vec<usize> = vec![1];
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
check_lines(
|
||||
&mut checks,
|
||||
line,
|
||||
&noqa_line_for,
|
||||
&settings::Settings {
|
||||
line_length,
|
||||
..settings::Settings::for_rule(CheckCode::E501)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
return checks;
|
||||
};
|
||||
assert!(!check_with_max_line_length(6).is_empty());
|
||||
assert!(check_with_max_line_length(7).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
324
src/checks.rs
324
src/checks.rs
@@ -1,13 +1,124 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
];
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 49] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
// Meta
|
||||
CheckCode::M001,
|
||||
// Refactor
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
|
||||
pub enum CheckCode {
|
||||
// pycodestyle
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
@@ -22,9 +133,12 @@ pub enum CheckCode {
|
||||
E743,
|
||||
E902,
|
||||
E999,
|
||||
// pyflakes
|
||||
F401,
|
||||
F402,
|
||||
F403,
|
||||
F404,
|
||||
F405,
|
||||
F406,
|
||||
F407,
|
||||
F541,
|
||||
@@ -48,8 +162,17 @@ pub enum CheckCode {
|
||||
F831,
|
||||
F841,
|
||||
F901,
|
||||
// flake8-builtins
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
// flake8-super
|
||||
SPR001,
|
||||
// Refactor
|
||||
R001,
|
||||
R002,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
|
||||
impl FromStr for CheckCode {
|
||||
@@ -57,6 +180,7 @@ impl FromStr for CheckCode {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
// pycodestyle
|
||||
"E402" => Ok(CheckCode::E402),
|
||||
"E501" => Ok(CheckCode::E501),
|
||||
"E711" => Ok(CheckCode::E711),
|
||||
@@ -71,9 +195,12 @@ impl FromStr for CheckCode {
|
||||
"E743" => Ok(CheckCode::E743),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"E999" => Ok(CheckCode::E999),
|
||||
// pyflakes
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F402" => Ok(CheckCode::F402),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F404" => Ok(CheckCode::F404),
|
||||
"F405" => Ok(CheckCode::F405),
|
||||
"F406" => Ok(CheckCode::F406),
|
||||
"F407" => Ok(CheckCode::F407),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
@@ -97,8 +224,17 @@ impl FromStr for CheckCode {
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
// flake8-builtins
|
||||
"A001" => Ok(CheckCode::A001),
|
||||
"A002" => Ok(CheckCode::A002),
|
||||
"A003" => Ok(CheckCode::A003),
|
||||
// flake8-super
|
||||
"SPR001" => Ok(CheckCode::SPR001),
|
||||
// Refactor
|
||||
"R001" => Ok(CheckCode::R001),
|
||||
"R002" => Ok(CheckCode::R002),
|
||||
// Meta
|
||||
"M001" => Ok(CheckCode::M001),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
}
|
||||
@@ -107,6 +243,7 @@ impl FromStr for CheckCode {
|
||||
impl CheckCode {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
// pycodestyle
|
||||
CheckCode::E402 => "E402",
|
||||
CheckCode::E501 => "E501",
|
||||
CheckCode::E711 => "E711",
|
||||
@@ -121,9 +258,12 @@ impl CheckCode {
|
||||
CheckCode::E743 => "E743",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::E999 => "E999",
|
||||
// pyflakes
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F402 => "F402",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F404 => "F404",
|
||||
CheckCode::F405 => "F405",
|
||||
CheckCode::F406 => "F406",
|
||||
CheckCode::F407 => "F407",
|
||||
CheckCode::F541 => "F541",
|
||||
@@ -147,19 +287,89 @@ impl CheckCode {
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => "A001",
|
||||
CheckCode::A002 => "A002",
|
||||
CheckCode::A003 => "A003",
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => "SPR001",
|
||||
// Refactor
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
// Meta
|
||||
CheckCode::M001 => "M001",
|
||||
}
|
||||
}
|
||||
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E501 => &LintSource::Lines,
|
||||
CheckCode::E902 | CheckCode::E999 => &LintSource::FileSystem,
|
||||
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder representation of the CheckKind for the check.
|
||||
pub fn kind(&self) -> CheckKind {
|
||||
match self {
|
||||
// pycodestyle
|
||||
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
|
||||
CheckCode::E713 => CheckKind::NotInTest,
|
||||
CheckCode::E714 => CheckKind::NotIsTest,
|
||||
CheckCode::E721 => CheckKind::TypeComparison,
|
||||
CheckCode::E722 => CheckKind::DoNotUseBareExcept,
|
||||
CheckCode::E731 => CheckKind::DoNotAssignLambda,
|
||||
CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
|
||||
CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
|
||||
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckCode::E902 => CheckKind::IOError("...".to_string()),
|
||||
CheckCode::E999 => CheckKind::SyntaxError("...".to_string()),
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
CheckCode::F404 => CheckKind::LateFutureImport,
|
||||
CheckCode::F405 => CheckKind::ImportStarUsage("...".to_string(), "...".to_string()),
|
||||
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
|
||||
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
|
||||
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
|
||||
CheckCode::F621 => CheckKind::TooManyExpressionsInStarredAssignment,
|
||||
CheckCode::F622 => CheckKind::TwoStarredExpressions,
|
||||
CheckCode::F631 => CheckKind::AssertTuple,
|
||||
CheckCode::F632 => CheckKind::IsLiteral,
|
||||
CheckCode::F633 => CheckKind::InvalidPrintSyntax,
|
||||
CheckCode::F634 => CheckKind::IfTuple,
|
||||
CheckCode::F701 => CheckKind::BreakOutsideLoop,
|
||||
CheckCode::F702 => CheckKind::ContinueOutsideLoop,
|
||||
CheckCode::F704 => CheckKind::YieldOutsideFunction,
|
||||
CheckCode::F706 => CheckKind::ReturnOutsideFunction,
|
||||
CheckCode::F707 => CheckKind::DefaultExceptNotLast,
|
||||
CheckCode::F722 => CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
|
||||
CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
|
||||
CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
|
||||
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
|
||||
CheckCode::F831 => CheckKind::DuplicateArgumentName,
|
||||
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
|
||||
CheckCode::F901 => CheckKind::RaiseNotImplemented,
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
|
||||
// Refactor
|
||||
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::R002 => CheckKind::NoAssertEquals,
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
@@ -177,6 +387,7 @@ pub enum RejectedCmpop {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
UnusedNOQA(Option<String>),
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
@@ -192,8 +403,10 @@ pub enum CheckKind {
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportShadowedByLoopVar(String, usize),
|
||||
ImportStarNotPermitted(String),
|
||||
ImportStarUsage(String),
|
||||
ImportStarUsage(String, String),
|
||||
ImportStarUsed(String),
|
||||
InvalidPrintSyntax,
|
||||
IsLiteral,
|
||||
LateFutureImport,
|
||||
@@ -219,6 +432,12 @@ pub enum CheckKind {
|
||||
UnusedVariable(String),
|
||||
UselessObjectInheritance(String),
|
||||
YieldOutsideFunction,
|
||||
// flake8-builtin
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-super
|
||||
SuperCallWithParameters,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
@@ -240,8 +459,10 @@ impl CheckKind {
|
||||
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportShadowedByLoopVar(_, _) => "ImportShadowedByLoopVar",
|
||||
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
|
||||
CheckKind::ImportStarUsage(_) => "ImportStarUsage",
|
||||
CheckKind::ImportStarUsage(_, _) => "ImportStarUsage",
|
||||
CheckKind::ImportStarUsed(_) => "ImportStarUsed",
|
||||
CheckKind::InvalidPrintSyntax => "InvalidPrintSyntax",
|
||||
CheckKind::IsLiteral => "IsLiteral",
|
||||
CheckKind::LateFutureImport => "LateFutureImport",
|
||||
@@ -269,6 +490,13 @@ impl CheckKind {
|
||||
CheckKind::UnusedVariable(_) => "UnusedVariable",
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
|
||||
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
|
||||
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,8 +518,10 @@ impl CheckKind {
|
||||
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportShadowedByLoopVar(_, _) => &CheckCode::F402,
|
||||
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
|
||||
CheckKind::ImportStarUsage(_) => &CheckCode::F403,
|
||||
CheckKind::ImportStarUsage(_, _) => &CheckCode::F405,
|
||||
CheckKind::ImportStarUsed(_) => &CheckCode::F403,
|
||||
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
|
||||
CheckKind::IsLiteral => &CheckCode::F632,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
@@ -314,9 +544,16 @@ impl CheckKind {
|
||||
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
|
||||
CheckKind::UndefinedName(_) => &CheckCode::F821,
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,17 +593,21 @@ impl CheckKind {
|
||||
CheckKind::FutureFeatureNotDefined(name) => {
|
||||
format!("future feature '{name}' is not defined")
|
||||
}
|
||||
CheckKind::IOError(name) => {
|
||||
format!("No such file or directory: `{name}`")
|
||||
}
|
||||
CheckKind::IOError(message) => message.clone(),
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::InvalidPrintSyntax => "use of >> is invalid with print function".to_string(),
|
||||
CheckKind::ImportShadowedByLoopVar(name, line) => {
|
||||
format!("import '{name}' from line {line} shadowed by loop variable")
|
||||
}
|
||||
CheckKind::ImportStarNotPermitted(name) => {
|
||||
format!("`from {name} import *` only allowed at module level")
|
||||
}
|
||||
CheckKind::ImportStarUsage(name) => {
|
||||
CheckKind::ImportStarUsed(name) => {
|
||||
format!("`from {name} import *` used; unable to detect undefined names")
|
||||
}
|
||||
CheckKind::ImportStarUsage(name, sources) => {
|
||||
format!("'{name}' may be undefined, or defined from star imports: {sources}")
|
||||
}
|
||||
CheckKind::IsLiteral => "use ==/!= to compare constant literals".to_string(),
|
||||
CheckKind::LateFutureImport => {
|
||||
"from __future__ imports must occur at the beginning of the file".to_string()
|
||||
@@ -443,6 +684,24 @@ impl CheckKind {
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"a `yield` or `yield from` statement outside of a function/method".to_string()
|
||||
}
|
||||
CheckKind::UnusedNOQA(code) => match code {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
Some(code) => format!("Unused `noqa` directive for: {code}"),
|
||||
},
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
}
|
||||
CheckKind::BuiltinArgumentShadowing(name) => {
|
||||
format!("Argument `{name}` is shadowing a python builtin")
|
||||
}
|
||||
CheckKind::BuiltinAttributeShadowing(name) => {
|
||||
format!("class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +709,10 @@ impl CheckKind {
|
||||
pub fn fixable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_)
|
||||
CheckKind::NoAssertEquals
|
||||
| CheckKind::UselessObjectInheritance(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
| CheckKind::SuperCallWithParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -458,8 +720,8 @@ impl CheckKind {
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Fix {
|
||||
pub content: String,
|
||||
pub start: Location,
|
||||
pub end: Location,
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
pub applied: bool,
|
||||
}
|
||||
|
||||
@@ -467,19 +729,16 @@ pub struct Fix {
|
||||
pub struct Check {
|
||||
pub kind: CheckKind,
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
pub fix: Option<Fix>,
|
||||
}
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?").expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
impl Check {
|
||||
pub fn new(kind: CheckKind, location: Location) -> Self {
|
||||
pub fn new(kind: CheckKind, span: Range) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
location,
|
||||
location: span.location,
|
||||
end_location: span.end_location,
|
||||
fix: None,
|
||||
}
|
||||
}
|
||||
@@ -487,25 +746,4 @@ impl Check {
|
||||
pub fn amend(&mut self, fix: Fix) {
|
||||
self.fix = Some(fix);
|
||||
}
|
||||
|
||||
pub fn is_inline_ignored(&self, line: &str) -> bool {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("codes") {
|
||||
Some(codes) => {
|
||||
for code in SPLIT_COMMA_REGEX
|
||||
.split(codes.as_str())
|
||||
.map(|code| code.trim())
|
||||
.filter(|code| !code.is_empty())
|
||||
{
|
||||
if code == self.kind.code().as_str() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
283
src/fs.rs
283
src/fs.rs
@@ -1,35 +1,57 @@
|
||||
use std::env;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use glob::Pattern;
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::debug;
|
||||
use path_absolutize::path_dedot;
|
||||
use path_absolutize::Absolutize;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
fn is_excluded(path: &Path, exclude: &[Pattern]) -> bool {
|
||||
// Check the basename.
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if let Some(file_name) = file_name.to_str() {
|
||||
for pattern in exclude {
|
||||
if pattern.matches(file_name) {
|
||||
use crate::checks::CheckCode;
|
||||
use crate::settings::{FilePattern, PerFileIgnore};
|
||||
|
||||
/// Extract the absolute path and basename (as strings) from a Path.
|
||||
fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
|
||||
let file_path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?;
|
||||
let file_basename = path
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?;
|
||||
Ok((file_path, file_basename))
|
||||
}
|
||||
|
||||
fn is_excluded<'a, T>(file_path: &str, file_basename: &str, exclude: T) -> bool
|
||||
where
|
||||
T: Iterator<Item = &'a FilePattern>,
|
||||
{
|
||||
for pattern in exclude {
|
||||
match pattern {
|
||||
FilePattern::Simple(basename) => {
|
||||
if *basename == file_basename {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the complete path.
|
||||
if let Some(file_name) = path.to_str() {
|
||||
for pattern in exclude {
|
||||
if pattern.matches(file_name) {
|
||||
return true;
|
||||
FilePattern::Complex(absolute, basename) => {
|
||||
if absolute.matches(file_path) {
|
||||
return true;
|
||||
}
|
||||
if basename
|
||||
.as_ref()
|
||||
.map(|pattern| pattern.matches(file_basename))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -39,50 +61,107 @@ fn is_included(path: &Path) -> bool {
|
||||
}
|
||||
|
||||
pub fn iter_python_files<'a>(
|
||||
path: &'a PathBuf,
|
||||
exclude: &'a [Pattern],
|
||||
extend_exclude: &'a [Pattern],
|
||||
) -> impl Iterator<Item = DirEntry> + 'a {
|
||||
path: &'a Path,
|
||||
exclude: &'a [FilePattern],
|
||||
extend_exclude: &'a [FilePattern],
|
||||
) -> impl Iterator<Item = Result<DirEntry, walkdir::Error>> + 'a {
|
||||
// Run some checks over the provided patterns, to enable optimizations below.
|
||||
let has_exclude = !exclude.is_empty();
|
||||
let has_extend_exclude = !extend_exclude.is_empty();
|
||||
let exclude_simple = exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
let extend_exclude_simple = extend_exclude
|
||||
.iter()
|
||||
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
|
||||
|
||||
WalkDir::new(normalize_path(path))
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_entry(|entry| {
|
||||
if exclude.is_empty() && extend_exclude.is_empty() {
|
||||
.filter_entry(move |entry| {
|
||||
if !has_exclude && !has_extend_exclude {
|
||||
return true;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
if is_excluded(path, exclude) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
false
|
||||
} else if is_excluded(path, extend_exclude) {
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
match extract_path_names(path) {
|
||||
Ok((file_path, file_basename)) => {
|
||||
let file_type = entry.file_type();
|
||||
|
||||
if has_exclude
|
||||
&& (!exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, exclude.iter())
|
||||
{
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
false
|
||||
} else if has_extend_exclude
|
||||
&& (!extend_exclude_simple || file_type.is_dir())
|
||||
&& is_excluded(file_path, file_basename, extend_exclude.iter())
|
||||
{
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Ignored path due to error in parsing: {:?}", path);
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| {
|
||||
let path = entry.path();
|
||||
is_included(path)
|
||||
entry.as_ref().map_or(true, |entry| {
|
||||
(entry.depth() == 0 || is_included(entry.path()))
|
||||
&& !entry.file_type().is_dir()
|
||||
&& !(entry.file_type().is_symlink() && entry.path().is_dir())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalize_path(path: &PathBuf) -> PathBuf {
|
||||
if path == Path::new(".") || path == Path::new("..") {
|
||||
return path.clone();
|
||||
}
|
||||
if let Ok(path) = path.absolutize() {
|
||||
if let Ok(root) = env::current_dir() {
|
||||
if let Ok(path) = path.strip_prefix(root) {
|
||||
return Path::new(".").join(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
path.clone()
|
||||
/// Create tree set with codes matching the pattern/code pairs.
|
||||
pub fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [PerFileIgnore],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
let (file_path, file_basename) = extract_path_names(path)?;
|
||||
Ok(pattern_code_pairs
|
||||
.iter()
|
||||
.filter(|pattern_code_pair| {
|
||||
is_excluded(
|
||||
file_path,
|
||||
file_basename,
|
||||
[&pattern_code_pair.pattern].into_iter(),
|
||||
)
|
||||
})
|
||||
.map(|pattern_code_pair| &pattern_code_pair.code)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Convert any path to an absolute path (based on the current working directory).
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize() {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
path.to_path_buf()
|
||||
}
|
||||
|
||||
/// Convert any path to an absolute path (based on the specified project root).
|
||||
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize_from(project_root) {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
path.to_path_buf()
|
||||
}
|
||||
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
pub fn relativize_path(path: &Path) -> Cow<str> {
|
||||
if let Ok(path) = path.strip_prefix(path_dedot::CWD.deref()) {
|
||||
return path.to_string_lossy();
|
||||
}
|
||||
path.to_string_lossy()
|
||||
}
|
||||
|
||||
/// Read a file's contents from disk.
|
||||
pub fn read_file(path: &Path) -> Result<String> {
|
||||
let file = File::open(path)?;
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
@@ -95,53 +174,95 @@ pub fn read_file(path: &Path) -> Result<String> {
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use glob::Pattern;
|
||||
use anyhow::Result;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::fs::{is_excluded, is_included};
|
||||
use crate::fs::{extract_path_names, is_excluded, is_included};
|
||||
use crate::settings::FilePattern;
|
||||
|
||||
#[test]
|
||||
fn inclusions() {
|
||||
let path = Path::new("foo/bar/baz.py");
|
||||
assert!(is_included(path));
|
||||
let path = Path::new("foo/bar/baz.py").absolutize().unwrap();
|
||||
assert!(is_included(&path));
|
||||
|
||||
let path = Path::new("foo/bar/baz.pyi");
|
||||
assert!(is_included(path));
|
||||
let path = Path::new("foo/bar/baz.pyi").absolutize().unwrap();
|
||||
assert!(is_included(&path));
|
||||
|
||||
let path = Path::new("foo/bar/baz.js");
|
||||
assert!(!is_included(path));
|
||||
let path = Path::new("foo/bar/baz.js").absolutize().unwrap();
|
||||
assert!(!is_included(&path));
|
||||
|
||||
let path = Path::new("foo/bar/baz");
|
||||
assert!(!is_included(path));
|
||||
let path = Path::new("foo/bar/baz").absolutize().unwrap();
|
||||
assert!(!is_included(&path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusions() {
|
||||
let path = Path::new("foo");
|
||||
let exclude = vec![Pattern::new("foo").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
fn exclusions() -> Result<()> {
|
||||
let project_root = Path::new("/tmp/");
|
||||
|
||||
let path = Path::new("foo/bar");
|
||||
let exclude = vec![Pattern::new("bar").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
let path = Path::new("foo").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py");
|
||||
let exclude = vec![Pattern::new("baz.py").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"bar",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar");
|
||||
let exclude = vec![Pattern::new("foo/bar").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz.py",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py");
|
||||
let exclude = vec![Pattern::new("foo/bar/baz.py").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
let path = Path::new("foo/bar").absolutize_from(project_root).unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py");
|
||||
let exclude = vec![Pattern::new("foo/bar/*.py").unwrap()];
|
||||
assert!(is_excluded(path, &exclude));
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/baz.py",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py");
|
||||
let exclude = vec![Pattern::new("baz").unwrap()];
|
||||
assert!(!is_excluded(path, &exclude));
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"foo/bar/*.py",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
let path = Path::new("foo/bar/baz.py")
|
||||
.absolutize_from(project_root)
|
||||
.unwrap();
|
||||
let exclude = vec![FilePattern::from_user(
|
||||
"baz",
|
||||
&Some(project_root.to_path_buf()),
|
||||
)];
|
||||
let (file_path, file_basename) = extract_path_names(&path)?;
|
||||
assert!(!is_excluded(file_path, file_basename, exclude.iter()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
61
src/lib.rs
61
src/lib.rs
@@ -1,4 +1,13 @@
|
||||
extern crate core;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::autofix::fixer::Mode;
|
||||
use crate::linter::{check_path, tokenize};
|
||||
use crate::message::Message;
|
||||
use crate::settings::Settings;
|
||||
|
||||
mod ast;
|
||||
mod autofix;
|
||||
@@ -10,7 +19,55 @@ pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
pub mod printer;
|
||||
mod pyproject;
|
||||
pub mod pyproject;
|
||||
mod python;
|
||||
pub mod settings;
|
||||
|
||||
/// Run ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
|
||||
match &project_root {
|
||||
Some(path) => debug!("Found project root at: {:?}", path),
|
||||
None => debug!("Unable to identify project root; assuming current directory..."),
|
||||
};
|
||||
let pyproject = pyproject::find_pyproject_toml(&project_root);
|
||||
match &pyproject {
|
||||
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_pyproject(pyproject, project_root)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
contents,
|
||||
tokens,
|
||||
&noqa_line_for,
|
||||
&settings,
|
||||
&Mode::None,
|
||||
)?;
|
||||
|
||||
// Convert to messages.
|
||||
let messages: Vec<Message> = checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
493
src/linter.rs
493
src/linter.rs
@@ -2,20 +2,38 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use rustpython_parser::parser;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
use rustpython_parser::{lexer, parser};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::autofix::fixer::fix_file;
|
||||
use crate::check_ast::check_ast;
|
||||
use crate::check_lines::check_lines;
|
||||
use crate::checks::{Check, LintSource};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
|
||||
use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::settings::Settings;
|
||||
use crate::{cache, fs};
|
||||
use crate::{cache, fs, noqa};
|
||||
|
||||
fn check_path(
|
||||
/// Collect tokens up to and including the first error.
|
||||
pub(crate) fn tokenize(contents: &str) -> Vec<LexResult> {
|
||||
let mut tokens: Vec<LexResult> = vec![];
|
||||
for tok in lexer::make_tokenizer(contents) {
|
||||
let is_err = tok.is_err();
|
||||
tokens.push(tok);
|
||||
if is_err {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
pub(crate) fn check_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
tokens: Vec<LexResult>,
|
||||
noqa_line_for: &[usize],
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
@@ -28,12 +46,37 @@ fn check_path(
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
|
||||
{
|
||||
let python_ast = parser::parse_program(contents, "<filename>")?;
|
||||
checks.extend(check_ast(&python_ast, contents, settings, autofix, path));
|
||||
match parser::parse_program_tokens(tokens, "<filename>") {
|
||||
Ok(python_ast) => {
|
||||
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
|
||||
}
|
||||
Err(parse_error) => {
|
||||
if settings.select.contains(&CheckCode::E999) {
|
||||
checks.push(Check::new(
|
||||
CheckKind::SyntaxError(parse_error.error.to_string()),
|
||||
Range {
|
||||
location: parse_error.location,
|
||||
end_location: parse_error.location,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the lines-based checks.
|
||||
check_lines(&mut checks, contents, settings);
|
||||
check_lines(&mut checks, contents, noqa_line_for, settings, autofix);
|
||||
|
||||
// Create path ignores.
|
||||
if !checks.is_empty() && !settings.per_file_ignores.is_empty() {
|
||||
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores)?;
|
||||
if !ignores.is_empty() {
|
||||
return Ok(checks
|
||||
.into_iter()
|
||||
.filter(|check| !ignores.contains(check.kind.code()))
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(checks)
|
||||
}
|
||||
@@ -55,8 +98,14 @@ pub fn lint_path(
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&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, settings, autofix)?;
|
||||
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
|
||||
|
||||
// Apply autofix.
|
||||
if matches!(autofix, fixer::Mode::Apply) {
|
||||
@@ -70,6 +119,7 @@ pub fn lint_path(
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.collect();
|
||||
@@ -78,18 +128,43 @@ pub fn lint_path(
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
&contents,
|
||||
tokens,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
&fixer::Mode::None,
|
||||
)?;
|
||||
|
||||
add_noqa(&checks, &contents, &noqa_line_for, path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::fs;
|
||||
use crate::linter;
|
||||
use crate::linter::tokenize;
|
||||
use crate::settings;
|
||||
use crate::{fs, noqa};
|
||||
|
||||
fn check_path(
|
||||
path: &Path,
|
||||
@@ -97,19 +172,16 @@ mod tests {
|
||||
autofix: &fixer::Mode,
|
||||
) -> Result<Vec<Check>> {
|
||||
let contents = fs::read_file(path)?;
|
||||
linter::check_path(path, &contents, settings, autofix)
|
||||
let tokens: Vec<LexResult> = tokenize(&contents);
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E402.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E402]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -121,12 +193,7 @@ mod tests {
|
||||
fn e501() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E501.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E501]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E501),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -138,12 +205,7 @@ mod tests {
|
||||
fn e711() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E711.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E711]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E711),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -155,12 +217,7 @@ mod tests {
|
||||
fn e712() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E712.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E712]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E712),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -172,12 +229,7 @@ mod tests {
|
||||
fn e713() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E713.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E713]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E713),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -189,12 +241,7 @@ mod tests {
|
||||
fn e721() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E721.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E721]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E721),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -206,12 +253,7 @@ mod tests {
|
||||
fn e722() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E722.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E722]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E722),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -223,12 +265,7 @@ mod tests {
|
||||
fn e714() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E714.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E714]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E714),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -240,12 +277,7 @@ mod tests {
|
||||
fn e731() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E731.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E731]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E731),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -257,12 +289,7 @@ mod tests {
|
||||
fn e741() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E741.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E741]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E741),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -274,12 +301,7 @@ mod tests {
|
||||
fn e742() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E742.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E742]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E742),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -291,12 +313,7 @@ mod tests {
|
||||
fn e743() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E743.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::E743]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::E743),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -308,12 +325,19 @@ mod tests {
|
||||
fn f401() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F401.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F401]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F401),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F402.py"),
|
||||
&settings::Settings::for_rule(CheckCode::F402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -325,12 +349,7 @@ mod tests {
|
||||
fn f403() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F403.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F403]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F403),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -342,12 +361,19 @@ mod tests {
|
||||
fn f404() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F404.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F404]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F404),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f405() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F405.py"),
|
||||
&settings::Settings::for_rule(CheckCode::F405),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -359,12 +385,7 @@ mod tests {
|
||||
fn f406() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F406.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F406]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F406),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -376,12 +397,7 @@ mod tests {
|
||||
fn f407() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F407.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F407]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F407),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -393,12 +409,7 @@ mod tests {
|
||||
fn f541() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F541.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F541]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F541),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -410,12 +421,7 @@ mod tests {
|
||||
fn f601() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F601.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F601]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F601),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -427,12 +433,7 @@ mod tests {
|
||||
fn f602() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F602.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F602]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F602),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -444,12 +445,7 @@ mod tests {
|
||||
fn f622() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F622.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F622]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F622),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -461,12 +457,7 @@ mod tests {
|
||||
fn f631() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F631.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F631]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F631),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -478,12 +469,7 @@ mod tests {
|
||||
fn f632() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F632.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F632]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F632),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -495,12 +481,7 @@ mod tests {
|
||||
fn f633() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F633.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F633]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F633),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -512,12 +493,7 @@ mod tests {
|
||||
fn f634() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F634.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F634]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F634),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -529,12 +505,7 @@ mod tests {
|
||||
fn f701() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F701.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F701]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F701),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -546,12 +517,7 @@ mod tests {
|
||||
fn f702() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F702.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F702]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F702),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -563,12 +529,7 @@ mod tests {
|
||||
fn f704() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F704.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F704]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F704),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -580,12 +541,7 @@ mod tests {
|
||||
fn f706() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F706.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F706]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F706),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -597,12 +553,7 @@ mod tests {
|
||||
fn f707() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F707.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F707]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F707),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -614,12 +565,7 @@ mod tests {
|
||||
fn f722() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F722.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F722]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F722),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -631,12 +577,7 @@ mod tests {
|
||||
fn f821() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F821.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F821]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F821),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -648,12 +589,7 @@ mod tests {
|
||||
fn f822() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F822.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F822]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F822),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -665,12 +601,7 @@ mod tests {
|
||||
fn f823() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F823.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F823]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F823),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -682,12 +613,7 @@ mod tests {
|
||||
fn f831() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F831.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F831]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F831),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -697,13 +623,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn f841() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F841.py"),
|
||||
&settings::Settings::for_rule(CheckCode::F841),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f841_dummy_variable_rgx() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F841.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F841]),
|
||||
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
|
||||
..settings::Settings::for_rule(CheckCode::F841)
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
@@ -716,12 +652,19 @@ mod tests {
|
||||
fn f901() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/F901.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F901]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::F901),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -733,12 +676,7 @@ mod tests {
|
||||
fn r001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R001.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::R001]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::R001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -750,12 +688,7 @@ mod tests {
|
||||
fn r002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R002.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::R002]),
|
||||
},
|
||||
&settings::Settings::for_rule(CheckCode::R002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -767,12 +700,7 @@ mod tests {
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F821, CheckCode::F822]),
|
||||
},
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -784,12 +712,67 @@ mod tests {
|
||||
fn future_annotations() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F401, CheckCode::F821]),
|
||||
},
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e999() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/E999.py"),
|
||||
&settings::Settings::for_rule(CheckCode::E999),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A001.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A002.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a003() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/A003.py"),
|
||||
&settings::Settings::for_rule(CheckCode::A003),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spr001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/SPR001.py"),
|
||||
&settings::Settings::for_rule(CheckCode::SPR001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
|
||||
244
src/main.rs
244
src/main.rs
@@ -1,70 +1,95 @@
|
||||
use std::path::PathBuf;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueHint};
|
||||
use clap::{command, Parser};
|
||||
use colored::Colorize;
|
||||
use glob::Pattern;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
use regex::Regex;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use ::ruff::cache;
|
||||
use ::ruff::checks::CheckCode;
|
||||
use ::ruff::checks::CheckKind;
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::add_noqa_to_path;
|
||||
use ::ruff::linter::lint_path;
|
||||
use ::ruff::logging::set_up_logging;
|
||||
use ::ruff::message::Message;
|
||||
use ::ruff::printer::{Printer, SerializationFormat};
|
||||
use ::ruff::settings::Settings;
|
||||
use ::ruff::pyproject::{self, StrCheckCodePair};
|
||||
use ::ruff::settings::CurrentSettings;
|
||||
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
|
||||
use ::ruff::tell_user;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(name = format!("{CARGO_PKG_NAME} (v{CARGO_PKG_VERSION})"))]
|
||||
#[clap(about = "An extremely fast Python linter.", long_about = None)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
struct Cli {
|
||||
#[clap(parse(from_os_str), value_hint = ValueHint::AnyPath, required = true)]
|
||||
#[arg(required = true)]
|
||||
files: Vec<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
quiet: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[clap(short, long, action)]
|
||||
#[arg(short, long)]
|
||||
no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[clap(long, multiple = true)]
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[clap(long, multiple = true)]
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[clap(long, multiple = true)]
|
||||
exclude: Vec<Pattern>,
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
#[clap(long, multiple = true)]
|
||||
extend_exclude: Vec<Pattern>,
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
per_file_ignores: Vec<StrCheckCodePair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[clap(long, arg_enum, default_value_t=SerializationFormat::Text)]
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[arg(long)]
|
||||
show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
add_noqa: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
dummy_variable_rgx: Option<Regex>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
@@ -91,6 +116,22 @@ fn check_for_updates() {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(settings: Settings) {
|
||||
println!("{:#?}", CurrentSettings::from_settings(settings));
|
||||
}
|
||||
|
||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
let mut entries: Vec<DirEntry> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.flatten()
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.path().cmp(b.path()));
|
||||
for entry in entries {
|
||||
println!("{}", entry.path().to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
@@ -99,7 +140,7 @@ fn run_once(
|
||||
) -> Result<Vec<Message>> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<DirEntry> = files
|
||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.collect();
|
||||
@@ -110,16 +151,34 @@ fn run_once(
|
||||
let mut messages: Vec<Message> = paths
|
||||
.par_iter()
|
||||
.map(|entry| {
|
||||
lint_path(entry.path(), settings, &cache.into(), &autofix.into()).unwrap_or_else(|e| {
|
||||
if settings.select.contains(&CheckCode::E999) {
|
||||
vec![Message {
|
||||
kind: CheckKind::SyntaxError(e.to_string()),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
filename: entry.path().to_string_lossy().to_string(),
|
||||
}]
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
lint_path(path, settings, &cache.into(), &autofix.into())
|
||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
||||
}
|
||||
Err(e) => Err((
|
||||
e.path().map(Path::to_owned),
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
}
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = path {
|
||||
if settings.select.contains(&CheckCode::E902) {
|
||||
vec![Message {
|
||||
kind: CheckKind::IOError(message),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
end_location: Default::default(),
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
}]
|
||||
} else {
|
||||
error!("Failed to check {}: {message}", path.to_string_lossy());
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
error!("Failed to check {}: {e:?}", entry.path().to_string_lossy());
|
||||
error!("{message}");
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
@@ -127,19 +186,6 @@ fn run_once(
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if settings.select.contains(&CheckCode::E902) {
|
||||
for file in files {
|
||||
if !file.exists() {
|
||||
messages.push(Message {
|
||||
kind: CheckKind::IOError(file.to_string_lossy().to_string()),
|
||||
fixed: false,
|
||||
location: Default::default(),
|
||||
filename: file.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort_unstable();
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked files in: {:?}", duration);
|
||||
@@ -147,37 +193,123 @@ fn run_once(
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||
.iter()
|
||||
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
|
||||
.collect();
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
let start = Instant::now();
|
||||
let modifications: usize = paths
|
||||
.par_iter()
|
||||
.map(|entry| match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
add_noqa_to_path(path, settings)
|
||||
}
|
||||
Err(_) => Ok(0),
|
||||
})
|
||||
.flatten()
|
||||
.sum();
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Added noqa to files in: {:?}", duration);
|
||||
|
||||
Ok(modifications)
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<ExitCode> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
set_up_logging(cli.verbose)?;
|
||||
|
||||
let mut settings = Settings::from_paths(&cli.files);
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&cli.files);
|
||||
match &project_root {
|
||||
Some(path) => debug!("Found project root at: {:?}", path),
|
||||
None => debug!("Unable to identify project root; assuming current directory..."),
|
||||
};
|
||||
let pyproject = pyproject::find_pyproject_toml(&project_root);
|
||||
match &pyproject {
|
||||
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let mut printer = Printer::new(cli.format);
|
||||
// Parse the settings from the pyproject.toml and command-line arguments.
|
||||
let exclude: Vec<FilePattern> = cli
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
let extend_exclude: Vec<FilePattern> = cli
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect();
|
||||
let per_file_ignores: Vec<PerFileIgnore> = cli
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
|
||||
let mut settings = Settings::from_pyproject(pyproject, project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
settings.exclude = exclude;
|
||||
}
|
||||
if !extend_exclude.is_empty() {
|
||||
settings.extend_exclude = extend_exclude;
|
||||
}
|
||||
if !per_file_ignores.is_empty() {
|
||||
settings.per_file_ignores = per_file_ignores;
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
settings.clear();
|
||||
settings.select(cli.select);
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
settings.select(cli.extend_select);
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
settings.ignore(&cli.ignore);
|
||||
}
|
||||
if !cli.exclude.is_empty() {
|
||||
settings.exclude = cli.exclude;
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
settings.ignore(&cli.extend_ignore);
|
||||
}
|
||||
if !cli.extend_exclude.is_empty() {
|
||||
settings.extend_exclude = cli.extend_exclude;
|
||||
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
|
||||
settings.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
eprintln!("Error: specify --show-settings or show-files (not both).");
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_files {
|
||||
show_files(&cli.files, &settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(settings);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
cache::init()?;
|
||||
|
||||
let mut printer = Printer::new(cli.format, cli.verbose);
|
||||
if cli.watch {
|
||||
if cli.fix {
|
||||
println!("Warning: --fix is not enabled in watch mode.");
|
||||
eprintln!("Warning: --fix is not enabled in watch mode.");
|
||||
}
|
||||
|
||||
if cli.add_noqa {
|
||||
eprintln!("Warning: --no-qa is not enabled in watch mode.");
|
||||
}
|
||||
|
||||
if cli.format != SerializationFormat::Text {
|
||||
println!("Warning: --format 'text' is used in watch mode.");
|
||||
eprintln!("Warning: --format 'text' is used in watch mode.");
|
||||
}
|
||||
|
||||
// Perform an initial run instantly.
|
||||
@@ -214,6 +346,11 @@ fn inner_main() -> Result<ExitCode> {
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
} else if cli.add_noqa {
|
||||
let modifications = add_noqa(&cli.files, &settings)?;
|
||||
if modifications > 0 {
|
||||
println!("Added {modifications} noqa directives.");
|
||||
}
|
||||
} else {
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
|
||||
if !cli.quiet {
|
||||
@@ -234,6 +371,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
fn main() -> ExitCode {
|
||||
match inner_main() {
|
||||
Ok(code) => code,
|
||||
Err(_) => ExitCode::FAILURE,
|
||||
Err(err) => {
|
||||
eprintln!("{} {:?}", "error".red().bold(), err);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use colored::Colorize;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::CheckKind;
|
||||
use crate::fs::relativize_path;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub kind: CheckKind,
|
||||
pub fixed: bool,
|
||||
pub location: Location,
|
||||
pub end_location: Location,
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
@@ -36,7 +39,7 @@ impl fmt::Display for Message {
|
||||
write!(
|
||||
f,
|
||||
"{}{}{}{}{}{} {} {}",
|
||||
self.filename.white().bold(),
|
||||
relativize_path(Path::new(&self.filename)).white().bold(),
|
||||
":".cyan(),
|
||||
self.location.row(),
|
||||
":".cyan(),
|
||||
|
||||
298
src/noqa.rs
Normal file
298
src/noqa.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
.expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Directive<'a> {
|
||||
None,
|
||||
All(usize, usize),
|
||||
Codes(usize, usize, Vec<&'a str>),
|
||||
}
|
||||
|
||||
pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
match NO_QA_REGEX.captures(line) {
|
||||
Some(caps) => match caps.name("noqa") {
|
||||
Some(noqa) => match caps.name("codes") {
|
||||
Some(codes) => Directive::Codes(
|
||||
noqa.start(),
|
||||
noqa.end(),
|
||||
SPLIT_COMMA_REGEX
|
||||
.split(codes.as_str())
|
||||
.map(|code| code.trim())
|
||||
.filter(|code| !code.is_empty())
|
||||
.collect(),
|
||||
),
|
||||
None => Directive::All(noqa.start(), noqa.end()),
|
||||
},
|
||||
None => Directive::None,
|
||||
},
|
||||
None => Directive::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
|
||||
let mut noqa_line_for: Vec<usize> = vec![];
|
||||
|
||||
let mut last_is_string = false;
|
||||
let mut last_seen = usize::MIN;
|
||||
let mut min_line = usize::MAX;
|
||||
let mut max_line = usize::MIN;
|
||||
|
||||
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 last_is_string {
|
||||
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
|
||||
} else {
|
||||
for i in (min_line - 1)..(max_line) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
min_line = usize::MAX;
|
||||
max_line = usize::MIN;
|
||||
} else {
|
||||
// Handle empty lines.
|
||||
if start.row() > last_seen {
|
||||
for i in last_seen..(start.row() - 1) {
|
||||
noqa_line_for.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
min_line = min(min_line, start.row());
|
||||
max_line = max(max_line, end.row());
|
||||
}
|
||||
last_seen = start.row();
|
||||
last_is_string = matches!(tok, Tok::String { .. });
|
||||
}
|
||||
|
||||
noqa_line_for
|
||||
}
|
||||
|
||||
fn add_noqa_inner(
|
||||
checks: &Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
) -> Result<(usize, String)> {
|
||||
let lines: Vec<&str> = contents.lines().collect();
|
||||
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
|
||||
for lineno in 0..lines.len() {
|
||||
let mut codes: BTreeSet<&CheckCode> = BTreeSet::new();
|
||||
for check in checks {
|
||||
if check.location.row() == lineno + 1 {
|
||||
codes.insert(check.kind.code());
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the noqa (logical) line number for the current (physical) line.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
if !codes.is_empty() {
|
||||
let matches = matches_by_line
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(BTreeSet::new);
|
||||
matches.append(&mut codes);
|
||||
}
|
||||
}
|
||||
|
||||
let mut count: usize = 0;
|
||||
let mut output = "".to_string();
|
||||
for (lineno, line) in lines.iter().enumerate() {
|
||||
match matches_by_line.get(&lineno) {
|
||||
None => {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
}
|
||||
Some(codes) => {
|
||||
match extract_noqa_directive(line) {
|
||||
Directive::None => {
|
||||
output.push_str(line);
|
||||
}
|
||||
Directive::All(start, _) => output.push_str(&line[..start]),
|
||||
Directive::Codes(start, _, _) => output.push_str(&line[..start]),
|
||||
};
|
||||
let codes: Vec<&str> = codes.iter().map(|code| code.as_str()).collect();
|
||||
output.push_str(" # noqa: ");
|
||||
output.push_str(&codes.join(", "));
|
||||
output.push('\n');
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((count, output))
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
checks: &Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
path: &Path,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for)?;
|
||||
fs::write(path, output)?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ast::types::Range;
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::noqa::{add_noqa_inner, extract_noqa_line_for};
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
println!("{:?}", extract_noqa_line_for(&lxr));
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4, 5, 6]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modification() -> Result<()> {
|
||||
let checks = vec![];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output.trim(), contents.trim());
|
||||
|
||||
let checks = vec![Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
)];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
|
||||
|
||||
let checks = vec![
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
let checks = vec![
|
||||
Check::new(
|
||||
CheckKind::AmbiguousVariableName("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
),
|
||||
Check::new(
|
||||
CheckKind::UnusedVariable("x".to_string()),
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
),
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = vec![1];
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for)?;
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
use colored::Colorize;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::ValueEnum;
|
||||
use colored::Colorize;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
use crate::message::Message;
|
||||
use crate::tell_user;
|
||||
|
||||
@@ -12,13 +14,25 @@ pub enum SerializationFormat {
|
||||
Json,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExpandedMessage<'a> {
|
||||
kind: &'a CheckKind,
|
||||
code: &'a CheckCode,
|
||||
message: String,
|
||||
fixed: bool,
|
||||
location: Location,
|
||||
end_location: Location,
|
||||
filename: &'a String,
|
||||
}
|
||||
|
||||
pub struct Printer {
|
||||
format: SerializationFormat,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
pub fn new(format: SerializationFormat) -> Self {
|
||||
Self { format }
|
||||
pub fn new(format: SerializationFormat, verbose: bool) -> Self {
|
||||
Self { format, verbose }
|
||||
}
|
||||
|
||||
pub fn write_once(&mut self, messages: &[Message]) -> Result<()> {
|
||||
@@ -31,7 +45,23 @@ impl Printer {
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Json => {
|
||||
println!("{}", serde_json::to_string_pretty(&messages)?)
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&messages
|
||||
.iter()
|
||||
.map(|m| ExpandedMessage {
|
||||
kind: &m.kind,
|
||||
code: m.kind.code(),
|
||||
message: m.kind.body(),
|
||||
fixed: m.fixed,
|
||||
location: m.location,
|
||||
end_location: m.end_location,
|
||||
filename: &m.filename,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
)?
|
||||
)
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
if !fixed.is_empty() {
|
||||
@@ -40,7 +70,7 @@ impl Printer {
|
||||
outstanding.len(),
|
||||
fixed.len()
|
||||
)
|
||||
} else {
|
||||
} else if !outstanding.is_empty() || self.verbose {
|
||||
println!("Found {} error(s).", outstanding.len())
|
||||
}
|
||||
|
||||
|
||||
211
src/pyproject.rs
211
src/pyproject.rs
@@ -1,33 +1,26 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use common_path::common_path_all;
|
||||
use log::debug;
|
||||
use serde::Deserialize;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::de;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::fs;
|
||||
|
||||
pub fn load_config(paths: &[PathBuf]) -> Config {
|
||||
match find_project_root(paths) {
|
||||
Some(project_root) => match find_pyproject_toml(&project_root) {
|
||||
Some(path) => {
|
||||
debug!("Found pyproject.toml at: {:?}", path);
|
||||
match parse_pyproject_toml(&path) {
|
||||
Ok(pyproject) => pyproject
|
||||
.tool
|
||||
.and_then(|tool| tool.ruff)
|
||||
.unwrap_or_default(),
|
||||
Err(e) => {
|
||||
println!("Failed to load pyproject.toml: {:?}", e);
|
||||
println!("Falling back to default configuration...");
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
},
|
||||
None => Default::default(),
|
||||
pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
|
||||
match pyproject {
|
||||
Some(pyproject) => Ok(parse_pyproject_toml(pyproject)?
|
||||
.tool
|
||||
.and_then(|tool| tool.ruff)
|
||||
.unwrap_or_default()),
|
||||
None => {
|
||||
eprintln!("No pyproject.toml found.");
|
||||
eprintln!("Falling back to default configuration...");
|
||||
Ok(Default::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +28,54 @@ pub fn load_config(paths: &[PathBuf]) -> Config {
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Config {
|
||||
pub line_length: Option<usize>,
|
||||
pub exclude: Option<Vec<PathBuf>>,
|
||||
pub extend_exclude: Option<Vec<PathBuf>>,
|
||||
pub exclude: Option<Vec<String>>,
|
||||
pub extend_exclude: Option<Vec<String>>,
|
||||
pub select: Option<Vec<CheckCode>>,
|
||||
pub ignore: Option<Vec<CheckCode>>,
|
||||
pub per_file_ignores: Option<Vec<StrCheckCodePair>>,
|
||||
pub dummy_variable_rgx: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StrCheckCodePair {
|
||||
pub pattern: String,
|
||||
pub code: CheckCode,
|
||||
}
|
||||
|
||||
impl StrCheckCodePair {
|
||||
const EXPECTED_PATTERN: &'static str = "<FilePattern>:<CheckCode> pattern";
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for StrCheckCodePair {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str_result = String::deserialize(deserializer)?;
|
||||
Self::from_str(str_result.as_str()).map_err(|_| {
|
||||
de::Error::invalid_value(
|
||||
de::Unexpected::Str(str_result.as_str()),
|
||||
&Self::EXPECTED_PATTERN,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StrCheckCodePair {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
let (pattern_str, code_string) = {
|
||||
let tokens = string.split(':').collect::<Vec<_>>();
|
||||
if tokens.len() != 2 {
|
||||
return Err(anyhow!("Expected {}", Self::EXPECTED_PATTERN));
|
||||
}
|
||||
(tokens[0], tokens[1])
|
||||
};
|
||||
let code = CheckCode::from_str(code_string)?;
|
||||
let pattern = pattern_str.into();
|
||||
Ok(Self { pattern, code })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
@@ -56,20 +93,34 @@ fn parse_pyproject_toml(path: &Path) -> Result<PyProject> {
|
||||
toml::from_str(&contents).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn find_pyproject_toml(path: &Path) -> Option<PathBuf> {
|
||||
let path_pyproject_toml = path.join("pyproject.toml");
|
||||
if path_pyproject_toml.is_file() {
|
||||
return Some(path_pyproject_toml);
|
||||
pub fn find_pyproject_toml(path: &Option<PathBuf>) -> Option<PathBuf> {
|
||||
if let Some(path) = path {
|
||||
let path_pyproject_toml = path.join("pyproject.toml");
|
||||
if path_pyproject_toml.is_file() {
|
||||
return Some(path_pyproject_toml);
|
||||
}
|
||||
}
|
||||
|
||||
find_user_pyproject_toml()
|
||||
}
|
||||
|
||||
fn find_user_pyproject_toml() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".ruff"))
|
||||
let mut path = dirs::config_dir()?;
|
||||
path.push("ruff");
|
||||
path.push("pyproject.toml");
|
||||
if path.is_file() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
|
||||
if let Some(prefix) = common_path_all(sources.iter().map(PathBuf::as_path)) {
|
||||
pub fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
|
||||
let absolute_sources: Vec<PathBuf> = sources
|
||||
.iter()
|
||||
.flat_map(|source| source.absolutize().map(|path| path.to_path_buf()))
|
||||
.collect();
|
||||
if let Some(prefix) = common_path_all(absolute_sources.iter().map(PathBuf::as_path)) {
|
||||
for directory in prefix.ancestors() {
|
||||
if directory.join(".git").is_dir() {
|
||||
return Some(directory.to_path_buf());
|
||||
@@ -88,10 +139,13 @@ fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::StrCheckCodePair;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::pyproject::{
|
||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Config, PyProject, Tools,
|
||||
@@ -124,6 +178,8 @@ mod tests {
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -144,6 +200,8 @@ line-length = 79
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -160,10 +218,12 @@ exclude = ["foo.py"]
|
||||
Some(Tools {
|
||||
ruff: Some(Config {
|
||||
line_length: None,
|
||||
exclude: Some(vec![Path::new("foo.py").to_path_buf()]),
|
||||
exclude: Some(vec!["foo.py".to_string()]),
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -184,6 +244,8 @@ select = ["E501"]
|
||||
extend_exclude: None,
|
||||
select: Some(vec![CheckCode::E501]),
|
||||
ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -204,6 +266,8 @@ ignore = ["E501"]
|
||||
extend_exclude: None,
|
||||
select: None,
|
||||
ignore: Some(vec![CheckCode::E501]),
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -241,13 +305,15 @@ other-attribute = 1
|
||||
|
||||
#[test]
|
||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
||||
let cwd = current_dir()?;
|
||||
let project_root =
|
||||
find_project_root(&[PathBuf::from("resources/test/fixtures/__init__.py")])
|
||||
.expect("Unable to find project root.");
|
||||
assert_eq!(project_root, Path::new("resources/test/fixtures"));
|
||||
assert_eq!(project_root, cwd.join("resources/test/fixtures"));
|
||||
|
||||
let path = find_pyproject_toml(&project_root).expect("Unable to find pyproject.toml.");
|
||||
assert_eq!(path, Path::new("resources/test/fixtures/pyproject.toml"));
|
||||
let path =
|
||||
find_pyproject_toml(&Some(project_root)).expect("Unable to find pyproject.toml.");
|
||||
assert_eq!(path, cwd.join("resources/test/fixtures/pyproject.toml"));
|
||||
|
||||
let pyproject = parse_pyproject_toml(&path)?;
|
||||
let config = pyproject
|
||||
@@ -260,58 +326,35 @@ other-attribute = 1
|
||||
line_length: Some(88),
|
||||
exclude: None,
|
||||
extend_exclude: Some(vec![
|
||||
Path::new("excluded.py").to_path_buf(),
|
||||
Path::new("migrations").to_path_buf(),
|
||||
Path::new("./resources/test/fixtures/directory/also_excluded.py").to_path_buf()
|
||||
]),
|
||||
select: Some(vec![
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
"excluded.py".to_string(),
|
||||
"migrations".to_string(),
|
||||
"directory/also_excluded.py".to_string(),
|
||||
]),
|
||||
select: None,
|
||||
ignore: None,
|
||||
per_file_ignores: None,
|
||||
dummy_variable_rgx: None,
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_check_code_pair_strings() {
|
||||
let result = StrCheckCodePair::from_str("foo:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("E501:foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("E501");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("foo:E501:E402");
|
||||
assert!(result.is_err());
|
||||
let result = StrCheckCodePair::from_str("**/bar:E501");
|
||||
assert!(result.is_ok());
|
||||
let result = StrCheckCodePair::from_str("bar:E502");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"AbstractAsyncContextManager",
|
||||
|
||||
279
src/settings.rs
279
src/settings.rs
@@ -1,57 +1,141 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use glob::Pattern;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::pyproject::load_config;
|
||||
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
use crate::fs;
|
||||
use crate::pyproject::{load_config, StrCheckCodePair};
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum FilePattern {
|
||||
Simple(&'static str),
|
||||
Complex(Pattern, Option<Pattern>),
|
||||
}
|
||||
|
||||
impl FilePattern {
|
||||
pub fn from_user(pattern: &str, project_root: &Option<PathBuf>) -> Self {
|
||||
let path = Path::new(pattern);
|
||||
let absolute_path = match project_root {
|
||||
Some(project_root) => fs::normalize_path_to(path, project_root),
|
||||
None => fs::normalize_path(path),
|
||||
};
|
||||
|
||||
let absolute = Pattern::new(&absolute_path.to_string_lossy()).expect("Invalid pattern.");
|
||||
let basename = if !pattern.contains(std::path::MAIN_SEPARATOR) {
|
||||
Some(Pattern::new(pattern).expect("Invalid pattern."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
FilePattern::Complex(absolute, basename)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct PerFileIgnore {
|
||||
pub pattern: FilePattern,
|
||||
pub code: CheckCode,
|
||||
}
|
||||
|
||||
impl PerFileIgnore {
|
||||
pub fn new(user_in: StrCheckCodePair, project_root: &Option<PathBuf>) -> Self {
|
||||
let pattern = FilePattern::from_user(user_in.pattern.as_str(), project_root);
|
||||
let code = user_in.code;
|
||||
Self { pattern, code }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub line_length: usize,
|
||||
pub exclude: Vec<Pattern>,
|
||||
pub extend_exclude: Vec<Pattern>,
|
||||
pub exclude: Vec<FilePattern>,
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub select: BTreeSet<CheckCode>,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub dummy_variable_rgx: Regex,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn for_rule(check_code: CheckCode) -> Self {
|
||||
Self {
|
||||
pyproject: None,
|
||||
project_root: None,
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from([check_code]),
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
|
||||
Self {
|
||||
pyproject: None,
|
||||
project_root: None,
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
extend_exclude: vec![],
|
||||
select: BTreeSet::from_iter(check_codes),
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Settings {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.line_length.hash(state);
|
||||
self.dummy_variable_rgx.as_str().hash(state);
|
||||
for value in self.select.iter() {
|
||||
value.hash(state);
|
||||
}
|
||||
for value in self.per_file_ignores.iter() {
|
||||
value.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DEFAULT_EXCLUDE: Lazy<Vec<Pattern>> = Lazy::new(|| {
|
||||
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
||||
vec![
|
||||
Pattern::new(".bzr").unwrap(),
|
||||
Pattern::new(".direnv").unwrap(),
|
||||
Pattern::new(".eggs").unwrap(),
|
||||
Pattern::new(".git").unwrap(),
|
||||
Pattern::new(".hg").unwrap(),
|
||||
Pattern::new(".mypy_cache").unwrap(),
|
||||
Pattern::new(".nox").unwrap(),
|
||||
Pattern::new(".pants.d").unwrap(),
|
||||
Pattern::new(".ruff_cache").unwrap(),
|
||||
Pattern::new(".svn").unwrap(),
|
||||
Pattern::new(".tox").unwrap(),
|
||||
Pattern::new(".venv").unwrap(),
|
||||
Pattern::new("__pypackages__").unwrap(),
|
||||
Pattern::new("_build").unwrap(),
|
||||
Pattern::new("buck-out").unwrap(),
|
||||
Pattern::new("build").unwrap(),
|
||||
Pattern::new("dist").unwrap(),
|
||||
Pattern::new("node_modules").unwrap(),
|
||||
Pattern::new("venv").unwrap(),
|
||||
FilePattern::Simple(".bzr"),
|
||||
FilePattern::Simple(".direnv"),
|
||||
FilePattern::Simple(".eggs"),
|
||||
FilePattern::Simple(".git"),
|
||||
FilePattern::Simple(".hg"),
|
||||
FilePattern::Simple(".mypy_cache"),
|
||||
FilePattern::Simple(".nox"),
|
||||
FilePattern::Simple(".pants.d"),
|
||||
FilePattern::Simple(".ruff_cache"),
|
||||
FilePattern::Simple(".svn"),
|
||||
FilePattern::Simple(".tox"),
|
||||
FilePattern::Simple(".venv"),
|
||||
FilePattern::Simple("__pypackages__"),
|
||||
FilePattern::Simple("_build"),
|
||||
FilePattern::Simple("buck-out"),
|
||||
FilePattern::Simple("build"),
|
||||
FilePattern::Simple("dist"),
|
||||
FilePattern::Simple("node_modules"),
|
||||
FilePattern::Simple("venv"),
|
||||
]
|
||||
});
|
||||
|
||||
static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
|
||||
|
||||
impl Settings {
|
||||
pub fn from_paths(paths: &[PathBuf]) -> Self {
|
||||
let config = load_config(paths);
|
||||
pub fn from_pyproject(
|
||||
pyproject: Option<PathBuf>,
|
||||
project_root: Option<PathBuf>,
|
||||
) -> Result<Self> {
|
||||
let config = load_config(&pyproject)?;
|
||||
let mut settings = Settings {
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
exclude: config
|
||||
@@ -59,9 +143,7 @@ impl Settings {
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
|
||||
})
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
@@ -70,67 +152,43 @@ impl Settings {
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
|
||||
})
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
select: BTreeSet::from_iter(config.select.unwrap_or_else(|| {
|
||||
vec![
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// Disable refactoring codes by default.
|
||||
// CheckCode::R001,
|
||||
// CheckCode::R002,
|
||||
]
|
||||
})),
|
||||
select: if let Some(select) = config.select {
|
||||
BTreeSet::from_iter(select)
|
||||
} else {
|
||||
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
|
||||
},
|
||||
per_file_ignores: config
|
||||
.per_file_ignores
|
||||
.map(|ignore_strings| {
|
||||
ignore_strings
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
dummy_variable_rgx: match config.dummy_variable_rgx {
|
||||
Some(pattern) => Regex::new(&pattern)
|
||||
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
|
||||
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
},
|
||||
pyproject,
|
||||
project_root,
|
||||
};
|
||||
if let Some(ignore) = &config.ignore {
|
||||
settings.ignore(ignore);
|
||||
}
|
||||
settings
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.select.clear();
|
||||
}
|
||||
|
||||
pub fn select(&mut self, codes: Vec<CheckCode>) {
|
||||
self.select.clear();
|
||||
for code in codes {
|
||||
self.select.insert(code);
|
||||
}
|
||||
@@ -142,3 +200,62 @@ impl Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to render user-facing exclusion patterns.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Exclusion {
|
||||
basename: Option<String>,
|
||||
absolute: Option<String>,
|
||||
}
|
||||
|
||||
impl Exclusion {
|
||||
pub fn from_file_pattern(file_pattern: FilePattern) -> Self {
|
||||
match file_pattern {
|
||||
FilePattern::Simple(basename) => Exclusion {
|
||||
basename: Some(basename.to_string()),
|
||||
absolute: None,
|
||||
},
|
||||
FilePattern::Complex(absolute, basename) => Exclusion {
|
||||
basename: basename.map(|pattern| pattern.to_string()),
|
||||
absolute: Some(absolute.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to render user-facing Settings.
|
||||
#[derive(Debug)]
|
||||
pub struct CurrentSettings {
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub line_length: usize,
|
||||
pub exclude: Vec<Exclusion>,
|
||||
pub extend_exclude: Vec<Exclusion>,
|
||||
pub select: BTreeSet<CheckCode>,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub dummy_variable_rgx: Regex,
|
||||
}
|
||||
|
||||
impl CurrentSettings {
|
||||
pub fn from_settings(settings: Settings) -> Self {
|
||||
Self {
|
||||
pyproject: settings.pyproject,
|
||||
project_root: settings.project_root,
|
||||
line_length: settings.line_length,
|
||||
exclude: settings
|
||||
.exclude
|
||||
.into_iter()
|
||||
.map(Exclusion::from_file_pattern)
|
||||
.collect(),
|
||||
extend_exclude: settings
|
||||
.extend_exclude
|
||||
.into_iter()
|
||||
.map(Exclusion::from_file_pattern)
|
||||
.collect(),
|
||||
select: settings.select,
|
||||
per_file_ignores: settings.per_file_ignores,
|
||||
dummy_variable_rgx: settings.dummy_variable_rgx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
167
src/snapshots/ruff__linter__tests__a001.snap
Normal file
167
src/snapshots/ruff__linter__tests__a001.snap
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinVariableShadowing: sum
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: int
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: print
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: copyright
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: complex
|
||||
location:
|
||||
row: 6
|
||||
column: 2
|
||||
end_location:
|
||||
row: 6
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: float
|
||||
location:
|
||||
row: 7
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: object
|
||||
location:
|
||||
row: 7
|
||||
column: 9
|
||||
end_location:
|
||||
row: 7
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: min
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
end_location:
|
||||
row: 8
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: max
|
||||
location:
|
||||
row: 8
|
||||
column: 6
|
||||
end_location:
|
||||
row: 8
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: bytes
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
end_location:
|
||||
row: 13
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: slice
|
||||
location:
|
||||
row: 13
|
||||
column: 1
|
||||
end_location:
|
||||
row: 16
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: ValueError
|
||||
location:
|
||||
row: 18
|
||||
column: 1
|
||||
end_location:
|
||||
row: 21
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: memoryview
|
||||
location:
|
||||
row: 21
|
||||
column: 5
|
||||
end_location:
|
||||
row: 21
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: bytearray
|
||||
location:
|
||||
row: 21
|
||||
column: 18
|
||||
end_location:
|
||||
row: 21
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: str
|
||||
location:
|
||||
row: 24
|
||||
column: 22
|
||||
end_location:
|
||||
row: 24
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: all
|
||||
location:
|
||||
row: 24
|
||||
column: 45
|
||||
end_location:
|
||||
row: 24
|
||||
column: 48
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: any
|
||||
location:
|
||||
row: 24
|
||||
column: 50
|
||||
end_location:
|
||||
row: 24
|
||||
column: 53
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinVariableShadowing: sum
|
||||
location:
|
||||
row: 27
|
||||
column: 8
|
||||
end_location:
|
||||
row: 27
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
68
src/snapshots/ruff__linter__tests__a002.snap
Normal file
68
src/snapshots/ruff__linter__tests__a002.snap
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: str
|
||||
location:
|
||||
row: 1
|
||||
column: 11
|
||||
end_location:
|
||||
row: 1
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: type
|
||||
location:
|
||||
row: 1
|
||||
column: 19
|
||||
end_location:
|
||||
row: 1
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: complex
|
||||
location:
|
||||
row: 1
|
||||
column: 26
|
||||
end_location:
|
||||
row: 1
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: Exception
|
||||
location:
|
||||
row: 1
|
||||
column: 35
|
||||
end_location:
|
||||
row: 1
|
||||
column: 44
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: getattr
|
||||
location:
|
||||
row: 1
|
||||
column: 48
|
||||
end_location:
|
||||
row: 1
|
||||
column: 55
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: bytes
|
||||
location:
|
||||
row: 5
|
||||
column: 17
|
||||
end_location:
|
||||
row: 5
|
||||
column: 22
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinArgumentShadowing: float
|
||||
location:
|
||||
row: 9
|
||||
column: 16
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__a003.snap
Normal file
23
src/snapshots/ruff__linter__tests__a003.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BuiltinAttributeShadowing: ImportError
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
BuiltinAttributeShadowing: str
|
||||
location:
|
||||
row: 7
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
@@ -4,7 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: ModuleImportNotAtTopOfFile
|
||||
location:
|
||||
row: 20
|
||||
row: 24
|
||||
column: 1
|
||||
end_location:
|
||||
row: 24
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ expression: checks
|
||||
- 88
|
||||
location:
|
||||
row: 5
|
||||
column: 89
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 124
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,47 +7,71 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 11
|
||||
end_location:
|
||||
row: 2
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: NotEq
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 5
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: Eq
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: NotEq
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: Eq
|
||||
location:
|
||||
row: 14
|
||||
column: 14
|
||||
end_location:
|
||||
row: 14
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: NotEq
|
||||
location:
|
||||
row: 17
|
||||
column: 14
|
||||
end_location:
|
||||
row: 17
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: NotEq
|
||||
location:
|
||||
row: 20
|
||||
column: 4
|
||||
end_location:
|
||||
row: 20
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NoneComparison: Eq
|
||||
location:
|
||||
row: 23
|
||||
column: 4
|
||||
end_location:
|
||||
row: 23
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 11
|
||||
end_location:
|
||||
row: 2
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -17,6 +20,9 @@ expression: checks
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -25,6 +31,9 @@ expression: checks
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -33,6 +42,9 @@ expression: checks
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -41,6 +53,9 @@ expression: checks
|
||||
location:
|
||||
row: 14
|
||||
column: 14
|
||||
end_location:
|
||||
row: 14
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -49,6 +64,9 @@ expression: checks
|
||||
location:
|
||||
row: 17
|
||||
column: 14
|
||||
end_location:
|
||||
row: 17
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -57,6 +75,9 @@ expression: checks
|
||||
location:
|
||||
row: 20
|
||||
column: 20
|
||||
end_location:
|
||||
row: 20
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -65,6 +86,9 @@ expression: checks
|
||||
location:
|
||||
row: 20
|
||||
column: 44
|
||||
end_location:
|
||||
row: 20
|
||||
column: 49
|
||||
fix: ~
|
||||
- kind:
|
||||
TrueFalseComparison:
|
||||
@@ -73,5 +97,8 @@ expression: checks
|
||||
location:
|
||||
row: 22
|
||||
column: 5
|
||||
end_location:
|
||||
row: 22
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,25 +6,40 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 10
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind: NotInTest
|
||||
location:
|
||||
row: 5
|
||||
column: 12
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: NotInTest
|
||||
location:
|
||||
row: 8
|
||||
column: 10
|
||||
end_location:
|
||||
row: 8
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind: NotInTest
|
||||
location:
|
||||
row: 11
|
||||
column: 25
|
||||
end_location:
|
||||
row: 11
|
||||
column: 29
|
||||
fix: ~
|
||||
- kind: NotInTest
|
||||
location:
|
||||
row: 14
|
||||
column: 11
|
||||
end_location:
|
||||
row: 14
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 10
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind: NotIsTest
|
||||
location:
|
||||
row: 5
|
||||
column: 12
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: NotIsTest
|
||||
location:
|
||||
row: 8
|
||||
column: 10
|
||||
end_location:
|
||||
row: 8
|
||||
column: 23
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,80 +6,128 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 14
|
||||
end_location:
|
||||
row: 2
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 5
|
||||
column: 14
|
||||
end_location:
|
||||
row: 5
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 10
|
||||
column: 8
|
||||
end_location:
|
||||
row: 10
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 15
|
||||
column: 14
|
||||
end_location:
|
||||
row: 15
|
||||
column: 35
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 18
|
||||
column: 18
|
||||
end_location:
|
||||
row: 18
|
||||
column: 32
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 18
|
||||
column: 46
|
||||
end_location:
|
||||
row: 18
|
||||
column: 59
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 20
|
||||
column: 18
|
||||
end_location:
|
||||
row: 20
|
||||
column: 29
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 22
|
||||
column: 18
|
||||
end_location:
|
||||
row: 22
|
||||
column: 29
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 24
|
||||
column: 18
|
||||
end_location:
|
||||
row: 24
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 26
|
||||
column: 18
|
||||
end_location:
|
||||
row: 26
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 28
|
||||
column: 18
|
||||
end_location:
|
||||
row: 28
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 30
|
||||
column: 18
|
||||
end_location:
|
||||
row: 30
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 32
|
||||
column: 18
|
||||
end_location:
|
||||
row: 32
|
||||
column: 35
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 34
|
||||
column: 18
|
||||
end_location:
|
||||
row: 38
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 40
|
||||
column: 18
|
||||
end_location:
|
||||
row: 40
|
||||
column: 29
|
||||
fix: ~
|
||||
- kind: TypeComparison
|
||||
location:
|
||||
row: 42
|
||||
column: 18
|
||||
end_location:
|
||||
row: 42
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: DoNotUseBareExcept
|
||||
location:
|
||||
row: 11
|
||||
column: 1
|
||||
end_location:
|
||||
row: 14
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: DoNotUseBareExcept
|
||||
location:
|
||||
row: 16
|
||||
column: 1
|
||||
end_location:
|
||||
row: 19
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,25 +6,40 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: DoNotAssignLambda
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: DoNotAssignLambda
|
||||
location:
|
||||
row: 7
|
||||
column: 5
|
||||
end_location:
|
||||
row: 7
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind: DoNotAssignLambda
|
||||
location:
|
||||
row: 12
|
||||
column: 1
|
||||
end_location:
|
||||
row: 12
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: DoNotAssignLambda
|
||||
location:
|
||||
row: 16
|
||||
column: 1
|
||||
end_location:
|
||||
row: 16
|
||||
column: 26
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,149 +7,224 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: I
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: O
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 6
|
||||
column: 1
|
||||
end_location:
|
||||
row: 6
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 10
|
||||
column: 5
|
||||
end_location:
|
||||
row: 10
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 11
|
||||
column: 5
|
||||
end_location:
|
||||
row: 11
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 16
|
||||
column: 5
|
||||
end_location:
|
||||
row: 16
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 20
|
||||
column: 8
|
||||
end_location:
|
||||
row: 20
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 25
|
||||
column: 5
|
||||
end_location:
|
||||
row: 25
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 26
|
||||
column: 5
|
||||
end_location:
|
||||
row: 26
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 30
|
||||
column: 5
|
||||
end_location:
|
||||
row: 30
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 33
|
||||
column: 9
|
||||
end_location:
|
||||
row: 33
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 34
|
||||
column: 9
|
||||
end_location:
|
||||
row: 34
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 40
|
||||
column: 8
|
||||
end_location:
|
||||
row: 40
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: I
|
||||
location:
|
||||
row: 40
|
||||
column: 14
|
||||
end_location:
|
||||
row: 40
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 44
|
||||
column: 8
|
||||
end_location:
|
||||
row: 44
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: I
|
||||
location:
|
||||
row: 44
|
||||
column: 16
|
||||
end_location:
|
||||
row: 44
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 48
|
||||
column: 9
|
||||
end_location:
|
||||
row: 48
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: I
|
||||
location:
|
||||
row: 48
|
||||
column: 14
|
||||
end_location:
|
||||
row: 48
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 57
|
||||
column: 16
|
||||
end_location:
|
||||
row: 57
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 66
|
||||
column: 20
|
||||
end_location:
|
||||
row: 66
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 71
|
||||
column: 1
|
||||
end_location:
|
||||
row: 74
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousVariableName: l
|
||||
location:
|
||||
row: 74
|
||||
column: 5
|
||||
end_location:
|
||||
row: 74
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,17 +7,26 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousClassName: I
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousClassName: O
|
||||
location:
|
||||
row: 9
|
||||
column: 1
|
||||
end_location:
|
||||
row: 13
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,17 +7,26 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousFunctionName: I
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
AmbiguousFunctionName: O
|
||||
location:
|
||||
row: 10
|
||||
column: 5
|
||||
end_location:
|
||||
row: 14
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__e999.snap
Normal file
14
src/snapshots/ruff__linter__tests__e999.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
SyntaxError: Got unexpected EOF
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
@@ -7,17 +7,26 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedImport: collections.OrderedDict
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 2
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedImport: logging.handlers
|
||||
location:
|
||||
row: 13
|
||||
column: 1
|
||||
end_location:
|
||||
row: 13
|
||||
column: 24
|
||||
fix: ~
|
||||
|
||||
|
||||
27
src/snapshots/ruff__linter__tests__f402.snap
Normal file
27
src/snapshots/ruff__linter__tests__f402.snap
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportShadowedByLoopVar:
|
||||
- os
|
||||
- 1
|
||||
location:
|
||||
row: 5
|
||||
column: 5
|
||||
end_location:
|
||||
row: 5
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportShadowedByLoopVar:
|
||||
- path
|
||||
- 2
|
||||
location:
|
||||
row: 8
|
||||
column: 5
|
||||
end_location:
|
||||
row: 8
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
@@ -3,15 +3,21 @@ source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportStarUsage: F634
|
||||
ImportStarUsed: F634
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportStarUsage: F634
|
||||
ImportStarUsed: F634
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 19
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,5 +6,8 @@ expression: checks
|
||||
location:
|
||||
row: 7
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 38
|
||||
fix: ~
|
||||
|
||||
|
||||
27
src/snapshots/ruff__linter__tests__f405.snap
Normal file
27
src/snapshots/ruff__linter__tests__f405.snap
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportStarUsage:
|
||||
- name
|
||||
- mymodule
|
||||
location:
|
||||
row: 5
|
||||
column: 11
|
||||
end_location:
|
||||
row: 5
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportStarUsage:
|
||||
- a
|
||||
- mymodule
|
||||
location:
|
||||
row: 11
|
||||
column: 1
|
||||
end_location:
|
||||
row: 11
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
@@ -7,11 +7,17 @@ expression: checks
|
||||
location:
|
||||
row: 5
|
||||
column: 5
|
||||
end_location:
|
||||
row: 5
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportStarNotPermitted: F634
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 23
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 44
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 4
|
||||
column: 7
|
||||
end_location:
|
||||
row: 4
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind: FStringMissingPlaceholders
|
||||
location:
|
||||
row: 5
|
||||
column: 7
|
||||
end_location:
|
||||
row: 5
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind: FStringMissingPlaceholders
|
||||
location:
|
||||
row: 7
|
||||
column: 7
|
||||
end_location:
|
||||
row: 7
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiValueRepeatedKeyLiteral
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind: MultiValueRepeatedKeyLiteral
|
||||
location:
|
||||
row: 11
|
||||
column: 7
|
||||
end_location:
|
||||
row: 11
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ expression: checks
|
||||
location:
|
||||
row: 5
|
||||
column: 5
|
||||
end_location:
|
||||
row: 5
|
||||
column: 6
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,5 +6,8 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,10 +6,16 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: AssertTuple
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,10 +6,16 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind: IsLiteral
|
||||
location:
|
||||
row: 4
|
||||
column: 8
|
||||
end_location:
|
||||
row: 4
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,5 +6,8 @@ expression: checks
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 6
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,10 +6,16 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: IfTuple
|
||||
location:
|
||||
row: 7
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 5
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,20 +6,32 @@ expression: checks
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: BreakOutsideLoop
|
||||
location:
|
||||
row: 16
|
||||
column: 5
|
||||
end_location:
|
||||
row: 16
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: BreakOutsideLoop
|
||||
location:
|
||||
row: 20
|
||||
column: 5
|
||||
end_location:
|
||||
row: 20
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind: BreakOutsideLoop
|
||||
location:
|
||||
row: 23
|
||||
column: 1
|
||||
end_location:
|
||||
row: 23
|
||||
column: 6
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,20 +6,32 @@ expression: checks
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ContinueOutsideLoop
|
||||
location:
|
||||
row: 16
|
||||
column: 5
|
||||
end_location:
|
||||
row: 16
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ContinueOutsideLoop
|
||||
location:
|
||||
row: 20
|
||||
column: 5
|
||||
end_location:
|
||||
row: 20
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ContinueOutsideLoop
|
||||
location:
|
||||
row: 23
|
||||
column: 1
|
||||
end_location:
|
||||
row: 23
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
end_location:
|
||||
row: 6
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind: YieldOutsideFunction
|
||||
location:
|
||||
row: 9
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: YieldOutsideFunction
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
end_location:
|
||||
row: 10
|
||||
column: 13
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,10 +6,16 @@ expression: checks
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ReturnOutsideFunction
|
||||
location:
|
||||
row: 9
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: DefaultExceptNotLast
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
end_location:
|
||||
row: 12
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind: DefaultExceptNotLast
|
||||
location:
|
||||
row: 19
|
||||
column: 1
|
||||
end_location:
|
||||
row: 21
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ expression: checks
|
||||
location:
|
||||
row: 9
|
||||
column: 13
|
||||
end_location:
|
||||
row: 9
|
||||
column: 17
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,35 +7,71 @@ expression: checks
|
||||
location:
|
||||
row: 2
|
||||
column: 12
|
||||
end_location:
|
||||
row: 2
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: self
|
||||
location:
|
||||
row: 6
|
||||
column: 13
|
||||
end_location:
|
||||
row: 6
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: self
|
||||
location:
|
||||
row: 10
|
||||
column: 9
|
||||
end_location:
|
||||
row: 10
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: numeric_string
|
||||
location:
|
||||
row: 21
|
||||
column: 12
|
||||
end_location:
|
||||
row: 21
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Bar
|
||||
location:
|
||||
row: 58
|
||||
column: 5
|
||||
end_location:
|
||||
row: 58
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: TOMATO
|
||||
location:
|
||||
row: 83
|
||||
column: 11
|
||||
end_location:
|
||||
row: 83
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: B
|
||||
location:
|
||||
row: 87
|
||||
column: 7
|
||||
end_location:
|
||||
row: 87
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: B
|
||||
location:
|
||||
row: 89
|
||||
column: 7
|
||||
end_location:
|
||||
row: 89
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ expression: checks
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
end_location:
|
||||
row: 6
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@ expression: checks
|
||||
location:
|
||||
row: 1
|
||||
column: 25
|
||||
end_location:
|
||||
row: 1
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind: DuplicateArgumentName
|
||||
location:
|
||||
row: 5
|
||||
column: 28
|
||||
end_location:
|
||||
row: 5
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: DuplicateArgumentName
|
||||
location:
|
||||
row: 9
|
||||
column: 27
|
||||
end_location:
|
||||
row: 9
|
||||
column: 33
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,29 +7,44 @@ expression: checks
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: z
|
||||
location:
|
||||
row: 16
|
||||
column: 5
|
||||
end_location:
|
||||
row: 16
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: foo
|
||||
location:
|
||||
row: 20
|
||||
column: 5
|
||||
end_location:
|
||||
row: 20
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: a
|
||||
location:
|
||||
row: 21
|
||||
column: 6
|
||||
end_location:
|
||||
row: 21
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: b
|
||||
location:
|
||||
row: 21
|
||||
column: 9
|
||||
end_location:
|
||||
row: 21
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedVariable: e
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: foo
|
||||
location:
|
||||
row: 20
|
||||
column: 5
|
||||
end_location:
|
||||
row: 20
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: a
|
||||
location:
|
||||
row: 21
|
||||
column: 6
|
||||
end_location:
|
||||
row: 21
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: b
|
||||
location:
|
||||
row: 21
|
||||
column: 9
|
||||
end_location:
|
||||
row: 21
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: _
|
||||
location:
|
||||
row: 35
|
||||
column: 5
|
||||
end_location:
|
||||
row: 35
|
||||
column: 6
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: __
|
||||
location:
|
||||
row: 36
|
||||
column: 5
|
||||
end_location:
|
||||
row: 36
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedVariable: _discarded
|
||||
location:
|
||||
row: 37
|
||||
column: 5
|
||||
end_location:
|
||||
row: 37
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
@@ -5,11 +5,17 @@ expression: checks
|
||||
- kind: RaiseNotImplemented
|
||||
location:
|
||||
row: 2
|
||||
column: 25
|
||||
column: 11
|
||||
end_location:
|
||||
row: 2
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind: RaiseNotImplemented
|
||||
location:
|
||||
row: 6
|
||||
column: 11
|
||||
end_location:
|
||||
row: 6
|
||||
column: 25
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -7,11 +7,17 @@ expression: checks
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Bar
|
||||
location:
|
||||
row: 22
|
||||
column: 19
|
||||
end_location:
|
||||
row: 22
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
|
||||
107
src/snapshots/ruff__linter__tests__m001.snap
Normal file
107
src/snapshots/ruff__linter__tests__m001.snap
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedNOQA: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 18
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 13
|
||||
column: 10
|
||||
end_location:
|
||||
row: 13
|
||||
column: 24
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 13
|
||||
column: 10
|
||||
end_location:
|
||||
row: 13
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
end_location:
|
||||
row: 16
|
||||
column: 30
|
||||
fix:
|
||||
content: " # noqa: F841"
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
end_location:
|
||||
row: 16
|
||||
column: 30
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: F841
|
||||
location:
|
||||
row: 41
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
column: 24
|
||||
fix:
|
||||
content: " # noqa: E501"
|
||||
location:
|
||||
row: 41
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
location:
|
||||
row: 49
|
||||
column: 4
|
||||
end_location:
|
||||
row: 49
|
||||
column: 18
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 49
|
||||
column: 4
|
||||
end_location:
|
||||
row: 49
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: ~
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
column: 12
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
column: 12
|
||||
applied: false
|
||||
|
||||
@@ -7,12 +7,15 @@ expression: checks
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
end_location:
|
||||
row: 5
|
||||
column: 15
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 5
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
applied: false
|
||||
@@ -21,12 +24,15 @@ expression: checks
|
||||
location:
|
||||
row: 10
|
||||
column: 5
|
||||
end_location:
|
||||
row: 10
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 9
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 11
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -35,12 +41,15 @@ expression: checks
|
||||
location:
|
||||
row: 16
|
||||
column: 5
|
||||
end_location:
|
||||
row: 16
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 15
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 18
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -49,12 +58,15 @@ expression: checks
|
||||
location:
|
||||
row: 24
|
||||
column: 5
|
||||
end_location:
|
||||
row: 24
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 22
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 25
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -63,12 +75,15 @@ expression: checks
|
||||
location:
|
||||
row: 31
|
||||
column: 5
|
||||
end_location:
|
||||
row: 31
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 29
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 32
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -77,12 +92,15 @@ expression: checks
|
||||
location:
|
||||
row: 37
|
||||
column: 5
|
||||
end_location:
|
||||
row: 37
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 36
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 39
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -91,12 +109,15 @@ expression: checks
|
||||
location:
|
||||
row: 45
|
||||
column: 5
|
||||
end_location:
|
||||
row: 45
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 43
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 47
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -105,12 +126,15 @@ expression: checks
|
||||
location:
|
||||
row: 53
|
||||
column: 5
|
||||
end_location:
|
||||
row: 53
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 51
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 55
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -119,12 +143,15 @@ expression: checks
|
||||
location:
|
||||
row: 61
|
||||
column: 5
|
||||
end_location:
|
||||
row: 61
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 59
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 63
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -133,12 +160,15 @@ expression: checks
|
||||
location:
|
||||
row: 69
|
||||
column: 5
|
||||
end_location:
|
||||
row: 69
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 67
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 71
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -147,12 +177,15 @@ expression: checks
|
||||
location:
|
||||
row: 75
|
||||
column: 12
|
||||
end_location:
|
||||
row: 75
|
||||
column: 18
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 75
|
||||
column: 10
|
||||
end:
|
||||
end_location:
|
||||
row: 75
|
||||
column: 18
|
||||
applied: false
|
||||
@@ -161,12 +194,15 @@ expression: checks
|
||||
location:
|
||||
row: 79
|
||||
column: 9
|
||||
end_location:
|
||||
row: 79
|
||||
column: 15
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 79
|
||||
column: 9
|
||||
end:
|
||||
end_location:
|
||||
row: 79
|
||||
column: 17
|
||||
applied: false
|
||||
@@ -175,12 +211,15 @@ expression: checks
|
||||
location:
|
||||
row: 84
|
||||
column: 5
|
||||
end_location:
|
||||
row: 84
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 84
|
||||
column: 5
|
||||
end:
|
||||
end_location:
|
||||
row: 85
|
||||
column: 5
|
||||
applied: false
|
||||
@@ -189,12 +228,15 @@ expression: checks
|
||||
location:
|
||||
row: 92
|
||||
column: 5
|
||||
end_location:
|
||||
row: 92
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 91
|
||||
column: 6
|
||||
end:
|
||||
end_location:
|
||||
row: 92
|
||||
column: 11
|
||||
applied: false
|
||||
@@ -203,12 +245,15 @@ expression: checks
|
||||
location:
|
||||
row: 98
|
||||
column: 5
|
||||
end_location:
|
||||
row: 98
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 98
|
||||
column: 5
|
||||
end:
|
||||
end_location:
|
||||
row: 100
|
||||
column: 5
|
||||
applied: false
|
||||
@@ -217,12 +262,15 @@ expression: checks
|
||||
location:
|
||||
row: 108
|
||||
column: 5
|
||||
end_location:
|
||||
row: 108
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 107
|
||||
column: 6
|
||||
end:
|
||||
end_location:
|
||||
row: 108
|
||||
column: 11
|
||||
applied: false
|
||||
@@ -231,12 +279,15 @@ expression: checks
|
||||
location:
|
||||
row: 114
|
||||
column: 13
|
||||
end_location:
|
||||
row: 114
|
||||
column: 19
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 114
|
||||
column: 12
|
||||
end:
|
||||
end_location:
|
||||
row: 114
|
||||
column: 20
|
||||
applied: false
|
||||
@@ -245,12 +296,15 @@ expression: checks
|
||||
location:
|
||||
row: 119
|
||||
column: 5
|
||||
end_location:
|
||||
row: 119
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 118
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 120
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -259,12 +313,15 @@ expression: checks
|
||||
location:
|
||||
row: 125
|
||||
column: 5
|
||||
end_location:
|
||||
row: 125
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 124
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 126
|
||||
column: 2
|
||||
applied: false
|
||||
@@ -273,12 +330,15 @@ expression: checks
|
||||
location:
|
||||
row: 131
|
||||
column: 5
|
||||
end_location:
|
||||
row: 131
|
||||
column: 11
|
||||
fix:
|
||||
content: ""
|
||||
start:
|
||||
location:
|
||||
row: 130
|
||||
column: 8
|
||||
end:
|
||||
end_location:
|
||||
row: 133
|
||||
column: 2
|
||||
applied: false
|
||||
|
||||
@@ -5,27 +5,33 @@ expression: checks
|
||||
- kind: NoAssertEquals
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
fix:
|
||||
content: assertEqual
|
||||
start:
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end:
|
||||
column: 2
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
column: 14
|
||||
applied: false
|
||||
- kind: NoAssertEquals
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
fix:
|
||||
content: assertEqual
|
||||
start:
|
||||
location:
|
||||
row: 2
|
||||
column: 6
|
||||
end:
|
||||
column: 2
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
column: 14
|
||||
applied: false
|
||||
|
||||
|
||||
53
src/snapshots/ruff__linter__tests__spr001.snap
Normal file
53
src/snapshots/ruff__linter__tests__spr001.snap
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 17
|
||||
column: 18
|
||||
end_location:
|
||||
row: 17
|
||||
column: 36
|
||||
fix:
|
||||
content: super()
|
||||
location:
|
||||
row: 17
|
||||
column: 18
|
||||
end_location:
|
||||
row: 17
|
||||
column: 36
|
||||
applied: false
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
end_location:
|
||||
row: 18
|
||||
column: 27
|
||||
fix:
|
||||
content: super()
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
end_location:
|
||||
row: 18
|
||||
column: 27
|
||||
applied: false
|
||||
- kind: SuperCallWithParameters
|
||||
location:
|
||||
row: 19
|
||||
column: 9
|
||||
end_location:
|
||||
row: 22
|
||||
column: 10
|
||||
fix:
|
||||
content: super()
|
||||
location:
|
||||
row: 19
|
||||
column: 9
|
||||
end_location:
|
||||
row: 22
|
||||
column: 10
|
||||
applied: false
|
||||
|
||||
Reference in New Issue
Block a user