Compare commits

...

20 Commits

Author SHA1 Message Date
Charlie Marsh
2fe22a223b Bump version to 0.0.147 2022-11-29 20:17:58 -05:00
Charlie Marsh
e762dec677 Add one more note to README 2022-11-29 20:17:46 -05:00
Charlie Marsh
19baa50003 Remove extraneous key in pyproject.toml 2022-11-29 20:13:28 -05:00
Charlie Marsh
ab0df03a05 Fix pyproject tests to include pyupgrade 2022-11-29 20:11:16 -05:00
Charlie Marsh
808b348c5f Add W to pycodestyle list in README 2022-11-29 20:09:07 -05:00
Charlie Marsh
e55daa89e6 Uses dashes for README options (#966) 2022-11-29 20:08:03 -05:00
Charlie Marsh
b8e7d86696 Add pyupgrade's --keep-runtime-typing option (#965) 2022-11-29 20:05:32 -05:00
Charlie Marsh
ced7868559 Add format setting to pyproject.toml (#964) 2022-11-29 19:22:23 -05:00
Ramazan Elsunakev
7c344e8e4c feat: use more precise ranges for imports (#958) 2022-11-29 19:01:39 -05:00
Hayden
ca38c7ac48 Grouped format implementation (#954) 2022-11-29 18:45:16 -05:00
Guillaume Andreu Sabater
602291c0c2 README: fixed conf section typo (#959) 2022-11-29 09:27:02 -05:00
Charlie Marsh
d4cf376e9b Fix failing pyproject test 2022-11-29 00:00:43 -05:00
Charlie Marsh
0e6a38e6d8 Bump version to 0.0.146 2022-11-28 22:27:41 -05:00
Charlie Marsh
058fd8748d Re-generate check code prefix and rules table 2022-11-28 22:26:32 -05:00
Charlie Marsh
e8247e3ed9 Run cargo fmt 2022-11-28 22:25:09 -05:00
Charlie Marsh
ea73c717be Remove pre-commit note in README.md (#956) 2022-11-28 22:18:59 -05:00
Charlie Marsh
427e0c3158 Allow preservation of external check codes (#955) 2022-11-28 22:16:17 -05:00
Charlie Marsh
dca994d05f Bump version to 0.0.145 2022-11-28 20:57:58 -05:00
Charlie Marsh
9944246f98 Rewrite type annotations on Python 3.7 when __future__ enabled (#953) 2022-11-28 20:57:38 -05:00
Charlie Marsh
82b0b7941a Implement eradicate (#947) 2022-11-28 20:54:33 -05:00
55 changed files with 1616 additions and 672 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.144
rev: v0.0.147
hooks:
- id: ruff

6
Cargo.lock generated
View File

@@ -700,7 +700,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.144-dev.0"
version = "0.0.147-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -1805,7 +1805,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.144"
version = "0.0.147"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1856,7 +1856,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.144"
version = "0.0.147"
dependencies = [
"anyhow",
"clap 4.0.22",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.144"
version = "0.0.147"
edition = "2021"
rust-version = "1.65.0"

25
LICENSE
View File

@@ -267,6 +267,31 @@ are:
SOFTWARE.
"""
- flake8-eradicate, licensed as follows:
"""
MIT License
Copyright (c) 2018 Nikita Sobolev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-tidy-imports, licensed as follows:
"""
MIT License

201
README.md
View File

@@ -27,9 +27,11 @@ An extremely fast Python linter, written in Rust.
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/),
[`yesqa`](https://github.com/asottile/yesqa), and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/)
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
faster than any individual tool.
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool. Ruff goes beyond the
responsibilities of a traditional linter, instead functioning as an advanced code transformation
tool capable of upgrading type annotations, rewriting class definitions, sorting imports, and more.
Ruff is extremely actively developed and used in major open-source projects like:
@@ -68,11 +70,12 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [Configuration](#configuration)
1. [Supported Rules](#supported-rules)
1. [Pyflakes (F)](#pyflakes)
1. [pycodestyle (E)](#pycodestyle)
1. [pycodestyle (E, W)](#pycodestyle)
1. [isort (I)](#isort)
1. [pydocstyle (D)](#pydocstyle)
1. [pyupgrade (U)](#pyupgrade)
1. [pep8-naming (N)](#pep8-naming)
1. [eradicate (ERA)](#eradicate)
1. [flake8-bandit (S)](#flake8-bandit)
1. [flake8-comprehensions (C)](#flake8-comprehensions)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
@@ -134,14 +137,11 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.144
rev: v0.0.147
hooks:
- id: ruff
```
<!-- TODO(charlie): Remove this message a few versions after v0.0.86. -->
_Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as the hook ID._
## Configuration
Ruff is configurable both via `pyproject.toml` and the command line. For a full list of configurable
@@ -238,10 +238,10 @@ See `ruff --help` for more:
```shell
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] <FILES>...
Usage: ruff [OPTIONS] [FILES]...
Arguments:
<FILES>...
[FILES]...
Options:
--config <CONFIG>
@@ -279,7 +279,7 @@ Options:
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text, json]
Output serialization format for error messages [default: text] [possible values: text, json, grouped]
--show-source
Show violations with source code
--show-files
@@ -298,6 +298,8 @@ Options:
Max McCabe complexity allowed for a function
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--explain <EXPLAIN>
Explain a rule
-h, --help
Print help information
-V, --version
@@ -533,6 +535,14 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyP
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
### eradicate
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
### flake8-bandit
For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on PyPI.
@@ -854,27 +864,29 @@ plugins, (2) alongside Black, and (3) on Python 3 code.
Under those conditions, Ruff implements every rule in Flake8, with the exception of `F811`.
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
natively, including:
natively, including:
- [`isort`](https://pypi.org/project/isort/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`isort`](https://pypi.org/project/isort/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
@@ -891,20 +903,21 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/)
- [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
@@ -1238,7 +1251,7 @@ exclude = [".venv"]
---
#### [`extend_exclude`](#extend_exclude)
#### [`extend-exclude`](#extend-exclude)
A list of file patterns to omit from linting, in addition to those specified by `exclude`.
@@ -1278,7 +1291,7 @@ ignore = ["F841"]
---
#### [`extend_ignore`](#extend_ignore)
#### [`extend-ignore`](#extend-ignore)
A list of check code prefixes to ignore, in addition to those specified by `ignore`.
@@ -1318,7 +1331,7 @@ select = ["E", "F", "B", "Q"]
---
#### [`extend_select`](#extend_select)
#### [`extend-select`](#extend-select)
A list of check code prefixes to enable, in addition to those specified by `select`.
@@ -1336,6 +1349,27 @@ extend-select = ["B", "Q"]
---
#### [`external`](#external)
A list of check codes that are unsupported by Ruff, but should be preserved when (e.g.)
validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not
yet implemented in Ruff.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff]
# Avoiding flagging (and removing) `V101` from any `# noqa` directives, despite Ruff's lack of
# support for `vulture`.
external = ["V101"]
```
---
#### [`fix`](#fix)
Enable autofix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix`
@@ -1390,7 +1424,7 @@ unfixable = ["F401"]
---
#### [`line_length`](#line_length)
#### [`line-length`](#line-length)
The line length to use when enforcing long-lines violations (like E501).
@@ -1408,7 +1442,26 @@ line-length = 120
---
#### [`per_file_ignores`](#per_file_ignores)
#### [`format`](#format)
The style in which violation messages should be formatted: `"text"` (default), `"grouped"`
(group messages by file), or `"json"` (machine-readable).
**Default value**: `"text"`
**Type**: `SerializationFormat`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`per-file-ignores`](#per-file-ignores)
A list of mappings from file pattern to check code prefixes to exclude, when considering any
matching files.
@@ -1429,7 +1482,7 @@ matching files.
---
#### [`show_source`](#show_source)
#### [`show-source`](#show-source)
Whether to show source code snippets when reporting lint error violations (overridden by the
`--show-source` command-line flag).
@@ -1443,7 +1496,7 @@ Whether to show source code snippets when reporting lint error violations (overr
```toml
[tool.ruff]
# By default, always show source code snippets.
show_source = true
show-source = true
```
---
@@ -1466,7 +1519,7 @@ src = ["src", "test"]
---
#### [`target_version`](#target_version)
#### [`target-version`](#target-version)
The Python version to target, e.g., when considering automatic code upgrades, like rewriting type
annotations. Note that the target version will _not_ be inferred from the _current_ Python version,
@@ -1486,7 +1539,7 @@ target-version = "py37"
### `flake8-annotations`
#### [`mypy_init_return`](#mypy_init_return)
#### [`mypy-init-return`](#mypy-init-return)
Whether to allow the omission of a return type hint for `__init__` if at least one argument is
annotated.
@@ -1499,12 +1552,12 @@ annotated.
```toml
[tool.ruff.flake8-annotations]
mypy_init_return = true
mypy-init-return = true
```
---
#### [`suppress_dummy_args`](#suppress_dummy_args)
#### [`suppress-dummy-args`](#suppress-dummy-args)
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable regex (like
`_`).
@@ -1517,12 +1570,12 @@ Whether to suppress `ANN000`-level errors for arguments matching the "dummy" var
```toml
[tool.ruff.flake8-annotations]
suppress_dummy_args = true
suppress-dummy-args = true
```
---
#### [`suppress_none_returning`](#suppress_none_returning)
#### [`suppress-none-returning`](#suppress-none-returning)
Whether to suppress `ANN200`-level errors for functions that meet either of the following criteria:
@@ -1537,12 +1590,12 @@ Whether to suppress `ANN200`-level errors for functions that meet either of the
```toml
[tool.ruff.flake8-annotations]
suppress_none_returning = true
suppress-none-returning = true
```
---
#### [`allow_star_arg_any`](#allow_star_arg_any)
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
@@ -1554,12 +1607,12 @@ Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` argume
```toml
[tool.ruff.flake8-annotations]
allow_star_arg_any = true
allow-star-arg-any = true
```
### `flake8-bugbear`
#### [`extend_immutable_calls`](#extend_immutable_calls)
#### [`extend-immutable-calls`](#extend-immutable-calls)
Additional callable functions to consider "immutable" when evaluating, e.g., no-mutable-default-argument
checks (`B006`).
@@ -1578,7 +1631,7 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
### `flake8-quotes`
#### [`inline_quotes`](#inline_quotes)
#### [`inline-quotes`](#inline-quotes)
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
@@ -1595,7 +1648,7 @@ inline-quotes = "single"
---
#### [`multiline_quotes`](#multiline_quotes)
#### [`multiline-quotes`](#multiline-quotes)
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
@@ -1612,7 +1665,7 @@ multiline-quotes = "single"
---
#### [`docstring_quotes`](#docstring_quotes)
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
@@ -1629,7 +1682,7 @@ docstring-quotes = "single"
---
#### [`avoid_escape`](#avoid_escape)
#### [`avoid-escape`](#avoid-escape)
Whether to avoid using single quotes if a string contains single quotes, or vice-versa with
double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the
@@ -1649,7 +1702,7 @@ avoid-escape = false
### `flake8-tidy-imports`
#### [`ban_relative_imports`](#ban_relative_imports)
#### [`ban-relative-imports`](#ban-relative-imports)
Whether to ban all relative imports (`"all"`), or only those imports that extend into the parent
module and beyond (`"parents"`).
@@ -1668,7 +1721,7 @@ ban-relative-imports = "all"
### `isort`
#### [`known_first_party`](known_first_party)
#### [`known-first-party`](known-first-party)
A list of modules to consider first-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
@@ -1686,7 +1739,7 @@ known-first-party = ["src"]
---
#### [`known_third_party`](known_third_party)
#### [`known-third-party`](known-third-party)
A list of modules to consider third-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
@@ -1704,7 +1757,7 @@ known-third-party = ["fastapi"]
---
#### [`extra_standard_library`](extra_standard_library)
#### [`extra-standard-library`](extra-standard-library)
A list of modules to consider standard-library, in addition to those known to Ruff in advance.
@@ -1721,7 +1774,7 @@ extra-standard-library = ["path"]
### `mccabe`
#### [`max_complexity`](#max_complexity)
#### [`max-complexity`](#max-complexity)
The maximum McCabe complexity to allow before triggering `C901` errors.
@@ -1732,14 +1785,14 @@ The maximum McCabe complexity to allow before triggering `C901` errors.
**Example usage**:
```toml
[tool.ruff.flake8-tidy-imports]
[tool.ruff.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 5
```
### `pep8-naming`
#### [`ignore_names`](#ignore_names)
#### [`ignore-names`](#ignore-names)
A list of names to ignore when considering `pep8-naming` violations.
@@ -1756,7 +1809,7 @@ ignore-names = ["callMethod"]
---
#### [`classmethod_decorators`](#classmethod_decorators)
#### [`classmethod-decorators`](#classmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a class method. For example, Ruff will expect that any method decorated by a decorator in this list
@@ -1776,7 +1829,7 @@ classmethod-decorators = ["classmethod", "pydantic.validator"]
---
#### [`staticmethod_decorators`](#staticmethod_decorators)
#### [`staticmethod-decorators`](#staticmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a static method. For example, Ruff will expect that any method decorated by a decorator in this list
@@ -1794,6 +1847,26 @@ has no `self` or `cls` argument.
staticmethod-decorators = ["staticmethod", "stcmthd"]
```
### `pyupgrade`
#### [`keep-runtime-typing`](#keep-runtime-typing)
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`)
rewrites even if a file imports `from __future__ import annotations`. Note that this setting is
only applicable when the target Python version is below 3.9 and 3.10 respectively.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
```
## License
MIT

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.144"
version = "0.0.147"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.144"
version = "0.0.147"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.144-dev.0"
version = "0.0.147-dev.0"
edition = "2021"
[lib]

View File

@@ -248,8 +248,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -269,6 +271,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -287,8 +290,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -308,6 +313,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -326,8 +332,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -347,6 +355,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -365,8 +374,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -386,6 +397,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -404,8 +416,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -430,6 +444,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -451,8 +466,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -507,6 +524,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -525,8 +543,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -552,6 +572,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);

View File

@@ -6,18 +6,19 @@ use ruff::checks_gen::CheckCodePrefix;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
Flake8Annotations,
Flake8Bandit,
Flake8BlindExcept,
Flake8Bugbear,
Flake8Builtins,
Flake8Comprehensions,
Flake8Debugger,
Flake8Docstrings,
Flake8TidyImports,
Flake8Eradicate,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Flake8TidyImports,
McCabe,
Flake8BlindExcept,
PEP8Naming,
Pyupgrade,
}
@@ -27,17 +28,18 @@ impl FromStr for Plugin {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-bandit" => Ok(Plugin::Flake8Bandit),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"mccabe" => Ok(Plugin::McCabe),
"pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade),
@@ -49,17 +51,18 @@ impl FromStr for Plugin {
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8Bandit => CheckCodePrefix::S,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
Plugin::Flake8Debugger => CheckCodePrefix::T1,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
@@ -68,7 +71,9 @@ impl Plugin {
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
@@ -89,11 +94,10 @@ impl Plugin {
// Default to PEP8.
DocstringConvention::PEP8.select()
}
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
@@ -281,31 +285,6 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
@@ -331,6 +310,41 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-eradicate
"eradicate-aggressive" | "eradicate_aggressive" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist" | "eradicate_whitelist" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist-extend" | "eradicate_whitelist_extend" => {
plugins.insert(Plugin::Flake8Eradicate);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-tidy-imports
"ban-relative-imports" | "ban_relative_imports" => {
plugins.insert(Plugin::Flake8TidyImports);
@@ -364,17 +378,18 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Annotations,
Plugin::Flake8Bandit,
Plugin::Flake8BlindExcept,
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Debugger,
Plugin::Flake8Docstrings,
Plugin::Flake8TidyImports,
Plugin::Flake8Eradicate,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::Flake8BlindExcept,
Plugin::Flake8TidyImports,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]

View File

@@ -31,7 +31,3 @@ build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true
[tool.isort]
profile = "black"
known_third_party = ["fastapi", "pydantic", "starlette"]

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

@@ -0,0 +1,13 @@
#import os
# from foo import junk
#a = 3
a = 4
#foo(1, 2, 3)
def foo(x, y, z):
contentet = 1 # print('hello')
print(x, y, z)
# This is a real comment.
#return True
return False

View File

@@ -18,6 +18,9 @@ def f() -> None:
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Invalid (but external)
d = 1 # noqa: F841, V101
# fmt: off
# Invalid - no space before #
d = 1# noqa: E501

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from models import (
Fruit,
@@ -28,3 +29,12 @@ class Foo:
@classmethod
def d(cls) -> Fruit:
return cls(x=0, y=0)
def f(x: int) -> List[int]:
y = List[int]()
y.append(x)
return y
x: Optional[int] = None

View File

@@ -5,6 +5,7 @@ extend-exclude = [
"migrations",
"with_excluded_file/other_excluded_file.py",
]
external = ["V101"]
per-file-ignores = { "__init__.py" = ["F401"] }
[tool.ruff.flake8-bugbear]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.144"
version = "0.0.147"
edition = "2021"
[dependencies]

View File

@@ -107,9 +107,3 @@ pub struct Binding {
/// the binding was last used.
pub used: Option<(usize, Range)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,
ImportFrom,
}

View File

@@ -4,14 +4,14 @@ use serde::{Deserialize, Serialize};
pub mod fixer;
pub mod helpers;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Patch {
pub content: String,
pub location: Location,
pub end_location: Location,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub patch: Patch,
}

View File

@@ -3,7 +3,6 @@
use std::collections::BTreeMap;
use std::path::Path;
use itertools::Itertools;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Withitem;
@@ -19,8 +18,7 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Node, Range,
Scope, ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::{helpers, operations, visitor};
@@ -78,6 +76,7 @@ pub struct Checker<'a> {
in_f_string: Option<Range>,
in_annotation: bool,
in_deferred_string_annotation: bool,
in_deferred_annotation: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
@@ -124,6 +123,7 @@ impl<'a> Checker<'a> {
in_f_string: None,
in_annotation: false,
in_deferred_string_annotation: false,
in_deferred_annotation: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
@@ -592,7 +592,7 @@ where
self.binding_context(),
),
used: None,
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
} else {
@@ -627,12 +627,12 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
))
} else {
None
},
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
}
@@ -752,9 +752,9 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
)),
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
@@ -766,7 +766,7 @@ where
if !ALL_FEATURE_NAMES.contains(&&*alias.node.name) {
self.add_check(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
Range::from_located(stmt),
Range::from_located(alias),
));
}
}
@@ -828,6 +828,7 @@ where
None => alias.node.name.to_string(),
Some(parent) => format!("{parent}.{}", alias.node.name),
};
let range = Range::from_located(alias);
self.add_binding(
name,
Binding {
@@ -850,12 +851,12 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
range,
))
} else {
None
},
range: Range::from_located(stmt),
range,
},
);
}
@@ -1191,10 +1192,14 @@ where
// Pre-visit.
match &expr.node {
ExprKind::Subscript { value, slice, .. } => {
// Ex) typing.List[...]
// Ex) Optional[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U007)
&& self.settings.target_version >= PythonVersion::Py310
&& (self.settings.target_version >= PythonVersion::Py310
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
{
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
}
@@ -1233,7 +1238,11 @@ where
// Ex) List[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(
expr,
&self.from_imports,
@@ -1268,8 +1277,12 @@ where
}
ExprKind::Attribute { attr, .. } => {
// Ex) typing.List[...]
if self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(expr, &self.from_imports, &self.import_aliases)
{
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
@@ -2743,7 +2756,9 @@ impl<'a> Checker<'a> {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_annotation = true;
self.visit_expr(expr);
self.in_deferred_annotation = false;
}
}
@@ -2769,9 +2784,9 @@ impl<'a> Checker<'a> {
}
}
for (expr, (scopes, parents)) in allocator.iter().zip(stacks) {
self.in_deferred_string_annotation = true;
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_string_annotation = true;
self.visit_expr(expr);
self.in_deferred_string_annotation = false;
}
@@ -2902,68 +2917,54 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::F401) {
// Collect all unused imports by location. (Multiple unused imports at the same
// location indicates an `import from`.)
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
type UnusedImport<'a> = (&'a String, &'a Range);
let mut unused: BTreeMap<(usize, Option<usize>), Vec<UnusedImport>> =
BTreeMap::new();
for (name, binding) in &scope.values {
if !matches!(
binding.kind,
BindingKind::Importation(..)
| BindingKind::SubmoduleImportation(..)
| BindingKind::FromImportation(..)
) {
continue;
}
let (full_name, context) = match &binding.kind {
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context) => {
(full_name, context)
}
_ => continue,
};
let used = binding.used.is_some()
// Skip used exports from `__all__`
if binding.used.is_some()
|| all_names
.as_ref()
.map(|names| names.contains(name))
.unwrap_or_default();
if !used {
match &binding.kind {
BindingKind::FromImportation(_, full_name, context) => {
unused
.entry((
ImportKind::ImportFrom,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context) => {
unused
.entry((
ImportKind::Import,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
_ => unreachable!("Already filtered on BindingKind."),
}
.unwrap_or_default()
{
continue;
}
unused
.entry((context.defined_by, context.defined_in))
.or_default()
.push((full_name, &binding.range));
}
for ((kind, defined_by, defined_in), full_names) in unused {
for ((defined_by, defined_in), unused_imports) in unused {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if self.patch(&CheckCode::F401) {
let in_init_py = self.path.ends_with("__init__.py");
let fix = if !in_init_py && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match match kind {
ImportKind::Import => pyflakes::fixes::remove_unused_imports,
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
}(
self.locator, &full_names, child, parent, &deleted
match pyflakes::fixes::remove_unused_imports(
self.locator,
&unused_imports,
child,
parent,
&deleted,
) {
Ok(fix) => {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
@@ -2980,24 +2981,13 @@ impl<'a> Checker<'a> {
None
};
if self.path.ends_with("__init__.py") {
checks.push(Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
true,
),
Range::from_located(child),
));
} else {
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
false,
),
Range::from_located(child),
CheckKind::UnusedImport(full_name.clone(), in_init_py),
*range,
);
if let Some(fix) = fix {
check.amend(fix);
if let Some(fix) = fix.as_ref() {
check.amend(fix.clone());
}
checks.push(check);
}

View File

@@ -14,9 +14,9 @@ use crate::settings::Settings;
// Regex from PEP263
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").expect("Invalid regex"));
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").unwrap());
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").expect("Invalid regex"));
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap());
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
@@ -212,7 +212,7 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
if matches.contains(&code) {
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());

View File

@@ -6,7 +6,7 @@ use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, rules, Settings};
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
pub fn check_tokens(
locator: &SourceCodeLocator,
@@ -23,6 +23,7 @@ pub fn check_tokens(
|| settings.enabled.contains(&CheckCode::Q001)
|| settings.enabled.contains(&CheckCode::Q002)
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_commented_out_code = settings.enabled.contains(&CheckCode::ERA001);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let mut state_machine = StateMachine::default();
@@ -72,6 +73,17 @@ pub fn check_tokens(
}
}
// eradicate
if enforce_commented_out_code {
if matches!(tok, Tok::Comment) {
if let Some(check) =
eradicate::checks::commented_out_code(locator, start, end, settings, autofix)
{
checks.push(check);
}
}
}
// W605
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {

View File

@@ -254,6 +254,8 @@ pub enum CheckCode {
N818,
// isort
I001,
// eradicate
ERA001,
// flake8-bandit
S101,
S102,
@@ -282,6 +284,7 @@ pub enum CheckCategory {
Pydocstyle,
Pyupgrade,
PEP8Naming,
Eradicate,
Flake8Bandit,
Flake8Comprehensions,
Flake8Debugger,
@@ -302,69 +305,71 @@ pub enum CheckCategory {
impl CheckCategory {
pub fn title(&self) -> &'static str {
match self {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Isort => "isort",
CheckCategory::Eradicate => "eradicate",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake8Bandit => "flake8-bandit",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Flake8BooleanTrap => "flake8-boolean-trap",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
CheckCategory::Ruff => "Ruff-specific rules",
CheckCategory::Meta => "Meta rules",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
}
pub fn url(&self) -> Option<&'static str> {
match self {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
CheckCategory::Eradicate => Some("https://pypi.org/project/eradicate/2.1.0/"),
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"),
CheckCategory::Flake8BlindExcept => {
Some("https://pypi.org/project/flake8-blind-except/0.2.1/")
}
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
}
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8Debugger => {
Some("https://pypi.org/project/flake8-debugger/4.1.2/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"),
CheckCategory::Flake8BlindExcept => {
Some("https://pypi.org/project/flake8-blind-except/0.2.1/")
}
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
}
CheckCategory::Ruff => None,
CheckCategory::Meta => None,
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Ruff => None,
}
}
}
@@ -444,7 +449,7 @@ pub enum CheckKind {
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>, bool),
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction,
// flake8-builtins
@@ -611,6 +616,8 @@ pub enum CheckKind {
ErrorSuffixOnExceptionName(String),
// isort
UnsortedImports,
// eradicate
CommentedOutCode,
// flake8-bandit
AssertUsed,
ExecUsed,
@@ -641,7 +648,8 @@ impl CheckCode {
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
&LintSource::Lines
}
CheckCode::Q000
CheckCode::ERA001
| CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
| CheckCode::Q003
@@ -677,7 +685,7 @@ impl CheckCode {
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()], false),
CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
@@ -925,6 +933,8 @@ impl CheckCode {
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
// isort
CheckCode::I001 => CheckKind::UnsortedImports,
// eradicate
CheckCode::ERA001 => CheckKind::CommentedOutCode,
// flake8-bandit
CheckCode::S101 => CheckKind::AssertUsed,
CheckCode::S102 => CheckKind::ExecUsed,
@@ -950,67 +960,20 @@ impl CheckCode {
pub fn category(&self) -> CheckCategory {
#[allow(clippy::match_same_arms)]
match self {
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F501 => CheckCategory::Pyflakes,
CheckCode::F502 => CheckCategory::Pyflakes,
CheckCode::F503 => CheckCategory::Pyflakes,
CheckCode::F504 => CheckCategory::Pyflakes,
CheckCode::F505 => CheckCategory::Pyflakes,
CheckCode::F506 => CheckCategory::Pyflakes,
CheckCode::F507 => CheckCategory::Pyflakes,
CheckCode::F508 => CheckCategory::Pyflakes,
CheckCode::F509 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::A001 => CheckCategory::Flake8Builtins,
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
@@ -1055,49 +1018,7 @@ impl CheckCode {
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
CheckCode::T100 => CheckCategory::Flake8Debugger,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
@@ -1142,6 +1063,69 @@ impl CheckCode {
CheckCode::D417 => CheckCategory::Pydocstyle,
CheckCode::D418 => CheckCategory::Pydocstyle,
CheckCode::D419 => CheckCategory::Pydocstyle,
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::ERA001 => CheckCategory::Eradicate,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F501 => CheckCategory::Pyflakes,
CheckCode::F502 => CheckCategory::Pyflakes,
CheckCode::F503 => CheckCategory::Pyflakes,
CheckCode::F504 => CheckCategory::Pyflakes,
CheckCode::F505 => CheckCategory::Pyflakes,
CheckCode::F506 => CheckCategory::Pyflakes,
CheckCode::F507 => CheckCategory::Pyflakes,
CheckCode::F508 => CheckCategory::Pyflakes,
CheckCode::F509 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
CheckCode::N803 => CheckCategory::PEP8Naming,
@@ -1157,22 +1141,49 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
CheckCode::S104 => CheckCategory::Flake8Bandit,
CheckCode::S105 => CheckCategory::Flake8Bandit,
CheckCode::S106 => CheckCategory::Flake8Bandit,
CheckCode::S107 => CheckCategory::Flake8Bandit,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::T100 => CheckCategory::Flake8Debugger,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
}
}
}
@@ -1405,6 +1416,8 @@ impl CheckKind {
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
// isort
CheckKind::UnsortedImports => &CheckCode::I001,
// eradicate
CheckKind::CommentedOutCode => &CheckCode::ERA001,
// flake8-bandit
CheckKind::AssertUsed => &CheckCode::S101,
CheckKind::ExecUsed => &CheckCode::S102,
@@ -1590,12 +1603,11 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(names, in_init_py) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
CheckKind::UnusedImport(name, in_init_py) => {
if *in_init_py {
format!("{names} imported but unused and missing from `__all__`")
format!("`{name}` imported but unused and missing from `__all__`")
} else {
format!("{names} imported but unused")
format!("`{name}` imported but unused")
}
}
CheckKind::UnusedVariable(name) => {
@@ -2117,6 +2129,8 @@ impl CheckKind {
}
// isort
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
// eradicate
CheckKind::CommentedOutCode => "Found commented-out code".to_string(),
// flake8-bandit
CheckKind::AssertUsed => "Use of `assert` detected".to_string(),
CheckKind::ExecUsed => "Use of `exec` detected".to_string(),
@@ -2221,6 +2235,7 @@ impl CheckKind {
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)

View File

@@ -176,6 +176,10 @@ pub enum CheckCodePrefix {
E902,
E99,
E999,
ERA,
ERA0,
ERA00,
ERA001,
F,
F4,
F40,
@@ -187,6 +191,16 @@ pub enum CheckCodePrefix {
F406,
F407,
F5,
F50,
F501,
F502,
F503,
F504,
F505,
F506,
F507,
F508,
F509,
F52,
F521,
F522,
@@ -853,6 +867,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => vec![CheckCode::E902],
CheckCodePrefix::E99 => vec![CheckCode::E999],
CheckCodePrefix::E999 => vec![CheckCode::E999],
CheckCodePrefix::ERA => vec![CheckCode::ERA001],
CheckCodePrefix::ERA0 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA00 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA001 => vec![CheckCode::ERA001],
CheckCodePrefix::F => vec![
CheckCode::F401,
CheckCode::F402,
@@ -923,6 +941,15 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => vec![CheckCode::F406],
CheckCodePrefix::F407 => vec![CheckCode::F407],
CheckCodePrefix::F5 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
@@ -930,6 +957,26 @@ impl CheckCodePrefix {
CheckCode::F525,
CheckCode::F541,
],
CheckCodePrefix::F50 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
],
CheckCodePrefix::F501 => vec![CheckCode::F501],
CheckCodePrefix::F502 => vec![CheckCode::F502],
CheckCodePrefix::F503 => vec![CheckCode::F503],
CheckCodePrefix::F504 => vec![CheckCode::F504],
CheckCodePrefix::F505 => vec![CheckCode::F505],
CheckCodePrefix::F506 => vec![CheckCode::F506],
CheckCodePrefix::F507 => vec![CheckCode::F507],
CheckCodePrefix::F508 => vec![CheckCode::F508],
CheckCodePrefix::F509 => vec![CheckCode::F509],
CheckCodePrefix::F52 => vec![
CheckCode::F521,
CheckCode::F522,
@@ -1455,6 +1502,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => PrefixSpecificity::Explicit,
CheckCodePrefix::E99 => PrefixSpecificity::Tens,
CheckCodePrefix::E999 => PrefixSpecificity::Explicit,
CheckCodePrefix::ERA => PrefixSpecificity::Category,
CheckCodePrefix::ERA0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ERA00 => PrefixSpecificity::Tens,
CheckCodePrefix::ERA001 => PrefixSpecificity::Explicit,
CheckCodePrefix::F => PrefixSpecificity::Category,
CheckCodePrefix::F4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F40 => PrefixSpecificity::Tens,
@@ -1466,6 +1517,16 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => PrefixSpecificity::Explicit,
CheckCodePrefix::F407 => PrefixSpecificity::Explicit,
CheckCodePrefix::F5 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F50 => PrefixSpecificity::Tens,
CheckCodePrefix::F501 => PrefixSpecificity::Explicit,
CheckCodePrefix::F502 => PrefixSpecificity::Explicit,
CheckCodePrefix::F503 => PrefixSpecificity::Explicit,
CheckCodePrefix::F504 => PrefixSpecificity::Explicit,
CheckCodePrefix::F505 => PrefixSpecificity::Explicit,
CheckCodePrefix::F506 => PrefixSpecificity::Explicit,
CheckCodePrefix::F507 => PrefixSpecificity::Explicit,
CheckCodePrefix::F508 => PrefixSpecificity::Explicit,
CheckCodePrefix::F509 => PrefixSpecificity::Explicit,
CheckCodePrefix::F52 => PrefixSpecificity::Tens,
CheckCodePrefix::F521 => PrefixSpecificity::Explicit,
CheckCodePrefix::F522 => PrefixSpecificity::Explicit,
@@ -1630,6 +1691,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::ERA,
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,

View File

@@ -7,8 +7,9 @@ use rustc_hash::FxHashMap;
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::types::{FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion};
use crate::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
};
#[derive(Debug, Parser)]
#[command(author, about = "Ruff: An extremely fast Python linter.")]
@@ -77,8 +78,8 @@ pub struct Cli {
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t = SerializationFormat::Text)]
pub format: SerializationFormat,
#[arg(long, value_enum)]
pub format: Option<SerializationFormat>,
/// Show violations with source code.
#[arg(long)]
pub show_source: bool,
@@ -143,8 +144,6 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
LogLevel::Quiet
} else if cli.verbose {
LogLevel::Verbose
} else if matches!(cli.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
LogLevel::Default
}

View File

@@ -6,7 +6,7 @@ use walkdir::DirEntry;
use crate::checks::CheckCode;
use crate::fs::iter_python_files;
use crate::printer::SerializationFormat;
use crate::settings::types::SerializationFormat;
use crate::{Configuration, Settings};
/// Print the user-facing configuration settings.
@@ -43,7 +43,7 @@ struct Explanation<'a> {
/// Explain a `CheckCode` to the user.
pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
match format {
SerializationFormat::Text => {
SerializationFormat::Text | SerializationFormat::Grouped => {
println!(
"{} ({}): {}",
code.as_ref(),

51
src/eradicate/checks.rs Normal file
View File

@@ -0,0 +1,51 @@
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{CheckCode, CheckKind};
use crate::eradicate::detection::comment_contains_code;
use crate::{Check, Settings, SourceCodeLocator};
fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
} else if !char.is_whitespace() {
return false;
}
}
unreachable!("Comment should contain '#' character")
}
/// ERA001
pub fn commented_out_code(
locator: &SourceCodeLocator,
start: Location,
end: Location,
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
let line = locator.slice_source_code_range(&Range {
location,
end_location,
});
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(&line) && comment_contains_code(&line) {
let mut check = Check::new(
CheckKind::CommentedOutCode,
Range {
location: start,
end_location: end,
},
);
if autofix && settings.fixable.contains(&CheckCode::ERA001) {
check.amend(Fix::deletion(location, end_location));
}
Some(check)
} else {
None
}
}

223
src/eradicate/detection.rs Normal file
View File

@@ -0,0 +1,223 @@
/// See: [eradicate.py](https://github.com/myint/eradicate/blob/98f199940979c94447a461d50d27862b118b282d/eradicate.py)
use once_cell::sync::Lazy;
use regex::Regex;
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX"
).unwrap()
});
static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap());
static CODE_INDICATORS: &[&str] = &[
"(", ")", "[", "]", "{", "}", ":", "=", "%", "print", "return", "break", "continue", "import",
];
static CODE_KEYWORDS: Lazy<Vec<Regex>> = Lazy::new(|| {
vec![
Regex::new(r"^\s*elif\s+.*\s*:\s*$").unwrap(),
Regex::new(r"^\s*else\s*:\s*$").unwrap(),
Regex::new(r"^\s*try\s*:\s*$").unwrap(),
Regex::new(r"^\s*finally\s*:\s*$").unwrap(),
Regex::new(r"^\s*except\s+.*\s*:\s*$").unwrap(),
]
});
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)").unwrap());
static HASH_NUMBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d").unwrap());
static MULTILINE_ASSIGNMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*\w+\s*=.*[(\[{]$").unwrap());
static PARTIAL_DICTIONARY_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^\s*['"]\w+['"]\s*:.+[,{]\s*$"#).unwrap());
static PRINT_RETURN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap());
/// Returns `true` if a comment contains Python code.
pub fn comment_contains_code(line: &str) -> bool {
let line = if let Some(line) = line.trim().strip_prefix('#') {
line.trim()
} else {
return false;
};
// Ignore non-comment related hashes (e.g., "# Issue #999").
if HASH_NUMBER.is_match(line) {
return false;
}
// Ignore whitelisted comments.
if ALLOWLIST_REGEX.is_match(line) {
return false;
}
if CODING_COMMENT_REGEX.is_match(line) {
return false;
}
// Check that this is possibly code.
if CODE_INDICATORS.iter().all(|symbol| !line.contains(symbol)) {
return false;
}
if multiline_case(line) {
return true;
}
if CODE_KEYWORDS.iter().any(|symbol| symbol.is_match(line)) {
return true;
}
let line = PRINT_RETURN_REGEX.replace_all(line, "");
if PARTIAL_DICTIONARY_REGEX.is_match(&line) {
return true;
}
// Finally, compile the source code.
rustpython_parser::parser::parse_program(&line, "<filename>").is_ok()
}
/// Returns `true` if a line is probably part of some multiline code.
fn multiline_case(line: &str) -> bool {
if line.ends_with('\\') {
return true;
}
if MULTILINE_ASSIGNMENT_REGEX.is_match(line) {
return true;
}
if BRACKET_REGEX.is_match(line) {
return true;
}
false
}
#[cfg(test)]
mod tests {
use crate::eradicate::detection::comment_contains_code;
#[test]
fn comment_contains_code_basic() {
assert!(comment_contains_code("# x = 1"));
assert!(comment_contains_code("#from foo import eradicate"));
assert!(comment_contains_code("#import eradicate"));
assert!(comment_contains_code(r#"#"key": value,"#));
assert!(comment_contains_code(r#"#"key": "value","#));
assert!(comment_contains_code(r#"#"key": 1 + 1,"#));
assert!(comment_contains_code("#'key': 1 + 1,"));
assert!(comment_contains_code(r#"#"key": {"#));
assert!(comment_contains_code("#}"));
assert!(comment_contains_code("#} )]"));
assert!(!comment_contains_code("#"));
assert!(!comment_contains_code("# This is a (real) comment."));
assert!(!comment_contains_code("# 123"));
assert!(!comment_contains_code("# 123.1"));
assert!(!comment_contains_code("# 1, 2, 3"));
assert!(!comment_contains_code("x = 1 # x = 1"));
assert!(!comment_contains_code(
"# pylint: disable=redefined-outer-name"
));
assert!(!comment_contains_code("# Issue #999: This is not code"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#},"));
}
#[test]
fn comment_contains_code_with_print() {
assert!(comment_contains_code("#print"));
assert!(comment_contains_code("#print(1)"));
assert!(comment_contains_code("#print 1"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_return() {
assert!(comment_contains_code("#return x"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_multiline() {
assert!(comment_contains_code("#else:"));
assert!(comment_contains_code("# else : "));
assert!(comment_contains_code(r#"# "foo %d" % \\"#));
assert!(comment_contains_code("#elif True:"));
assert!(comment_contains_code("#x = foo("));
assert!(comment_contains_code("#except Exception:"));
assert!(!comment_contains_code("# this is = to that :("));
assert!(!comment_contains_code("#else"));
assert!(!comment_contains_code("#or else:"));
assert!(!comment_contains_code("#else True:"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():"));
}
#[test]
fn comment_contains_code_with_sentences() {
assert!(!comment_contains_code("#code is good"));
}
#[test]
fn comment_contains_code_with_encoding() {
assert!(comment_contains_code("# codings=utf-8"));
assert!(!comment_contains_code("# coding=utf-8"));
assert!(!comment_contains_code("#coding= utf-8"));
assert!(!comment_contains_code("# coding: utf-8"));
assert!(!comment_contains_code("# encoding: utf8"));
}
#[test]
fn comment_contains_code_with_default_allowlist() {
assert!(!comment_contains_code("# pylint: disable=A0123"));
assert!(!comment_contains_code("# pylint:disable=A0123"));
assert!(!comment_contains_code("# pylint: disable = A0123"));
assert!(!comment_contains_code("# pylint:disable = A0123"));
assert!(!comment_contains_code("# pyright: reportErrorName=true"));
assert!(!comment_contains_code("# noqa"));
assert!(!comment_contains_code("# NOQA"));
assert!(!comment_contains_code("# noqa: A123"));
assert!(!comment_contains_code("# noqa:A123"));
assert!(!comment_contains_code("# nosec"));
assert!(!comment_contains_code("# fmt: on"));
assert!(!comment_contains_code("# fmt: off"));
assert!(!comment_contains_code("# fmt:on"));
assert!(!comment_contains_code("# fmt:off"));
assert!(!comment_contains_code("# isort: on"));
assert!(!comment_contains_code("# isort:on"));
assert!(!comment_contains_code("# isort: off"));
assert!(!comment_contains_code("# isort:off"));
assert!(!comment_contains_code("# isort: skip"));
assert!(!comment_contains_code("# isort:skip"));
assert!(!comment_contains_code("# isort: skip_file"));
assert!(!comment_contains_code("# isort:skip_file"));
assert!(!comment_contains_code("# isort: split"));
assert!(!comment_contains_code("# isort:split"));
assert!(!comment_contains_code("# isort: dont-add-imports"));
assert!(!comment_contains_code("# isort:dont-add-imports"));
assert!(!comment_contains_code(
"# isort: dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort: dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code("# type: ignore"));
assert!(!comment_contains_code("# type:ignore"));
assert!(!comment_contains_code("# type: ignore[import]"));
assert!(!comment_contains_code("# type:ignore[import]"));
assert!(!comment_contains_code("# TODO: Do that"));
assert!(!comment_contains_code("# FIXME: Fix that"));
assert!(!comment_contains_code("# XXX: What ever"));
}
}

2
src/eradicate/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod detection;

View File

@@ -1,4 +1,4 @@
//! Settings for the `pep8-naming` plugin.
//! Settings for the `flake8-bugbear` plugin.
use serde::{Deserialize, Serialize};

View File

@@ -39,6 +39,7 @@ pub mod commands;
mod cst;
mod directives;
mod docstrings;
mod eradicate;
mod flake8_2020;
pub mod flake8_annotations;
pub mod flake8_bandit;

View File

@@ -262,7 +262,13 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
true,
)?;
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
add_noqa(
path,
&checks,
&contents,
&directives.noqa_line_for,
&settings.external,
)
}
/// Apply autoformatting to the source code at the given `Path`.
@@ -399,6 +405,7 @@ mod tests {
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::settings;
use crate::settings::types::PythonVersion;
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
@@ -487,9 +494,9 @@ mod tests {
#[test_case(CheckCode::D414, Path::new("sections.py"); "D414")]
#[test_case(CheckCode::D415, Path::new("D.py"); "D415")]
#[test_case(CheckCode::D416, Path::new("D.py"); "D416")]
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
#[test_case(CheckCode::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
#[test_case(CheckCode::D417, Path::new("canonical_google_examples.py"); "D417_2")]
#[test_case(CheckCode::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
#[test_case(CheckCode::D418, Path::new("D.py"); "D418")]
#[test_case(CheckCode::D419, Path::new("D.py"); "D419")]
#[test_case(CheckCode::E402, Path::new("E402.py"); "E402")]
@@ -505,6 +512,7 @@ mod tests {
#[test_case(CheckCode::E742, Path::new("E742.py"); "E742")]
#[test_case(CheckCode::E743, Path::new("E743.py"); "E743")]
#[test_case(CheckCode::E999, Path::new("E999.py"); "E999")]
#[test_case(CheckCode::ERA001, Path::new("ERA001.py"); "ERA001")]
#[test_case(CheckCode::F401, Path::new("F401_0.py"); "F401_0")]
#[test_case(CheckCode::F401, Path::new("F401_1.py"); "F401_1")]
#[test_case(CheckCode::F401, Path::new("F401_2.py"); "F401_2")]
@@ -519,14 +527,14 @@ mod tests {
#[test_case(CheckCode::F406, Path::new("F406.py"); "F406")]
#[test_case(CheckCode::F407, Path::new("F407.py"); "F407")]
#[test_case(CheckCode::F501, Path::new("F50x.py"); "F501")]
#[test_case(CheckCode::F502, Path::new("F50x.py"); "F502_0")]
#[test_case(CheckCode::F502, Path::new("F502.py"); "F502_1")]
#[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")]
#[test_case(CheckCode::F502, Path::new("F50x.py"); "F502_0")]
#[test_case(CheckCode::F503, Path::new("F503.py"); "F503_1")]
#[test_case(CheckCode::F504, Path::new("F50x.py"); "F504_0")]
#[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")]
#[test_case(CheckCode::F504, Path::new("F504.py"); "F504_1")]
#[test_case(CheckCode::F505, Path::new("F50x.py"); "F505_0")]
#[test_case(CheckCode::F504, Path::new("F50x.py"); "F504_0")]
#[test_case(CheckCode::F505, Path::new("F504.py"); "F505_1")]
#[test_case(CheckCode::F505, Path::new("F50x.py"); "F505_0")]
#[test_case(CheckCode::F506, Path::new("F50x.py"); "F506")]
#[test_case(CheckCode::F507, Path::new("F50x.py"); "F507")]
#[test_case(CheckCode::F508, Path::new("F50x.py"); "F508")]
@@ -561,6 +569,9 @@ mod tests {
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
#[test_case(CheckCode::F841, Path::new("F841.py"); "F841")]
#[test_case(CheckCode::F901, Path::new("F901.py"); "F901")]
#[test_case(CheckCode::FBT001, Path::new("FBT.py"); "FBT001")]
#[test_case(CheckCode::FBT002, Path::new("FBT.py"); "FBT002")]
#[test_case(CheckCode::FBT003, Path::new("FBT.py"); "FBT003")]
#[test_case(CheckCode::N801, Path::new("N801.py"); "N801")]
#[test_case(CheckCode::N802, Path::new("N802.py"); "N802")]
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
@@ -576,6 +587,16 @@ mod tests {
#[test_case(CheckCode::N816, Path::new("N816.py"); "N816")]
#[test_case(CheckCode::N817, Path::new("N817.py"); "N817")]
#[test_case(CheckCode::N818, Path::new("N818.py"); "N818")]
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_0.py"); "RUF101_0")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_1.py"); "RUF101_1")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_2.py"); "RUF101_2")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_3.py"); "RUF101_3")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_4.py"); "RUF101_4")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_5.py"); "RUF101_5")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_6.py"); "RUF101_6")]
#[test_case(CheckCode::S101, Path::new("S101.py"); "S101")]
#[test_case(CheckCode::S102, Path::new("S102.py"); "S102")]
#[test_case(CheckCode::S104, Path::new("S104.py"); "S104")]
@@ -609,16 +630,6 @@ mod tests {
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
#[test_case(CheckCode::W605, Path::new("W605_0.py"); "W605_0")]
#[test_case(CheckCode::W605, Path::new("W605_1.py"); "W605_1")]
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_0.py"); "RUF101_0")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_1.py"); "RUF101_1")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_2.py"); "RUF101_2")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_3.py"); "RUF101_3")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_4.py"); "RUF101_4")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_5.py"); "RUF101_5")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_6.py"); "RUF101_6")]
#[test_case(CheckCode::YTT101, Path::new("YTT101.py"); "YTT101")]
#[test_case(CheckCode::YTT102, Path::new("YTT102.py"); "YTT102")]
#[test_case(CheckCode::YTT103, Path::new("YTT103.py"); "YTT103")]
@@ -629,9 +640,6 @@ mod tests {
#[test_case(CheckCode::YTT301, Path::new("YTT301.py"); "YTT301")]
#[test_case(CheckCode::YTT302, Path::new("YTT302.py"); "YTT302")]
#[test_case(CheckCode::YTT303, Path::new("YTT303.py"); "YTT303")]
#[test_case(CheckCode::FBT001, Path::new("FBT.py"); "FBT001")]
#[test_case(CheckCode::FBT002, Path::new("FBT.py"); "FBT002")]
#[test_case(CheckCode::FBT003, Path::new("FBT.py"); "FBT003")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
@@ -694,4 +702,64 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_585_p37() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py37,
..settings::Settings::for_rule(CheckCode::U006)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_585_py310() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py310,
..settings::Settings::for_rule(CheckCode::U006)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_604_p37() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py37,
..settings::Settings::for_rule(CheckCode::U007)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_604_py310() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py310,
..settings::Settings::for_rule(CheckCode::U007)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -23,8 +23,9 @@ use ::ruff::fs::iter_python_files;
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::printer::Printer;
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
@@ -189,14 +190,7 @@ fn inner_main() -> Result<ExitCode> {
// Extract command-line arguments.
let cli = Cli::parse();
let fix = cli.fix();
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
if let Some(code) = cli.explain {
commands::explain(&code, cli.format)?;
return Ok(ExitCode::SUCCESS);
}
if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut std::io::stdout());
@@ -205,17 +199,9 @@ fn inner_main() -> Result<ExitCode> {
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&cli.files);
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
let pyproject = cli
.config
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
// Reconcile configuration from pyproject.toml and command-line arguments.
let mut configuration =
@@ -247,6 +233,9 @@ fn inner_main() -> Result<ExitCode> {
if !cli.unfixable.is_empty() {
configuration.unfixable = cli.unfixable;
}
if let Some(format) = cli.format {
configuration.format = format;
}
if let Some(line_length) = cli.line_length {
configuration.line_length = line_length;
}
@@ -279,6 +268,30 @@ fn inner_main() -> Result<ExitCode> {
let fix_enabled: bool = configuration.fix;
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
// If we're using JSON, override the log level.
let log_level = if matches!(settings.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
log_level
};
set_up_logging(&log_level)?;
// Now that we've inferred the appropriate log level, add some debug
// information.
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
if let Some(code) = cli.explain {
commands::explain(&code, settings.format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS);
@@ -291,24 +304,21 @@ fn inner_main() -> Result<ExitCode> {
cache_enabled = false;
}
let printer = Printer::new(&cli.format, &log_level);
let printer = Printer::new(&settings.format, &log_level);
if cli.watch {
if settings.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
if fix_enabled {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
if cli.add_noqa {
eprintln!("Warning: --no-qa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
// Perform an initial run instantly.
printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");

View File

@@ -1,16 +1,10 @@
use std::cmp::Ordering;
use std::fmt;
use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use colored::Colorize;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::fs::relativize_path;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -50,57 +44,6 @@ impl PartialOrd for Message {
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&self.filename)).bold(),
":".cyan(),
self.location.row(),
":".cyan(),
self.location.column(),
":".cyan(),
self.kind.code().as_ref().red().bold(),
self.kind.body(),
);
match &self.source {
None => write!(f, "{label}"),
Some(source) => {
let snippet = Snippet {
title: Some(Annotation {
label: Some(&label),
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: self.location.row(),
annotations: vec![SourceAnnotation {
label: self.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// `split_once(' ')` strips "error: " from `message`.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once(' ').unwrap();
write!(f, "{message}")
}
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Source {
pub contents: String,

View File

@@ -3,6 +3,7 @@ use std::fs;
use std::path::Path;
use anyhow::Result;
use itertools::Itertools;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -13,9 +14,9 @@ static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
)
.expect("Invalid regex")
.unwrap()
});
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
#[derive(Debug)]
pub enum Directive<'a> {
@@ -52,12 +53,13 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
}
pub fn add_noqa(
path: &Path,
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
path: &Path,
external: &BTreeSet<String>,
) -> Result<usize> {
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for);
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for, external);
fs::write(path, output)?;
Ok(count)
}
@@ -66,6 +68,7 @@ fn add_noqa_inner(
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &BTreeSet<String>,
) -> (usize, String) {
let lines: Vec<&str> = contents.lines().collect();
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
@@ -112,25 +115,47 @@ fn add_noqa_inner(
output.push('\n');
count += 1;
}
Directive::All(_, start, _) | Directive::Codes(_, start, ..) => {
let mut new_line = String::new();
Directive::All(_, start, _) => {
// Add existing content.
new_line.push_str(line[..start].trim_end());
output.push_str(line[..start].trim_end());
// Add `noqa` directive.
new_line.push_str(" # noqa: ");
output.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
let codes: Vec<&str> =
codes.iter().map(AsRef::as_ref).sorted_unstable().collect();
let suffix = codes.join(", ");
new_line.push_str(&suffix);
output.push_str(&suffix);
output.push('\n');
count += 1;
}
Directive::Codes(_, start, _, existing) => {
// Reconstruct the line based on the preserved check codes.
// This enables us to tally the number of edits.
let mut formatted = String::new();
output.push_str(&new_line);
// Add existing content.
formatted.push_str(line[..start].trim_end());
// Add `noqa` directive.
formatted.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes
.iter()
.map(AsRef::as_ref)
.chain(existing.into_iter().filter(|code| external.contains(*code)))
.sorted_unstable()
.collect();
let suffix = codes.join(", ");
formatted.push_str(&suffix);
output.push_str(&formatted);
output.push('\n');
// Only count if the new line is an actual edit.
if &new_line != line {
if &formatted != line {
count += 1;
}
}
@@ -144,6 +169,7 @@ fn add_noqa_inner(
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
@@ -171,7 +197,8 @@ mod tests {
let checks = vec![];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 0);
assert_eq!(output.trim(), contents.trim());
@@ -184,7 +211,8 @@ mod tests {
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
@@ -206,7 +234,8 @@ mod tests {
];
let contents = "x = 1 # noqa: E741";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
@@ -228,7 +257,8 @@ mod tests {
];
let contents = "x = 1 # noqa";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
}

View File

@@ -1,20 +1,22 @@
use std::collections::BTreeMap;
use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use anyhow::Result;
use clap::ValueEnum;
use colored::Colorize;
use itertools::iterate;
use rustpython_parser::ast::Location;
use serde::Serialize;
use crate::checks::{CheckCode, CheckKind};
use crate::fs::relativize_path;
use crate::linter::Diagnostics;
use crate::logging::LogLevel;
use crate::message::Message;
use crate::settings::types::SerializationFormat;
use crate::tell_user;
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Debug)]
pub enum SerializationFormat {
Text,
Json,
}
#[derive(Serialize)]
struct ExpandedMessage<'a> {
kind: &'a CheckKind,
@@ -41,6 +43,28 @@ impl<'a> Printer<'a> {
}
}
fn pre_text(&self, diagnostics: &Diagnostics) {
if self.log_level >= &LogLevel::Default {
if diagnostics.fixed > 0 {
println!(
"Found {} error(s) ({} fixed).",
diagnostics.messages.len(),
diagnostics.fixed,
);
} else if !diagnostics.messages.is_empty() {
println!("Found {} error(s).", diagnostics.messages.len());
}
}
}
fn post_text(&self, num_fixable: usize) {
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.");
}
}
}
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
@@ -73,27 +97,56 @@ impl<'a> Printer<'a> {
);
}
SerializationFormat::Text => {
if self.log_level >= &LogLevel::Default {
if diagnostics.fixed > 0 {
println!(
"Found {} error(s) ({} fixed).",
diagnostics.messages.len(),
diagnostics.fixed,
);
} else if !diagnostics.messages.is_empty() {
println!("Found {} error(s).", diagnostics.messages.len());
}
}
self.pre_text(diagnostics);
for message in &diagnostics.messages {
println!("{message}");
print_message(message);
}
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.");
}
self.post_text(num_fixable);
}
SerializationFormat::Grouped => {
self.pre_text(diagnostics);
println!();
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
for (filename, messages) in grouped_messages {
// Compute the maximum number of digits in the row and column, for messages in
// this file.
let row_length = num_digits(
messages
.iter()
.map(|message| message.location.row())
.max()
.unwrap(),
);
let column_length = num_digits(
messages
.iter()
.map(|message| message.location.column())
.max()
.unwrap(),
);
// Print the filename.
println!("{}:", relativize_path(Path::new(&filename)).underline());
// Print each message.
for message in messages {
print_grouped_message(message, row_length, column_length);
}
println!();
}
self.post_text(num_fixable);
}
}
@@ -117,7 +170,7 @@ impl<'a> Printer<'a> {
println!();
}
for message in &diagnostics.messages {
println!("{message}");
print_message(message);
}
}
@@ -130,3 +183,107 @@ impl<'a> Printer<'a> {
Ok(())
}
}
fn num_digits(n: usize) -> usize {
iterate(n, |&n| n / 10)
.take_while(|&n| n > 0)
.count()
.max(1)
}
/// Print a single `Message` with full details.
fn print_message(message: &Message) {
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)).bold(),
":".cyan(),
message.location.row(),
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.code().as_ref().red().bold(),
message.kind.body(),
);
println!("{label}");
if let Some(source) = &message.source {
let snippet = Snippet {
title: Some(Annotation {
label: None,
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// Skip the first line, since we format the `label` ourselves.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap();
println!("{message}");
}
}
/// Print a grouped `Message`, assumed to be printed in a group with others from
/// the same file.
fn print_grouped_message(message: &Message, row_length: usize, column_length: usize) {
let label = format!(
" {}{}{}{}{} {} {}",
" ".repeat(row_length - num_digits(message.location.row())),
message.location.row(),
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.code().as_ref().red().bold(),
message.kind.body(),
);
println!("{label}");
if let Some(source) = &message.source {
let snippet = Snippet {
title: Some(Annotation {
label: None,
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// Skip the first line, since we format the `label` ourselves.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap();
let message = textwrap::indent(message, " ");
println!("{message}");
}
}

View File

@@ -1360,7 +1360,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
static GOOGLE_ARGS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").expect("Invalid regex"));
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").unwrap());
fn args_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
let mut args_sections: Vec<String> = vec![];

View File

@@ -1,7 +1,7 @@
use anyhow::Result;
use libcst_native::{
Codegen, CodegenState, CompOp, Comparison, ComparisonTarget, Expr, Expression, ImportNames,
NameOrAttribute, SmallStatement, Statement,
SmallStatement, Statement,
};
use rustpython_ast::Stmt;
@@ -14,7 +14,7 @@ use crate::source_code_locator::SourceCodeLocator;
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &SourceCodeLocator,
full_names: &[&str],
unused_imports: &Vec<(&String, &Range)>,
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
@@ -25,93 +25,40 @@ pub fn remove_unused_imports(
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
};
let Some(SmallStatement::Import(body)) = body.body.first_mut() 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`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if full_names.contains(&compose_module_path(&alias.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() {
helpers::remove_stmt(stmt, parent, deleted)
} else {
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
};
let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom"
));
};
let ImportNames::Aliases(aliases) = &mut body.names 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 NameOrAttribute::N(name) = &alias.name {
let import_name = name.value.to_string();
let full_name = body
.module
.as_ref()
.map(compose_module_path)
.map(|module_name| format!("{module_name}.{import_name}"))
.unwrap_or(import_name);
if full_names.contains(&full_name.as_str()) {
removable.push(index);
let (aliases, import_module) = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => Ok((&mut import_body.names, None)),
Some(SmallStatement::ImportFrom(import_body)) => {
if let ImportNames::Aliases(names) = &mut import_body.names {
Ok((names, import_body.module.as_ref()))
} else {
Err(anyhow::anyhow!("Expected node to be: Aliases"))
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
_ => Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom or SmallStatement::Import"
)),
}?;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
for (name_to_remove, _) in unused_imports {
let alias_index = aliases.iter().position(|alias| {
let full_name = match import_module {
Some(module_name) => format!(
"{}.{}",
compose_module_path(module_name),
compose_module_path(&alias.name)
),
None => compose_module_path(&alias.name),
};
&full_name.as_str() == name_to_remove
});
if let Some(index) = alias_index {
aliases.remove(index);
}
}
if let Some(alias) = aliases.last_mut() {

View File

@@ -1,4 +1,5 @@
mod checks;
pub mod fixes;
pub mod plugins;
pub mod settings;
pub mod types;

22
src/pyupgrade/settings.rs Normal file
View File

@@ -0,0 +1,22 @@
//! Settings for the `pyupgrade` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub keep_runtime_typing: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub keep_runtime_typing: bool,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
keep_runtime_typing: options.keep_runtime_typing.unwrap_or_default(),
}
}
}

View File

@@ -11,10 +11,10 @@ use regex::Regex;
use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
#[derive(Debug)]
@@ -24,8 +24,10 @@ pub struct Configuration {
pub extend_exclude: Vec<FilePattern>,
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub external: Vec<String>,
pub fix: bool,
pub fixable: Vec<CheckCodePrefix>,
pub format: SerializationFormat,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
@@ -42,6 +44,7 @@ pub struct Configuration {
pub isort: isort::settings::Settings,
pub mccabe: mccabe::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
pub pyupgrade: pyupgrade::settings::Settings,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -116,9 +119,11 @@ impl Configuration {
.select
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
extend_select: options.extend_select.unwrap_or_default(),
external: options.external.unwrap_or_default(),
fix: options.fix.unwrap_or_default(),
fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
unfixable: options.unfixable.unwrap_or_default(),
format: options.format.unwrap_or(SerializationFormat::Text),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),
per_file_ignores: options
@@ -160,6 +165,10 @@ impl Configuration {
.pep8_naming
.map(pep8_naming::settings::Settings::from_options)
.unwrap_or_default(),
pyupgrade: options
.pyupgrade
.map(pyupgrade::settings::Settings::from_options)
.unwrap_or_default(),
})
}
}

View File

@@ -15,10 +15,10 @@ use rustc_hash::FxHashSet;
use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
pub mod configuration;
@@ -32,7 +32,9 @@ pub struct Settings {
pub enabled: FxHashSet<CheckCode>,
pub exclude: GlobSet,
pub extend_exclude: GlobSet,
pub external: BTreeSet<String>,
pub fixable: FxHashSet<CheckCode>,
pub format: SerializationFormat,
pub line_length: usize,
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>,
pub show_source: bool,
@@ -46,6 +48,7 @@ pub struct Settings {
pub isort: isort::settings::Settings,
pub mccabe: mccabe::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
pub pyupgrade: pyupgrade::settings::Settings,
}
impl Settings {
@@ -69,7 +72,9 @@ impl Settings {
),
exclude: resolve_globset(config.exclude, project_root)?,
extend_exclude: resolve_globset(config.extend_exclude, project_root)?,
external: BTreeSet::from_iter(config.external),
fixable: resolve_codes(&config.fixable, &config.unfixable),
format: config.format,
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes,
@@ -78,6 +83,7 @@ impl Settings {
mccabe: config.mccabe,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
pyupgrade: config.pyupgrade,
per_file_ignores: resolve_per_file_ignores(config.per_file_ignores, project_root)?,
src: config.src,
target_version: config.target_version,
@@ -89,11 +95,14 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter([check_code.clone()]),
fixable: FxHashSet::from_iter([check_code]),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: BTreeSet::default(),
fixable: FxHashSet::from_iter([check_code]),
format: SerializationFormat::Text,
line_length: 88,
per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
@@ -103,7 +112,7 @@ impl Settings {
isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
pyupgrade: pyupgrade::settings::Settings::default(),
}
}
@@ -111,11 +120,14 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter(check_codes.clone()),
fixable: FxHashSet::from_iter(check_codes),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: BTreeSet::default(),
fixable: FxHashSet::from_iter(check_codes),
format: SerializationFormat::Text,
line_length: 88,
per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
@@ -125,7 +137,7 @@ impl Settings {
isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
pyupgrade: pyupgrade::settings::Settings::default(),
}
}
}
@@ -137,6 +149,7 @@ impl Hash for Settings {
for value in &self.enabled {
value.hash(state);
}
self.external.hash(state);
for value in &self.fixable {
value.hash(state);
}
@@ -156,6 +169,7 @@ impl Hash for Settings {
self.isort.hash(state);
self.mccabe.hash(state);
self.pep8_naming.hash(state);
self.pyupgrade.hash(state);
}
}

View File

@@ -4,10 +4,10 @@ use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::PythonVersion;
use crate::settings::types::{PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
@@ -18,8 +18,10 @@ pub struct Options {
pub extend_exclude: Option<Vec<String>>,
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub external: Option<Vec<String>>,
pub fix: Option<bool>,
pub fixable: Option<Vec<CheckCodePrefix>>,
pub format: Option<SerializationFormat>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub line_length: Option<usize>,
pub select: Option<Vec<CheckCodePrefix>>,
@@ -35,6 +37,7 @@ pub struct Options {
pub isort: Option<isort::settings::Options>,
pub mccabe: Option<mccabe::settings::Options>,
pub pep8_naming: Option<pep8_naming::settings::Options>,
pub pyupgrade: Option<pyupgrade::settings::Options>,
// Tables are required to go last.
pub per_file_ignores: Option<FxHashMap<String, Vec<CheckCodePrefix>>>,
}

View File

@@ -138,6 +138,7 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -147,6 +148,7 @@ mod tests {
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -155,6 +157,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -175,6 +178,7 @@ line-length = 79
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -184,6 +188,7 @@ line-length = 79
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -192,6 +197,7 @@ line-length = 79
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -213,9 +219,11 @@ exclude = ["foo.py"]
extend_exclude: None,
select: None,
extend_select: None,
external: None,
ignore: None,
extend_ignore: None,
fixable: None,
format: None,
unfixable: None,
per_file_ignores: None,
dummy_variable_rgx: None,
@@ -229,6 +237,7 @@ exclude = ["foo.py"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -249,6 +258,7 @@ select = ["E501"]
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -258,6 +268,7 @@ select = ["E501"]
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -266,6 +277,7 @@ select = ["E501"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -287,6 +299,7 @@ ignore = ["E501"]
extend_exclude: None,
extend_ignore: None,
extend_select: Some(vec![CheckCodePrefix::M001]),
external: None,
fix: None,
fixable: None,
ignore: Some(vec![CheckCodePrefix::E501]),
@@ -296,6 +309,7 @@ ignore = ["E501"]
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -304,6 +318,7 @@ ignore = ["E501"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -369,9 +384,11 @@ other-attribute = 1
]),
select: None,
extend_select: None,
external: Some(vec!["V101".to_string()]),
ignore: None,
extend_ignore: None,
fixable: None,
format: None,
unfixable: None,
per_file_ignores: Some(FxHashMap::from_iter([(
"__init__.py".to_string(),
@@ -422,6 +439,7 @@ other-attribute = 1
]),
staticmethod_decorators: Some(vec!["staticmethod".to_string()]),
}),
pyupgrade: None,
}
);

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{anyhow, Result};
use clap::ValueEnum;
use globset::{Glob, GlobSetBuilder};
use serde::{de, Deserialize, Deserializer, Serialize};
@@ -141,3 +142,11 @@ impl FromStr for PatternPrefixPair {
Ok(Self { pattern, prefix })
}
}
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum SerializationFormat {
Text,
Json,
Grouped,
}

View File

@@ -0,0 +1,85 @@
---
source: src/linter.rs
expression: checks
---
- kind: CommentedOutCode
location:
row: 1
column: 0
end_location:
row: 1
column: 10
fix:
patch:
content: ""
location:
row: 1
column: 0
end_location:
row: 2
column: 0
- kind: CommentedOutCode
location:
row: 2
column: 0
end_location:
row: 2
column: 22
fix:
patch:
content: ""
location:
row: 2
column: 0
end_location:
row: 3
column: 0
- kind: CommentedOutCode
location:
row: 3
column: 0
end_location:
row: 3
column: 6
fix:
patch:
content: ""
location:
row: 3
column: 0
end_location:
row: 4
column: 0
- kind: CommentedOutCode
location:
row: 5
column: 0
end_location:
row: 5
column: 13
fix:
patch:
content: ""
location:
row: 5
column: 0
end_location:
row: 6
column: 0
- kind: CommentedOutCode
location:
row: 12
column: 4
end_location:
row: 12
column: 16
fix:
patch:
content: ""
location:
row: 12
column: 0
end_location:
row: 13
column: 0

View File

@@ -4,14 +4,14 @@ expression: checks
---
- kind:
UnusedImport:
- - functools
- functools
- false
location:
row: 2
column: 0
column: 7
end_location:
row: 2
column: 20
column: 16
fix:
patch:
content: import os
@@ -23,14 +23,14 @@ expression: checks
column: 20
- kind:
UnusedImport:
- - collections.OrderedDict
- collections.OrderedDict
- false
location:
row: 4
column: 0
row: 6
column: 4
end_location:
row: 8
column: 1
row: 6
column: 15
fix:
patch:
content: "from collections import (\n Counter,\n namedtuple,\n)"
@@ -42,11 +42,11 @@ expression: checks
column: 1
- kind:
UnusedImport:
- - logging.handlers
- logging.handlers
- false
location:
row: 12
column: 0
column: 7
end_location:
row: 12
column: 23
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - shelve
- shelve
- false
location:
row: 32
column: 4
column: 11
end_location:
row: 32
column: 17
@@ -80,11 +80,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - importlib
- importlib
- false
location:
row: 33
column: 4
column: 11
end_location:
row: 33
column: 20
@@ -99,11 +99,11 @@ expression: checks
column: 20
- kind:
UnusedImport:
- - pathlib
- pathlib
- false
location:
row: 37
column: 4
column: 11
end_location:
row: 37
column: 18
@@ -118,11 +118,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - pickle
- pickle
- false
location:
row: 52
column: 8
column: 15
end_location:
row: 52
column: 21

View File

@@ -4,11 +4,11 @@ expression: checks
---
- kind:
UnusedImport:
- - a.b.c
- a.b.c
- false
location:
row: 2
column: 0
column: 16
end_location:
row: 2
column: 17
@@ -23,11 +23,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - d.e.f
- d.e.f
- false
location:
row: 3
column: 0
column: 16
end_location:
row: 3
column: 22
@@ -42,11 +42,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - h.i
- h.i
- false
location:
row: 4
column: 0
column: 7
end_location:
row: 4
column: 10
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - j.k
- j.k
- false
location:
row: 5
column: 0
column: 7
end_location:
row: 5
column: 15

View File

@@ -4,11 +4,11 @@ expression: checks
---
- kind:
UnusedImport:
- - background.BackgroundTasks
- background.BackgroundTasks
- false
location:
row: 7
column: 0
column: 24
end_location:
row: 7
column: 39
@@ -23,11 +23,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - datastructures.UploadFile
- datastructures.UploadFile
- false
location:
row: 10
column: 0
column: 28
end_location:
row: 10
column: 52
@@ -42,11 +42,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - background
- background
- false
location:
row: 17
column: 0
column: 7
end_location:
row: 17
column: 17
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - datastructures
- datastructures
- false
location:
row: 20
column: 0
column: 7
end_location:
row: 20
column: 35

View File

@@ -6,7 +6,7 @@ expression: checks
FutureFeatureNotDefined: non_existent_feature
location:
row: 2
column: 0
column: 23
end_location:
row: 2
column: 43

View File

@@ -4,30 +4,30 @@ expression: checks
---
- kind:
UnusedImport:
- - models.Nut
- models.Nut
- false
location:
row: 5
column: 0
row: 8
column: 4
end_location:
row: 8
column: 1
column: 7
fix:
patch:
content: "from models import (\n Fruit,\n)"
location:
row: 5
row: 6
column: 0
end_location:
row: 8
row: 9
column: 1
- kind:
UndefinedName: Bar
location:
row: 25
row: 26
column: 18
end_location:
row: 25
row: 26
column: 21
fix: ~

View File

@@ -0,0 +1,22 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UsePEP585Annotation: List
location:
row: 34
column: 17
end_location:
row: 34
column: 21
fix:
patch:
content: list
location:
row: 34
column: 17
end_location:
row: 34
column: 21

View File

@@ -0,0 +1,39 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UsePEP585Annotation: List
location:
row: 34
column: 17
end_location:
row: 34
column: 21
fix:
patch:
content: list
location:
row: 34
column: 17
end_location:
row: 34
column: 21
- kind:
UsePEP585Annotation: List
location:
row: 35
column: 8
end_location:
row: 35
column: 12
fix:
patch:
content: list
location:
row: 35
column: 8
end_location:
row: 35
column: 12

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: UsePEP604Annotation
location:
row: 40
column: 3
end_location:
row: 40
column: 16
fix:
patch:
content: int | None
location:
row: 40
column: 3
end_location:
row: 40
column: 16

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: UsePEP604Annotation
location:
row: 40
column: 3
end_location:
row: 40
column: 16
fix:
patch:
content: int | None
location:
row: 40
column: 3
end_location:
row: 40
column: 16

View File

@@ -75,102 +75,121 @@ expression: checks
end_location:
row: 19
column: 29
- kind:
UnusedNOQA:
- F841
- V101
location:
row: 22
column: 11
end_location:
row: 22
column: 29
fix:
patch:
content: ""
location:
row: 22
column: 9
end_location:
row: 22
column: 29
- kind:
UnusedNOQA:
- E501
location:
row: 23
row: 26
column: 9
end_location:
row: 23
row: 26
column: 21
fix:
patch:
content: ""
location:
row: 23
row: 26
column: 9
end_location:
row: 23
row: 26
column: 21
- kind:
UnusedVariable: d
location:
row: 26
row: 29
column: 4
end_location:
row: 26
row: 29
column: 5
fix: ~
- kind:
UnusedNOQA:
- E501
location:
row: 26
row: 29
column: 32
end_location:
row: 26
row: 29
column: 44
fix:
patch:
content: ""
location:
row: 26
row: 29
column: 9
end_location:
row: 26
row: 29
column: 44
- kind:
UnusedNOQA:
- F841
location:
row: 52
row: 55
column: 5
end_location:
row: 52
row: 55
column: 23
fix:
patch:
content: "# noqa: E501"
location:
row: 52
row: 55
column: 5
end_location:
row: 52
row: 55
column: 23
- kind:
UnusedNOQA:
- E501
location:
row: 60
row: 63
column: 5
end_location:
row: 60
row: 63
column: 17
fix:
patch:
content: ""
location:
row: 60
row: 63
column: 3
end_location:
row: 60
row: 63
column: 17
- kind:
UnusedNOQA: ~
location:
row: 68
row: 71
column: 5
end_location:
row: 68
row: 71
column: 11
fix:
patch:
content: ""
location:
row: 68
row: 71
column: 3
end_location:
row: 68
row: 71
column: 11

View File

@@ -18,7 +18,7 @@ fn test_stdin_error() -> Result<()> {
.write_stdin("import os\n")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:8: F401"));
Ok(())
}
@@ -30,7 +30,7 @@ fn test_stdin_filename() -> Result<()> {
.write_stdin("import os\n")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:1: F401"));
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:8: F401"));
Ok(())
}