Compare commits

...

42 Commits

Author SHA1 Message Date
Charlie Marsh
9b564c9cf4 Bump version to 0.0.55 2022-10-04 20:07:31 -04:00
Charlie Marsh
5bf8b13644 Properly combine CLI and pyproject.toml ignores and selects (#329) 2022-10-04 20:07:17 -04:00
Anders Kaseorg
f80d5e70dd Support extend-select in pyproject.toml (#327) 2022-10-04 17:24:30 -04:00
Charlie Marsh
44897b2a5b Enable AST-to-source code generation (#292) 2022-10-04 16:27:57 -04:00
Anders Kaseorg
d1bcc919a2 Remove unnecessary Option wrapper from some pyproject::Config fields (#326) 2022-10-04 16:27:40 -04:00
Charlie Marsh
03e1397427 Bump version to 0.0.54 2022-10-04 14:32:06 -04:00
Charlie Marsh
fdb32330a9 Implement __metaclass__ = type removal (#324) 2022-10-04 14:31:52 -04:00
Charlie Marsh
4e6ae33a3a Only flag super calls in class-function scopes (#323) 2022-10-04 13:55:32 -04:00
Charlie Marsh
295ff8eb1a Add autofix and default status to README (#322) 2022-10-04 12:30:35 -04:00
Parth Shandilya
2449771d2f Fix the broken link to contribution guidelines (#321) 2022-10-04 11:10:10 -04:00
Charlie Marsh
406491a3a2 Bump version to 0.0.53 2022-10-04 08:56:46 -04:00
Charlie Marsh
bfae262359 Simplify noqa extraction logic (#320) 2022-10-04 08:56:14 -04:00
Charlie Marsh
af894f290f Disable plugin-based rules by default (#318) 2022-10-04 08:28:46 -04:00
Charlie Marsh
c901742244 Add plugins mention to README (#309) 2022-10-03 17:23:53 -04:00
Charlie Marsh
7e4faf4b69 Implement flake8-print (#308) 2022-10-03 17:19:56 -04:00
Charlie Marsh
31a0b20271 Bump version to 0.0.52 2022-10-03 15:22:58 -04:00
Charlie Marsh
0966bf2c66 Handle multi-import lines (#307) 2022-10-03 15:22:46 -04:00
Charlie Marsh
64d8e25528 Bump version to 0.0.51 2022-10-03 14:08:39 -04:00
Charlie Marsh
b049cced04 Automatically remove unused imports (#298) 2022-10-03 14:08:16 -04:00
Charlie Marsh
bc335f839e Visit lambda arguments prior to deferral (#303) 2022-10-02 20:54:02 -04:00
Charlie Marsh
4819e19ba2 Bump version to 0.0.50 2022-10-02 20:43:30 -04:00
Charlie Marsh
622b8adb79 Avoid falling back to A003 when A001 is disabled (#302) 2022-10-02 20:43:12 -04:00
Charlie Marsh
558d9fcbe3 Enable LibCST-based autofixing for SPR001 (#297) 2022-10-02 19:58:13 -04:00
Charlie Marsh
83f18193c2 Add an end location to Check (#299) 2022-10-02 12:50:42 -04:00
Charlie Marsh
46e6a1b3be Add end locations to all nodes (#296) 2022-10-02 12:49:48 -04:00
Suguru Yamamoto
4d0d433af9 fix: Make assigns to dunder exception for E402. (#294) 2022-10-01 09:43:47 -04:00
Christian Clauss
11f7532e72 pre-commit: Validate pyproject.toml (#266) 2022-09-30 19:21:12 -04:00
Charlie Marsh
417764d309 Expose a public 'check' method (#289) 2022-09-30 11:30:37 -04:00
Charlie Marsh
1e36c109c6 Bump version to 0.0.49 2022-09-30 09:15:32 -04:00
Nikita Sobolev
3960016d55 Create .editorconfig (#290) 2022-09-30 09:15:08 -04:00
Charlie Marsh
75d669fa86 Update check ordering 2022-09-30 09:14:41 -04:00
Nikita Sobolev
20989e12ba Implement flake8-super check (#291) 2022-09-30 09:12:09 -04:00
Charlie Marsh
5a1b6c32eb Add CONTRIBUTING.md (#288) 2022-09-30 07:51:30 -04:00
Charlie Marsh
46bdcb9080 Add instructions on GitHub Actions integration 2022-09-29 19:05:02 -04:00
Charlie Marsh
d16a7252af Add instructions on PyCharm integration 2022-09-29 18:52:45 -04:00
Charlie Marsh
ca6551eb37 Remove misc. unnecessary statements 2022-09-29 18:45:10 -04:00
Charlie Marsh
43a4f5749e Create CODE_OF_CONDUCT.md (#287) 2022-09-29 16:59:17 -04:00
Charlie Marsh
6fef4db433 Bump version to 0.0.48 2022-09-29 16:40:01 -04:00
Charlie Marsh
7470d6832f Add pattern matching limitation to README.md 2022-09-29 16:39:25 -04:00
Nikita Sobolev
63ba0bfeef Adds flake8-builtins (#284) 2022-09-29 16:37:43 -04:00
Anders Kaseorg
91666fcaf6 Don’t follow directory symlinks found while walking (#280) 2022-09-29 15:10:25 -04:00
Heyward Fann
643e27221d chore: fix eslint fix link (#281) 2022-09-29 07:15:07 -04:00
91 changed files with 4852 additions and 768 deletions

14
.editorconfig Normal file
View 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

View File

@@ -3,3 +3,8 @@ repos:
rev: v0.0.40
hooks:
- id: lint
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
hooks:
- id: validate-pyproject

128
CODE_OF_CONDUCT.md Normal file
View 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
View 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).

480
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.47"
version = "0.0.55"
edition = "2021"
[lib]
@@ -19,14 +19,17 @@ dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
itertools = { version = "0.10.3" }
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 = { 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 = "966a80597d626a9a47eaec78471164422d341453" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-common = { 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" }

166
README.md
View File

@@ -20,7 +20,7 @@ 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
- ⚖️ [Near-complete parity](#Parity-with-Flake8) with the built-in Flake8 rule set
@@ -57,7 +57,7 @@ ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.40
rev: v0.0.48
hooks:
- id: lint
```
@@ -201,67 +201,124 @@ 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 plugins, (2) alongside
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, 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.)
ruff also implements some of the most popular Flake8 plugins natively, including:
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
1. Flake8 has a plugin architecture and supports writing custom lint rules.
2. ruff does not yet support parenthesized context managers.
1. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
pattern matching and parenthesized context managers.
2. Flake8 has a plugin architecture and supports writing custom lint rules.
## Rules
| Code | Name | Message |
| ---- | ----- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E721 | TypeComparison | do not compare types, use `isinstance()` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
| E742 | AmbiguousClassName | ambiguous class name '...' |
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
| E902 | IOError | ... |
| E999 | SyntaxError | SyntaxError: ... |
| F401 | UnusedImport | `...` imported but unused |
| 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 |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F632 | IsLiteral | use ==/!= to compare constant literals |
| F633 | InvalidPrintSyntax | use of >> is invalid with print function |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
| F821 | UndefinedName | Undefined name `...` |
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
| R001 | UselessObjectInheritance | Class `...` inherits from object |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
| M001 | UnusedNOQA | Unused `noqa` directive |
The ✅ emoji indicates a rule is enabled by default.
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
| Code | Name | Message | | |
| ---- | ---- | ------- | --- | --- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | ✅ | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | ✅ | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | ✅ | |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | ✅ | |
| E713 | NotInTest | Test for membership should be `not in` | ✅ | |
| E714 | NotIsTest | Test for object identity should be `is not` | ✅ | |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | ✅ | |
| E722 | DoNotUseBareExcept | Do not use bare `except` | ✅ | |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | ✅ | |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | ✅ | |
| E742 | AmbiguousClassName | Ambiguous class name: `...` | ✅ | |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | |
| E902 | IOError | IOError: `...` | ✅ | |
| E999 | SyntaxError | SyntaxError: `...` | ✅ | |
| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 |
| 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 | ✅ | |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | ✅ | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | ✅ | |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | ✅ | |
| F622 | TwoStarredExpressions | Two starred expressions in assignment | ✅ | |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | ✅ | |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | ✅ | |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | ✅ | |
| F634 | IfTuple | If test is a tuple, which is always `True` | ✅ | |
| F701 | BreakOutsideLoop | `break` outside loop | ✅ | |
| F702 | ContinueOutsideLoop | `continue` not properly in loop | ✅ | |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | ✅ | |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | ✅ | |
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | ✅ | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | ✅ | |
| F821 | UndefinedName | Undefined name `...` | ✅ | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | ✅ | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | ✅ | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | ✅ | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | ✅ | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | ✅ | |
| 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)` | | 🛠 |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| 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:
![Install ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
ruff should then appear as a runnable action:
![ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### 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
@@ -422,3 +479,8 @@ Summary
## License
MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -1,19 +1,27 @@
/// Generate a Markdown-compatible table of supported lint rules.
use ruff::checks::{CheckCode, ALL_CHECK_CODES};
use ruff::checks::{CheckCode, ALL_CHECK_CODES, DEFAULT_CHECK_CODES};
fn main() {
let mut check_codes: Vec<CheckCode> = ALL_CHECK_CODES.to_vec();
check_codes.sort();
println!("| Code | Name | Message |");
println!("| ---- | ----- | ------- |");
println!("| Code | Name | Message | | |");
println!("| ---- | ---- | ------- | --- | --- |");
for check_code in check_codes {
let check_kind = check_code.kind();
let default_token = if DEFAULT_CHECK_CODES.contains(&check_code) {
""
} else {
""
};
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
println!(
"| {} | {} | {} |",
"| {} | {} | {} | {} | {} |",
check_kind.code().as_str(),
check_kind.name(),
check_kind.body()
check_kind.body(),
default_token,
fix_token
);
}
}

View File

@@ -0,0 +1,26 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use rustpython_parser::parser;
use ruff::code_gen::SourceGenerator;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
println!("{}", generator.generate()?);
Ok(())
}

37
output.py Normal file
View File

@@ -0,0 +1,37 @@
from collections import Counter
def f() -> None:
"""Docstring goes here."""
for x in range(5):
print(x)
else:
print("Nope!")
a = {
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
}
cls(title=title, before_text=before_text, after_text=after_text, before_description=before_description, after_description=after_description, transform_type=transform_type)

27
resources/test/fixtures/A001.py vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,8 @@
class MyClass:
ImportError = 4
def __init__(self):
self.float = 5 # is fine
def str(self):
pass

View File

@@ -1,4 +1,8 @@
"""Top-level docstring."""
__all__ = ["y"]
__version__: str = "0.1.0"
import a
try:

View File

@@ -1,6 +1,5 @@
from __future__ import all_feature_names
import os
import functools
import functools, os
from datetime import datetime
from collections import (
Counter,
@@ -11,14 +10,36 @@ import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import TYPING_CHECK, NamedTuple, Dict, Type, TypeVar, List, Set, Union, cast
from typing import (
TYPE_CHECKING,
NamedTuple,
Dict,
Type,
TypeVar,
List,
Set,
Union,
cast,
)
from dataclasses import MISSING, field
from blah import ClassA, ClassB, ClassC
if TYPING_CHECK:
if TYPE_CHECKING:
from models import Fruit, Nut, Vegetable
if TYPE_CHECKING:
import shelve
import importlib
if TYPE_CHECKING:
"""Hello, world!"""
import pathlib
z = 1
class X:
datetime: datetime
foo: Type["NamedTuple"]
@@ -28,6 +49,9 @@ class X:
y = Counter()
z = multiprocessing.pool.ThreadPool()
def b(self) -> None:
import pickle
__all__ = ["ClassA"] + ["ClassB"]
__all__ += ["ClassC"]
@@ -39,3 +63,5 @@ Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["Nut", None]
c = cast("Vegetable", b)
Field = lambda default=MISSING: field(default=default)

65
resources/test/fixtures/SPR001.py vendored Normal file
View File

@@ -0,0 +1,65 @@
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
class BaseClass:
def f(self):
print("f")
def defined_outside(self):
super(MyClass, self).f() # CANNOT use super()
class MyClass(BaseClass):
def normal(self):
super(MyClass, self).f() # can use super()
super().f()
def different_argument(self, other):
super(MyClass, other).f() # CANNOT use super()
def comprehension_scope(self):
[super(MyClass, self).f() for x in [1]] # CANNOT use super()
def inner_functions(self):
def outer_argument():
super(MyClass, self).f() # CANNOT use super()
def inner_argument(self):
super(MyClass, self).f() # can use super()
super().f()
outer_argument()
inner_argument(self)
def inner_class(self):
class InnerClass:
super(MyClass, self).f() # CANNOT use super()
def method(inner_self):
super(MyClass, self).f() # CANNOT use super()
InnerClass().method()
defined_outside = defined_outside

1
resources/test/fixtures/T201.py vendored Normal file
View File

@@ -0,0 +1 @@
print("Hello, world!") # T201

10
resources/test/fixtures/T203.py vendored Normal file
View File

@@ -0,0 +1,10 @@
from pprint import pprint
pprint("Hello, world!") # T203
import pprint
pprint.pprint("Hello, world!") # T203
pprint.pformat("Hello, world!")

13
resources/test/fixtures/U001.py vendored Normal file
View File

@@ -0,0 +1,13 @@
class A:
__metaclass__ = type
class B:
__metaclass__ = type
def __init__(self) -> None:
pass
class C(metaclass=type):
pass

View File

@@ -2,7 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from models import Fruit, Nut
from models import (
Fruit,
Nut,
)
@dataclass

View File

@@ -3,17 +3,20 @@ 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,
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
Keyword, Location, Stmt, StmtKind, Unaryop,
};
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::{Binding, BindingKind, CheckLocator, FunctionScope, Scope, ScopeKind};
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));
@@ -23,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));
@@ -50,7 +53,7 @@ pub fn check_not_tests(
if check_not_in {
checks.push(Check::new(
CheckKind::NotInTest,
locator.locate_check(operand.location),
locator.locate_check(Range::from_located(operand)),
));
}
}
@@ -58,7 +61,7 @@ pub fn check_not_tests(
if check_not_is {
checks.push(Check::new(
CheckKind::NotIsTest,
locator.locate_check(operand.location),
locator.locate_check(Range::from_located(operand)),
));
}
}
@@ -96,7 +99,7 @@ pub fn check_unused_variables(
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
locator.locate_check(binding.location),
locator.locate_check(binding.range),
));
}
}
@@ -105,7 +108,7 @@ pub fn check_unused_variables(
}
/// 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 {
@@ -113,12 +116,32 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Location) -> Option<Ch
}
}
/// Check UselessMetaclassType compliance.
pub fn check_useless_metaclass_type(
targets: &Vec<Expr>,
value: &Expr,
location: Range,
) -> Option<Check> {
if targets.len() == 1 {
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
if id == "__metaclass__" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "type" {
return Some(Check::new(CheckKind::UselessMetaclassType, location));
}
}
}
}
}
None
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
/// 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()),
@@ -130,7 +153,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()),
@@ -142,7 +165,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()),
@@ -174,7 +197,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(
@@ -205,7 +228,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),
));
}
}
@@ -219,13 +242,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),
));
}
}
_ => {}
@@ -257,7 +286,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);
}
@@ -271,12 +303,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(),
),
@@ -325,7 +361,7 @@ pub fn check_repeated_keys(
if check_repeated_literals && v1 == v2 {
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyLiteral,
locator.locate_check(k2.location),
locator.locate_check(Range::from_located(k2)),
))
}
}
@@ -333,7 +369,7 @@ pub fn check_repeated_keys(
if check_repeated_variables && v1 == v2 {
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyVariable((*v2).to_string()),
locator.locate_check(k2.location),
locator.locate_check(Range::from_located(k2)),
))
}
}
@@ -372,13 +408,13 @@ pub fn check_literal_comparisons(
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
}
@@ -392,13 +428,13 @@ pub fn check_literal_comparisons(
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
locator.locate_check(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::NoneComparison(RejectedCmpop::Eq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
}
@@ -438,13 +474,13 @@ pub fn check_literal_comparisons(
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
locator.locate_check(comparator.location),
locator.locate_check(Range::from_located(comparator)),
));
}
}
@@ -481,7 +517,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![];
@@ -502,7 +538,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![];
@@ -543,7 +579,7 @@ pub fn check_starred_expressions(
elts: &[Expr],
check_too_many_expressions: bool,
check_two_starred_expressions: bool,
location: Location,
location: Range,
) -> Option<Check> {
let mut has_starred: bool = false;
let mut starred_index: Option<usize> = None;
@@ -560,10 +596,7 @@ pub fn check_starred_expressions(
if check_too_many_expressions {
if let Some(starred_index) = starred_index {
if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 {
return Some(Check::new(
CheckKind::TooManyExpressionsInStarredAssignment,
location,
));
return Some(Check::new(CheckKind::ExpressionsInStarAssignment, location));
}
}
}
@@ -605,7 +638,7 @@ pub fn check_break_outside_loop(
if !allowed {
Some(Check::new(
CheckKind::BreakOutsideLoop,
locator.locate_check(stmt.location),
locator.locate_check(Range::from_located(stmt)),
))
} else {
None
@@ -646,9 +679,140 @@ pub fn check_continue_outside_loop(
if !allowed {
Some(Check::new(
CheckKind::ContinueOutsideLoop,
locator.locate_check(stmt.location),
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
}
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
// Check: is this a `super` call?
if let ExprKind::Name { id, .. } = &func.node {
id == "super" && !args.is_empty()
} else {
false
}
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(
scope: &Scope,
parents: &[&Stmt],
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if !is_super_call_with_arguments(func, args) {
return None;
}
// Check: are we in a Function scope?
if !matches!(scope.kind, ScopeKind::Function { .. }) {
return None;
}
let mut parents = parents.iter().rev();
// For a `super` invocation to be unnecessary, the first argument needs to match the enclosing
// class, and the second argument needs to match the first argument to the enclosing function.
if let [first_arg, second_arg] = args.as_slice() {
// Find the enclosing function definition (if any).
if let Some(StmtKind::FunctionDef {
args: parent_args, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::FunctionDef { .. }))
.map(|stmt| &stmt.node)
{
// Extract the name of the first argument to the enclosing function.
if let Some(ArgData {
arg: parent_arg, ..
}) = parent_args.args.first().map(|expr| &expr.node)
{
// Find the enclosing class definition (if any).
if let Some(StmtKind::ClassDef {
name: parent_name, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::ClassDef { .. }))
.map(|stmt| &stmt.node)
{
if let (
ExprKind::Name {
id: first_arg_id, ..
},
ExprKind::Name {
id: second_arg_id, ..
},
) = (&first_arg.node, &second_arg.node)
{
if first_arg_id == parent_name && second_arg_id == parent_arg {
return Some(Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
));
}
}
}
}
}
}
None
}
// flake8-print
/// Check whether a function call is a `print` or `pprint` invocation
pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
}
None
}

View File

@@ -1,6 +1,6 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::{BindingKind, Scope};
use crate::ast::types::{BindingKind, Range, Scope};
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
@@ -134,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() {
@@ -147,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, range: &Range) -> &'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[range.location.row() - 1] + range.location.column() - 1;
let end = self.offsets[range.end_location.row() - 1] + range.end_location.column() - 1;
&self.content[start..end]
}
}

View File

@@ -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 {

View File

@@ -1,13 +1,28 @@
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, PartialEq, Eq, PartialOrd, Ord)]
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,
@@ -40,6 +55,12 @@ impl Scope {
}
}
#[derive(Clone, Debug)]
pub struct BindingContext {
pub defined_by: usize,
pub defined_in: Option<usize>,
}
#[derive(Clone, Debug)]
pub enum BindingKind {
Annotation,
@@ -52,20 +73,27 @@ pub enum BindingKind {
Definition,
Export(Vec<String>),
FutureImportation,
Importation(String),
StarImportation,
SubmoduleImportation(String),
Importation(String, BindingContext),
FromImportation(String, BindingContext),
SubmoduleImportation(String, BindingContext),
}
#[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 range: 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: Location) -> Location;
fn locate_check(&self, default: Range) -> Range;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,
ImportFrom,
}

View File

@@ -2,6 +2,7 @@ use std::fs;
use std::path::Path;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use crate::checks::{Check, Fix};
@@ -43,41 +44,46 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
let mut output = "".to_string();
let mut last_pos: Location = Location::new(0, 0);
for fix in fixes {
for fix in fixes.sorted_by_key(|fix| fix.location) {
// 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;
}
if last_pos.row() > 0 || last_pos.column() > 0 {
if last_pos.row() > 0
&& (last_pos.row() - 1) < lines.len()
&& (last_pos.row() > 0 || last_pos.column() > 0)
{
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
output.push('\n');
}
for line in &lines[last_pos.row()..] {
output.push_str(line);
output.push('\n');
if last_pos.row() < lines.len() {
for line in &lines[last_pos.row()..] {
output.push_str(line);
output.push('\n');
}
}
output
@@ -106,8 +112,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 +136,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 +161,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 +193,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,
},
];

View File

@@ -1,8 +1,14 @@
use rustpython_parser::ast::{Expr, Keyword, Location};
use anyhow::Result;
use itertools::Itertools;
use libcst_native::ImportNames::Aliases;
use libcst_native::NameOrAttribute::N;
use libcst_native::{Codegen, Expression, SmallStatement, Statement};
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::token::Tok;
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::Range;
use crate::checks::Fix;
/// Convert a location within a file (relative to `base`) to an absolute position.
@@ -25,7 +31,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 +58,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 +97,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 +122,271 @@ 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 range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);
let mut tree = match libcst_native::parse_module(contents, None) {
Ok(m) => m,
Err(_) => return None,
};
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: range.location,
end_location: range.end_location,
applied: false,
});
}
}
}
None
}
/// Determine if a body contains only a single statement, taking into account deleted.
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
body.iter().filter(|child| !deleted.contains(child)).count() == 1
}
/// Determine if a child is the only statement in its body.
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
match &parent.node {
StmtKind::FunctionDef { body, .. }
| StmtKind::AsyncFunctionDef { body, .. }
| StmtKind::ClassDef { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::For { body, orelse, .. }
| StmtKind::AsyncFor { body, orelse, .. }
| StmtKind::While { body, orelse, .. }
| StmtKind::If { body, orelse, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
} => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else if finalbody.iter().contains(child) {
Ok(has_single_child(finalbody, deleted))
} else if let Some(body) = handlers.iter().find_map(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => {
if body.iter().contains(child) {
Some(body)
} else {
None
}
}
}) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
_ => Err(anyhow::anyhow!("Unable to find child in parent body.")),
}
}
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default()
{
// If removing this node would lead to an invalid syntax tree, replace
// it with a `pass`.
Ok(Fix {
location: stmt.location,
end_location: stmt.end_location,
content: "pass".to_string(),
applied: false,
})
} else {
// Otherwise, nuke the entire line.
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
Ok(Fix {
location: Location::new(stmt.location.row(), 1),
end_location: Location::new(stmt.end_location.row() + 1, 1),
content: "".to_string(),
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = &mut body.names;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(import_name) = &alias.name {
if full_names.contains(&import_name.value) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = if let Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(name) = &alias.name {
let import_name = if let Some(N(module_name)) = &body.module {
format!("{}.{}", module_name.value, name.value)
} else {
name.value.to_string()
};
if full_names.contains(&import_name.as_str()) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use crate::ast::types::Range;
use rustpython_parser::ast::Location;
use crate::autofix::fixer;
@@ -31,8 +32,8 @@ pub fn check_lines(
settings: &Settings,
autofix: &fixer::Mode,
) {
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
let enforce_noqa = settings.select.contains(&CheckCode::M001);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
@@ -64,11 +65,11 @@ pub fn check_lines(
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
match noqa {
(Directive::All(_), matches) => {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_str());
ignored.push(index)
}
(Directive::Codes(_, codes), matches) => {
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_str()) {
matches.push(check.kind.code().as_str());
ignored.push(index);
@@ -89,14 +90,17 @@ pub fn check_lines(
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Location::new(lineno + 1, settings.line_length + 1),
Range {
location: Location::new(lineno + 1, 1),
end_location: Location::new(lineno + 1, line_length + 1),
},
);
match noqa {
(Directive::All(_), matches) => {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_str());
}
(Directive::Codes(_, codes), matches) => {
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_str()) {
matches.push(check.kind.code().as_str());
} else {
@@ -113,24 +117,30 @@ pub fn check_lines(
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(column) => {
Directive::All(start, end) => {
if matches.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(None),
Location::new(row + 1, column + 1),
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(),
start: Location::new(row + 1, column + 1),
end: Location::new(row + 1, lines[row].chars().count() + 1),
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(column, codes) => {
Directive::Codes(start, end, codes) => {
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
@@ -144,21 +154,30 @@ pub fn check_lines(
if !invalid_codes.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(Some(invalid_codes.join(", "))),
Location::new(row + 1, column + 1),
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(),
start: Location::new(row + 1, column + 1),
end: Location::new(row + 1, lines[row].chars().count() + 1),
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(", ")),
start: Location::new(row + 1, column + 1),
end: Location::new(row + 1, lines[row].chars().count() + 1),
location: Location::new(row + 1, start + 1),
end_location: Location::new(
row + 1,
lines[row].chars().count() + 1,
),
applied: false,
});
}

View File

@@ -1,10 +1,13 @@
use std::str::FromStr;
use crate::ast::types::Range;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -19,6 +22,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pyflakes
CheckCode::F401,
CheckCode::F402,
CheckCode::F403,
@@ -49,7 +53,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];
pub const ALL_CHECK_CODES: [CheckCode; 45] = [
pub const ALL_CHECK_CODES: [CheckCode; 52] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -64,6 +69,7 @@ pub const ALL_CHECK_CODES: [CheckCode; 45] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pyflakes
CheckCode::F401,
CheckCode::F402,
CheckCode::F403,
@@ -92,13 +98,27 @@ pub const ALL_CHECK_CODES: [CheckCode; 45] = [
CheckCode::F831,
CheckCode::F841,
CheckCode::F901,
CheckCode::M001,
// flake8-builtins
CheckCode::A001,
CheckCode::A002,
CheckCode::A003,
// flake8-super
CheckCode::SPR001,
// flake8-print
CheckCode::T201,
CheckCode::T203,
// pyupgrade
CheckCode::U001,
// Refactor
CheckCode::R001,
CheckCode::R002,
// Meta
CheckCode::M001,
];
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
pub enum CheckCode {
// pycodestyle
E402,
E501,
E711,
@@ -113,6 +133,7 @@ pub enum CheckCode {
E743,
E902,
E999,
// pyflakes
F401,
F402,
F403,
@@ -141,8 +162,21 @@ pub enum CheckCode {
F831,
F841,
F901,
// flake8-builtins
A001,
A002,
A003,
// flake8-super
SPR001,
// flake8-print
T201,
T203,
// pyupgrade
U001,
// Refactor
R001,
R002,
// Meta
M001,
}
@@ -151,6 +185,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),
@@ -165,6 +200,7 @@ 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),
@@ -193,8 +229,18 @@ 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),
// pyupgrade
"U001" => Ok(CheckCode::U001),
// Refactor
"R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
// Meta
"M001" => Ok(CheckCode::M001),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
}
@@ -204,6 +250,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",
@@ -218,6 +265,7 @@ impl CheckCode {
CheckCode::E743 => "E743",
CheckCode::E902 => "E902",
CheckCode::E999 => "E999",
// pyflakes
CheckCode::F401 => "F401",
CheckCode::F402 => "F402",
CheckCode::F403 => "F403",
@@ -246,8 +294,21 @@ 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",
// flake8-print
CheckCode::T201 => "T201",
CheckCode::T203 => "T203",
// pyupgrade
CheckCode::U001 => "U001",
// Refactor
CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
// Meta
CheckCode::M001 => "M001",
}
}
@@ -264,6 +325,7 @@ impl CheckCode {
/// 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),
@@ -276,19 +338,22 @@ impl CheckCode {
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()),
CheckCode::E902 => CheckKind::IOError("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::F405 => {
CheckKind::ImportStarUsage("...".to_string(), vec!["...".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::F621 => CheckKind::ExpressionsInStarAssignment,
CheckCode::F622 => CheckKind::TwoStarredExpressions,
CheckCode::F631 => CheckKind::AssertTuple,
CheckCode::F632 => CheckKind::IsLiteral,
@@ -306,9 +371,22 @@ impl CheckCode {
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
CheckCode::M001 => CheckKind::UnusedNOQA(None),
// 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,
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
// Refactor
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::R002 => CheckKind::NoAssertEquals,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
}
}
@@ -328,7 +406,6 @@ pub enum RejectedCmpop {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
UnusedNOQA(Option<String>),
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
@@ -339,14 +416,15 @@ pub enum CheckKind {
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
ForwardAnnotationSyntaxError(String),
ExpressionsInStarAssignment,
FStringMissingPlaceholders,
ForwardAnnotationSyntaxError(String),
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportShadowedByLoopVar(String, usize),
ImportStarNotPermitted(String),
ImportStarUsage(String, String),
ImportStarUsage(String, Vec<String>),
ImportStarUsed(String),
InvalidPrintSyntax,
IsLiteral,
@@ -362,7 +440,6 @@ pub enum CheckKind {
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TooManyExpressionsInStarredAssignment,
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
@@ -370,9 +447,20 @@ pub enum CheckKind {
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(String),
UnusedNOQA(Option<String>),
UnusedVariable(String),
UselessMetaclassType,
UselessObjectInheritance(String),
YieldOutsideFunction,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flake8-super
SuperCallWithParameters,
// flake8-print
PrintFound,
PPrintFound,
}
impl CheckKind {
@@ -389,6 +477,7 @@ impl CheckKind {
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
CheckKind::ExpressionsInStarAssignment => "ExpressionsInStarAssignment",
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
@@ -405,16 +494,12 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::NoneComparison(_) => "NoneComparison",
CheckKind::NotInTest => "NotInTest",
CheckKind::NotIsTest => "NotIsTest",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::SyntaxError(_) => "SyntaxError",
CheckKind::TooManyExpressionsInStarredAssignment => {
"TooManyExpressionsInStarredAssignment"
}
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
CheckKind::TypeComparison => "TypeComparison",
@@ -423,8 +508,22 @@ impl CheckKind {
CheckKind::UndefinedName(_) => "UndefinedName",
CheckKind::UnusedImport(_) => "UnusedImport",
CheckKind::UnusedVariable(_) => "UnusedVariable",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
// flake8-super
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
// flake8-print
CheckKind::PrintFound => "PrintFound",
CheckKind::PPrintFound => "PPrintFound",
// pyupgrade
CheckKind::UselessMetaclassType => "UselessMetaclassType",
// Refactor
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
// Meta
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
}
}
@@ -458,14 +557,13 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621,
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
CheckKind::TwoStarredExpressions => &CheckCode::F622,
CheckKind::TypeComparison => &CheckCode::E721,
@@ -473,10 +571,24 @@ 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,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// pyupgrade
CheckKind::UselessMetaclassType => &CheckCode::U001,
// Refactor
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
}
@@ -484,13 +596,13 @@ impl CheckKind {
pub fn body(&self) -> String {
match self {
CheckKind::AmbiguousClassName(name) => {
format!("ambiguous class name '{}'", name)
format!("Ambiguous class name: `{}`", name)
}
CheckKind::AmbiguousFunctionName(name) => {
format!("ambiguous function name '{}'", name)
format!("Ambiguous function name: `{}`", name)
}
CheckKind::AmbiguousVariableName(name) => {
format!("ambiguous variable name '{}'", name)
format!("Ambiguous variable name: `{}`", name)
}
CheckKind::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
@@ -498,7 +610,7 @@ impl CheckKind {
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
CheckKind::DefaultExceptNotLast => {
"an `except:` block as not the last exception handler".to_string()
"An `except:` block as not the last exception handler".to_string()
}
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
@@ -508,19 +620,21 @@ impl CheckKind {
"Duplicate argument name in function definition".to_string()
}
CheckKind::ForwardAnnotationSyntaxError(body) => {
format!("syntax error in forward annotation '{body}'")
format!("Syntax error in forward annotation: `{body}`")
}
CheckKind::FStringMissingPlaceholders => {
"f-string without any placeholders".to_string()
}
CheckKind::FutureFeatureNotDefined(name) => {
format!("future feature '{name}' is not defined")
format!("Future feature `{name}` is not defined")
}
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::InvalidPrintSyntax => {
"Use of `>>` is invalid with `print` function".to_string()
}
CheckKind::ImportShadowedByLoopVar(name, line) => {
format!("import '{name}' from line {line} shadowed by loop variable")
format!("Import `{name}` from line {line} shadowed by loop variable")
}
CheckKind::ImportStarNotPermitted(name) => {
format!("`from {name} import *` only allowed at module level")
@@ -529,11 +643,15 @@ impl CheckKind {
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}")
let sources = sources
.iter()
.map(|source| format!("`{}`", source))
.join(", ");
format!("`{name}` may be undefined, or defined from star imports: {sources}")
}
CheckKind::IsLiteral => "use ==/!= to compare constant literals".to_string(),
CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(),
CheckKind::LateFutureImport => {
"from __future__ imports must occur at the beginning of the file".to_string()
"`from __future__` imports must occur at the beginning of the file".to_string()
}
CheckKind::LineTooLong(length, limit) => {
format!("Line too long ({length} > {limit} characters)")
@@ -547,9 +665,6 @@ impl CheckKind {
CheckKind::MultiValueRepeatedKeyVariable(name) => {
format!("Dictionary key `{name}` repeated")
}
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::NoneComparison(op) => match op {
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
RejectedCmpop::NotEq => {
@@ -562,11 +677,11 @@ impl CheckKind {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
}
CheckKind::ReturnOutsideFunction => {
"a `return` statement outside of a function/method".to_string()
"`return` statement outside of a function/method".to_string()
}
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
CheckKind::TooManyExpressionsInStarredAssignment => {
"too many expressions in star-unpacking assignment".to_string()
CheckKind::ExpressionsInStarAssignment => {
"Too many expressions in star-unpacking assignment".to_string()
}
CheckKind::TrueFalseComparison(value, op) => match *value {
true => match op {
@@ -586,8 +701,8 @@ impl CheckKind {
}
},
},
CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "do not compare types, use `isinstance()`".to_string(),
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
CheckKind::UndefinedExport(name) => {
format!("Undefined name `{name}` in `__all__`")
}
@@ -601,12 +716,37 @@ impl CheckKind {
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
}
CheckKind::YieldOutsideFunction => {
"`yield` or `yield from` statement outside of a function/method".to_string()
}
// 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()
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
// pyupgrade
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
// Refactor
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::UselessObjectInheritance(name) => {
format!("Class `{name}` inherits from object")
}
CheckKind::YieldOutsideFunction => {
"a `yield` or `yield from` statement outside of a function/method".to_string()
}
// Meta
CheckKind::UnusedNOQA(code) => match code {
None => "Unused `noqa` directive".to_string(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
@@ -619,8 +759,13 @@ impl CheckKind {
matches!(
self,
CheckKind::NoAssertEquals
| CheckKind::UselessObjectInheritance(_)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
| CheckKind::UnusedImport(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(_)
)
}
}
@@ -628,8 +773,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,
}
@@ -637,14 +782,16 @@ pub struct Fix {
pub struct Check {
pub kind: CheckKind,
pub location: Location,
pub end_location: Location,
pub fix: Option<Fix>,
}
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,
}
}

1039
src/code_gen.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -76,7 +76,6 @@ pub fn iter_python_files<'a>(
.all(|pattern| matches!(pattern, FilePattern::Simple(_)));
WalkDir::new(normalize_path(path))
.follow_links(true)
.into_iter()
.filter_entry(move |entry| {
if !has_exclude && !has_extend_exclude {
@@ -112,7 +111,9 @@ pub fn iter_python_files<'a>(
})
.filter(|entry| {
entry.as_ref().map_or(true, |entry| {
(entry.depth() == 0 && !entry.file_type().is_dir()) || is_included(entry.path())
(entry.depth() == 0 || is_included(entry.path()))
&& !entry.file_type().is_dir()
&& !(entry.file_type().is_symlink() && entry.path().is_dir())
})
})
}

View File

@@ -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::{RawSettings, Settings};
mod ast;
mod autofix;
@@ -6,6 +15,7 @@ pub mod cache;
pub mod check_ast;
mod check_lines;
pub mod checks;
pub mod code_gen;
pub mod fs;
pub mod linter;
pub mod logging;
@@ -15,3 +25,50 @@ pub mod printer;
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_raw(RawSettings::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)
}

View File

@@ -1,3 +1,4 @@
use std::fs::write;
use std::path::Path;
use anyhow::Result;
@@ -5,18 +6,20 @@ use log::debug;
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, CheckCode, CheckKind, LintSource};
use crate::code_gen::SourceGenerator;
use crate::message::Message;
use crate::noqa::add_noqa;
use crate::settings::Settings;
use crate::{cache, fs, noqa};
/// Collect tokens up to and including the first error.
fn tokenize(contents: &str) -> Vec<LexResult> {
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();
@@ -28,7 +31,7 @@ fn tokenize(contents: &str) -> Vec<LexResult> {
tokens
}
fn check_path(
pub(crate) fn check_path(
path: &Path,
contents: &str,
tokens: Vec<LexResult>,
@@ -41,7 +44,7 @@ fn check_path(
// Run the AST-based checks.
if settings
.select
.enabled
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
{
@@ -50,10 +53,13 @@ fn check_path(
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
}
Err(parse_error) => {
if settings.select.contains(&CheckCode::E999) {
if settings.enabled.contains(&CheckCode::E999) {
checks.push(Check::new(
CheckKind::SyntaxError(parse_error.error.to_string()),
parse_error.location,
Range {
location: parse_error.location,
end_location: parse_error.location,
},
))
}
}
@@ -115,6 +121,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();
@@ -146,6 +153,22 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
add_noqa(&checks, &contents, &noqa_line_for, path)
}
pub fn autoformat_path(path: &Path) -> Result<()> {
// Read the file from disk.
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// Generate the AST.
let python_ast = parser::parse_program_tokens(tokens, "<filename>")?;
let mut generator: SourceGenerator = Default::default();
generator.unparse_suite(&python_ast)?;
write(path, generator.generate()?)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
@@ -726,4 +749,88 @@ mod tests {
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);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn t201() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/T201.py"),
&settings::Settings::for_rule(CheckCode::T201),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn t203() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/T203.py"),
&settings::Settings::for_rule(CheckCode::T203),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U001.py"),
&settings::Settings::for_rule(CheckCode::U001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -1,5 +1,3 @@
extern crate core;
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
@@ -25,9 +23,11 @@ use ::ruff::logging::set_up_logging;
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::pyproject::{self, StrCheckCodePair};
use ::ruff::settings::CurrentSettings;
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ::ruff::tell_user;
use ruff::settings::CurrentSettings;
use ruff::linter::autoformat_path;
use ruff::settings::RawSettings;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -89,6 +89,9 @@ struct Cli {
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
add_noqa: bool,
/// Enable automatic formatting.
#[arg(long)]
autoformat: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
@@ -118,7 +121,7 @@ fn check_for_updates() {
}
}
fn show_settings(settings: Settings) {
fn show_settings(settings: RawSettings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
}
@@ -167,11 +170,12 @@ fn run_once(
}
.unwrap_or_else(|(path, message)| {
if let Some(path) = path {
if settings.select.contains(&CheckCode::E902) {
if settings.enabled.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 {
@@ -223,6 +227,33 @@ fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
Ok(modifications)
}
fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
// Collect all the files to format.
let start = Instant::now();
let paths: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.flatten()
.collect();
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications = paths
.par_iter()
.map(|entry| {
let path = entry.path();
autoformat_path(path)
})
.flatten()
.count();
let duration = start.elapsed();
debug!("Auto-formatted files in: {:?}", duration);
Ok(modifications)
}
fn inner_main() -> Result<ExitCode> {
let cli = Cli::parse();
@@ -257,7 +288,7 @@ fn inner_main() -> Result<ExitCode> {
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let mut settings = Settings::from_pyproject(pyproject, project_root)?;
let mut settings = RawSettings::from_pyproject(pyproject, project_root)?;
if !exclude.is_empty() {
settings.exclude = exclude;
}
@@ -268,17 +299,16 @@ fn inner_main() -> Result<ExitCode> {
settings.per_file_ignores = per_file_ignores;
}
if !cli.select.is_empty() {
settings.clear();
settings.select(cli.select);
settings.select = cli.select;
}
if !cli.extend_select.is_empty() {
settings.select(cli.extend_select);
settings.extend_select = cli.extend_select;
}
if !cli.ignore.is_empty() {
settings.ignore(&cli.ignore);
settings.ignore = cli.ignore;
}
if !cli.extend_ignore.is_empty() {
settings.ignore(&cli.extend_ignore);
settings.extend_ignore = cli.extend_ignore;
}
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
settings.dummy_variable_rgx = dummy_variable_rgx;
@@ -288,15 +318,18 @@ fn inner_main() -> Result<ExitCode> {
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);
}
let settings = Settings::from_raw(settings);
if cli.show_files {
show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS);
}
cache::init()?;
let mut printer = Printer::new(cli.format, cli.verbose);
@@ -309,6 +342,10 @@ fn inner_main() -> Result<ExitCode> {
eprintln!("Warning: --no-qa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
@@ -352,6 +389,11 @@ fn inner_main() -> Result<ExitCode> {
if modifications > 0 {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications = autoformat(&cli.files, &settings)?;
if modifications > 0 {
println!("Formatted {modifications} files.");
}
} else {
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
if !cli.quiet {

View File

@@ -14,6 +14,7 @@ pub struct Message {
pub kind: CheckKind,
pub fixed: bool,
pub location: Location,
pub end_location: Location,
pub filename: String,
}
@@ -39,7 +40,6 @@ impl fmt::Display for Message {
f,
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&self.filename)).white().bold(),
// self.filename.white(),
":".cyan(),
self.location.row(),
":".cyan(),

View File

@@ -19,8 +19,8 @@ static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect
#[derive(Debug)]
pub enum Directive<'a> {
None,
All(usize),
Codes(usize, Vec<&'a str>),
All(usize, usize),
Codes(usize, usize, Vec<&'a str>),
}
pub fn extract_noqa_directive(line: &str) -> Directive {
@@ -29,13 +29,14 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
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()),
None => Directive::All(noqa.start(), noqa.end()),
},
None => Directive::None,
},
@@ -46,10 +47,9 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
let mut noqa_line_for: Vec<usize> = vec![];
let mut last_is_string = false;
let mut last_seen = usize::MIN;
let mut min_line = usize::MAX;
let mut max_line = usize::MIN;
let mut in_string = false;
for (start, tok, end) in lxr.iter().flatten() {
if matches!(tok, Tok::EndOfFile) {
@@ -61,29 +61,20 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
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) {
if in_string {
for i in (noqa_line_for.len())..(min_line - 1) {
noqa_line_for.push(i + 1);
}
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
}
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 { .. });
in_string = matches!(tok, Tok::String { .. });
}
noqa_line_for
@@ -133,8 +124,8 @@ fn add_noqa_inner(
Directive::None => {
output.push_str(line);
}
Directive::All(start) => output.push_str(&line[..start]),
Directive::Codes(start, _) => output.push_str(&line[..start]),
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: ");
@@ -161,6 +152,7 @@ pub fn add_noqa(
#[cfg(test)]
mod tests {
use crate::ast::types::Range;
use anyhow::Result;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer;
@@ -171,6 +163,8 @@ mod tests {
#[test]
fn extraction() -> Result<()> {
let empty: Vec<usize> = Default::default();
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
@@ -178,7 +172,7 @@ z = x + 1",
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"
@@ -188,7 +182,7 @@ z = x + 1",
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -198,7 +192,7 @@ z = x + 1
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -209,7 +203,7 @@ z = x + 1
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = '''abc
@@ -220,7 +214,28 @@ y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4, 5, 6]);
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4]);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''
z = 2",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
Ok(())
}
@@ -236,7 +251,10 @@ z = x + 1",
let checks = vec![Check::new(
CheckKind::UnusedVariable("x".to_string()),
Location::new(1, 1),
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
)];
let contents = "x = 1";
let noqa_line_for = vec![1];
@@ -247,11 +265,17 @@ z = x + 1",
let checks = vec![
Check::new(
CheckKind::AmbiguousVariableName("x".to_string()),
Location::new(1, 1),
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
),
Check::new(
CheckKind::UnusedVariable("x".to_string()),
Location::new(1, 1),
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
),
];
let contents = "x = 1 # noqa: E741";
@@ -263,11 +287,17 @@ z = x + 1",
let checks = vec![
Check::new(
CheckKind::AmbiguousVariableName("x".to_string()),
Location::new(1, 1),
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
),
Check::new(
CheckKind::UnusedVariable("x".to_string()),
Location::new(1, 1),
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
),
];
let contents = "x = 1 # noqa";

View File

@@ -21,6 +21,7 @@ struct ExpandedMessage<'a> {
message: String,
fixed: bool,
location: Location,
end_location: Location,
filename: &'a String,
}
@@ -55,6 +56,7 @@ impl Printer {
message: m.kind.body(),
fixed: m.fixed,
location: m.location,
end_location: m.end_location,
filename: &m.filename,
})
.collect::<Vec<_>>()

View File

@@ -29,10 +29,17 @@ pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<String>>,
pub extend_exclude: Option<Vec<String>>,
#[serde(default)]
pub extend_exclude: Vec<String>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
pub per_file_ignores: Option<Vec<StrCheckCodePair>>,
#[serde(default)]
pub extend_select: Vec<CheckCode>,
#[serde(default)]
pub ignore: Vec<CheckCode>,
#[serde(default)]
pub extend_ignore: Vec<CheckCode>,
#[serde(default)]
pub per_file_ignores: Vec<StrCheckCodePair>,
pub dummy_variable_rgx: Option<String>,
}
@@ -175,10 +182,12 @@ mod tests {
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -197,10 +206,12 @@ line-length = 79
ruff: Some(Config {
line_length: Some(79),
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -219,10 +230,12 @@ exclude = ["foo.py"]
ruff: Some(Config {
line_length: None,
exclude: Some(vec!["foo.py".to_string()]),
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -241,10 +254,12 @@ select = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: Some(vec![CheckCode::E501]),
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -254,6 +269,7 @@ select = ["E501"]
r#"
[tool.black]
[tool.ruff]
extend-select = ["M001"]
ignore = ["E501"]
"#,
)?;
@@ -263,10 +279,12 @@ ignore = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: Some(vec![CheckCode::E501]),
per_file_ignores: None,
extend_select: vec![CheckCode::M001],
ignore: vec![CheckCode::E501],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
})
})
@@ -325,14 +343,16 @@ other-attribute = 1
Config {
line_length: Some(88),
exclude: None,
extend_exclude: Some(vec![
extend_exclude: vec![
"excluded.py".to_string(),
"migrations".to_string(),
"directory/also_excluded.py".to_string(),
]),
],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
}
);

View File

@@ -51,56 +51,18 @@ impl PerFileIgnore {
}
#[derive(Debug)]
pub struct Settings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub struct RawSettings {
pub dummy_variable_rgx: Regex,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
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);
}
}
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -130,14 +92,18 @@ static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
impl Settings {
impl RawSettings {
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),
Ok(RawSettings {
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(),
},
exclude: config
.exclude
.map(|paths| {
@@ -149,54 +115,91 @@ impl Settings {
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect()
})
.unwrap_or_default(),
select: if let Some(select) = config.select {
BTreeSet::from_iter(select)
} else {
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
},
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect(),
extend_ignore: config.extend_ignore,
extend_select: config.extend_select,
ignore: config.ignore,
line_length: config.line_length.unwrap_or(88),
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,
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect(),
project_root,
};
if let Some(ignore) = &config.ignore {
settings.ignore(ignore);
pyproject,
select: config
.select
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
})
}
}
#[derive(Debug)]
pub struct Settings {
pub dummy_variable_rgx: Regex,
pub enabled: BTreeSet<CheckCode>,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
}
impl Settings {
pub fn from_raw(settings: RawSettings) -> Self {
// Materialize the set of enabled CheckCodes.
let mut enabled: BTreeSet<CheckCode> = BTreeSet::new();
enabled.extend(settings.select);
enabled.extend(settings.extend_select);
for code in &settings.ignore {
enabled.remove(code);
}
Ok(settings)
}
pub fn clear(&mut self) {
self.select.clear();
}
pub fn select(&mut self, codes: Vec<CheckCode>) {
for code in codes {
self.select.insert(code);
for code in &settings.extend_ignore {
enabled.remove(code);
}
Self {
dummy_variable_rgx: settings.dummy_variable_rgx,
enabled,
exclude: settings.exclude,
extend_exclude: settings.extend_exclude,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
}
}
pub fn ignore(&mut self, codes: &[CheckCode]) {
for code in codes {
self.select.remove(code);
pub fn for_rule(check_code: CheckCode) -> Self {
Self {
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
enabled: BTreeSet::from([check_code]),
exclude: vec![],
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
}
}
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
Self {
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
enabled: BTreeSet::from_iter(check_codes),
exclude: vec![],
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
}
}
}
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.enabled.iter() {
value.hash(state);
}
for value in self.per_file_ignores.iter() {
value.hash(state);
}
}
}
@@ -227,22 +230,23 @@ impl Exclusion {
/// 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 dummy_variable_rgx: Regex,
pub exclude: Vec<Exclusion>,
pub extend_exclude: Vec<Exclusion>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub dummy_variable_rgx: Regex,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
}
impl CurrentSettings {
pub fn from_settings(settings: Settings) -> Self {
pub fn from_settings(settings: RawSettings) -> Self {
Self {
pyproject: settings.pyproject,
project_root: settings.project_root,
line_length: settings.line_length,
dummy_variable_rgx: settings.dummy_variable_rgx,
exclude: settings
.exclude
.into_iter()
@@ -253,9 +257,14 @@ impl CurrentSettings {
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
select: settings.select,
extend_ignore: settings.extend_ignore,
extend_select: settings.extend_select,
ignore: settings.ignore,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
dummy_variable_rgx: settings.dummy_variable_rgx,
project_root: settings.project_root,
pyproject: settings.pyproject,
select: settings.select,
}
}
}

View 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: ~

View 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: ~

View 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: ~

View File

@@ -4,7 +4,10 @@ expression: checks
---
- kind: ModuleImportNotAtTopOfFile
location:
row: 20
row: 24
column: 1
end_location:
row: 24
column: 9
fix: ~

View File

@@ -8,6 +8,9 @@ expression: checks
- 88
location:
row: 5
column: 89
column: 1
end_location:
row: 5
column: 124
fix: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 2
column: 1
end_location:
row: 2
column: 1
fix: ~

View File

@@ -5,19 +5,120 @@ expression: checks
- kind:
UnusedImport: functools
location:
row: 3
row: 2
column: 1
fix: ~
end_location:
row: 2
column: 21
fix:
content: import os
location:
row: 2
column: 1
end_location:
row: 2
column: 21
applied: false
- kind:
UnusedImport: collections.OrderedDict
location:
row: 5
row: 4
column: 1
fix: ~
end_location:
row: 8
column: 2
fix:
content: "from collections import (\n Counter,\n namedtuple,\n)"
location:
row: 4
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UnusedImport: logging.handlers
location:
row: 13
row: 12
column: 1
fix: ~
end_location:
row: 12
column: 24
fix:
content: import logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
applied: false
- kind:
UnusedImport: shelve
location:
row: 33
column: 5
end_location:
row: 33
column: 18
fix:
content: ""
location:
row: 33
column: 1
end_location:
row: 34
column: 1
applied: false
- kind:
UnusedImport: importlib
location:
row: 34
column: 5
end_location:
row: 34
column: 21
fix:
content: pass
location:
row: 34
column: 5
end_location:
row: 34
column: 21
applied: false
- kind:
UnusedImport: pathlib
location:
row: 38
column: 5
end_location:
row: 38
column: 19
fix:
content: ""
location:
row: 38
column: 1
end_location:
row: 39
column: 1
applied: false
- kind:
UnusedImport: pickle
location:
row: 53
column: 9
end_location:
row: 53
column: 22
fix:
content: pass
location:
row: 53
column: 9
end_location:
row: 53
column: 22
applied: false

View File

@@ -9,6 +9,9 @@ expression: checks
location:
row: 5
column: 5
end_location:
row: 5
column: 7
fix: ~
- kind:
ImportShadowedByLoopVar:
@@ -17,5 +20,8 @@ expression: checks
location:
row: 8
column: 5
end_location:
row: 8
column: 9
fix: ~

View File

@@ -7,11 +7,17 @@ expression: checks
location:
row: 1
column: 1
end_location:
row: 1
column: 19
fix: ~
- kind:
ImportStarUsed: F634
location:
row: 2
column: 1
end_location:
row: 2
column: 19
fix: ~

View File

@@ -6,5 +6,8 @@ expression: checks
location:
row: 7
column: 1
end_location:
row: 7
column: 38
fix: ~

View File

@@ -5,17 +5,23 @@ expression: checks
- kind:
ImportStarUsage:
- name
- mymodule
- - mymodule
location:
row: 5
column: 11
end_location:
row: 5
column: 15
fix: ~
- kind:
ImportStarUsage:
- a
- mymodule
- - mymodule
location:
row: 11
column: 1
end_location:
row: 11
column: 8
fix: ~

View File

@@ -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: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 2
column: 1
end_location:
row: 2
column: 44
fix: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 5
column: 5
end_location:
row: 5
column: 6
fix: ~

View File

@@ -6,5 +6,8 @@ expression: checks
location:
row: 1
column: 1
end_location:
row: 1
column: 10
fix: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -6,5 +6,8 @@ expression: checks
location:
row: 4
column: 1
end_location:
row: 4
column: 6
fix: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 9
column: 13
end_location:
row: 9
column: 17
fix: ~

View File

@@ -7,47 +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: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 3
column: 1
end_location:
row: 3
column: 8
fix: ~

View File

@@ -7,5 +7,8 @@ expression: checks
location:
row: 6
column: 5
end_location:
row: 6
column: 11
fix: ~

View File

@@ -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: ~

View File

@@ -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: ~

View File

@@ -7,41 +7,62 @@ expression: checks
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: ~

View File

@@ -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: ~

View File

@@ -7,11 +7,25 @@ expression: checks
location:
row: 5
column: 1
fix: ~
end_location:
row: 8
column: 2
fix:
content: "from models import (\n Fruit,\n)"
location:
row: 5
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UndefinedName: Bar
location:
row: 22
row: 25
column: 19
end_location:
row: 25
column: 22
fix: ~

View File

@@ -7,12 +7,15 @@ expression: checks
location:
row: 9
column: 10
end_location:
row: 9
column: 18
fix:
content: ""
start:
location:
row: 9
column: 10
end:
end_location:
row: 9
column: 18
applied: false
@@ -21,12 +24,15 @@ expression: checks
location:
row: 13
column: 10
end_location:
row: 13
column: 24
fix:
content: ""
start:
location:
row: 13
column: 10
end:
end_location:
row: 13
column: 24
applied: false
@@ -35,12 +41,15 @@ expression: checks
location:
row: 16
column: 10
end_location:
row: 16
column: 30
fix:
content: " # noqa: F841"
start:
location:
row: 16
column: 10
end:
end_location:
row: 16
column: 30
applied: false
@@ -49,12 +58,15 @@ expression: checks
location:
row: 41
column: 4
end_location:
row: 41
column: 24
fix:
content: " # noqa: E501"
start:
location:
row: 41
column: 4
end:
end_location:
row: 41
column: 24
applied: false
@@ -63,12 +75,15 @@ expression: checks
location:
row: 49
column: 4
end_location:
row: 49
column: 18
fix:
content: ""
start:
location:
row: 49
column: 4
end:
end_location:
row: 49
column: 18
applied: false
@@ -77,12 +92,15 @@ expression: checks
location:
row: 57
column: 4
end_location:
row: 57
column: 12
fix:
content: ""
start:
location:
row: 57
column: 4
end:
end_location:
row: 57
column: 12
applied: false

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,85 @@
---
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
- kind: SuperCallWithParameters
location:
row: 36
column: 9
end_location:
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
row: 50
column: 13
end_location:
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: PrintFound
location:
row: 1
column: 1
end_location:
row: 1
column: 23
fix:
content: ""
location:
row: 1
column: 1
end_location:
row: 2
column: 1
applied: false

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: PPrintFound
location:
row: 3
column: 1
end_location:
row: 3
column: 24
fix:
content: ""
location:
row: 3
column: 1
end_location:
row: 4
column: 1
applied: false
- kind: PPrintFound
location:
row: 8
column: 1
end_location:
row: 8
column: 31
fix:
content: ""
location:
row: 8
column: 1
end_location:
row: 9
column: 1
applied: false

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: UselessMetaclassType
location:
row: 2
column: 5
end_location:
row: 2
column: 25
fix:
content: pass
location:
row: 2
column: 5
end_location:
row: 2
column: 25
applied: false
- kind: UselessMetaclassType
location:
row: 6
column: 5
end_location:
row: 6
column: 25
fix:
content: ""
location:
row: 6
column: 1
end_location:
row: 7
column: 1
applied: false