Compare commits

...

44 Commits

Author SHA1 Message Date
Charlie Marsh
7024ad7cc7 Bump version to 0.0.158 2022-12-04 21:22:24 -05:00
Charlie Marsh
871ac511ae Add an option to force one-member-per-line for aliased import-froms (#1047) 2022-12-04 21:22:00 -05:00
Charlie Marsh
0685af8a4f Update RustPython (#1045) 2022-12-04 20:09:28 -05:00
Charlie Marsh
5e9a8fcf53 Bump version to 0.0.157 2022-12-04 14:46:46 -05:00
Charlie Marsh
76439235af Remove unused imports in __init__.py files by default (#1042) 2022-12-04 14:45:54 -05:00
Charlie Marsh
0c9c6a1c1c Remove extraneous test file 2022-12-04 13:59:55 -05:00
Charlie Marsh
46a99243cd Fix D205 autofix by detecting summary line (#1041) 2022-12-04 13:56:51 -05:00
Charlie Marsh
d06dc4c72d Bump version to 0.0.156 2022-12-04 10:22:09 -05:00
Jonathan Plasse
8f6b2fb32b Extend and rename RUF004 to PLR1722 (#1036) 2022-12-04 10:20:12 -05:00
Charlie Marsh
1d61db5b62 Allow import builtins under T100 (#1037) 2022-12-04 10:13:17 -05:00
Charlie Marsh
e15c0c68cb Fix PLW0120 snapshot 2022-12-04 10:10:00 -05:00
Harutaka Kawamura
abb2adc4d8 Implement useless-import-alias (#1025) 2022-12-04 09:48:56 -05:00
Harutaka Kawamura
e070166409 Implement useless-else-on-loop (#1031) 2022-12-04 09:22:04 -05:00
Jonathan Plasse
0ae6890094 Fix Table of Contents (#1030) 2022-12-04 09:14:09 -05:00
Harutaka Kawamura
21cace0973 Fix PLR0402 (#1024) 2022-12-04 09:13:47 -05:00
Jakub Kuczys
4994b72ba2 Fix README header links in isort config section (#1033) 2022-12-04 09:12:25 -05:00
Anders Kaseorg
dbb1a6e44b Remove sloppy match_name_or_attr helper (#1027) 2022-12-04 09:12:03 -05:00
Charlie Marsh
4b0c3e3bc9 Bump version to 0.0.155 2022-12-04 00:12:00 -05:00
Charlie Marsh
ddae3586d5 Add support for combine-as-imports import formatting (#1022) 2022-12-04 00:11:48 -05:00
Charlie Marsh
97684b7215 Mark MisplacedComparisonConstant as fixable 2022-12-04 00:11:19 -05:00
Harutaka Kawamura
20e6ff112a Implement misplaced-comparison-constant (#1023) 2022-12-04 00:10:07 -05:00
Charlie Marsh
6cb84d29f0 Rename M001 to RUF100 (#1021) 2022-12-03 22:51:05 -05:00
Charlie Marsh
d069054792 Rename RUF101 fixtures 2022-12-03 22:47:16 -05:00
Charlie Marsh
c659cb86d9 Rename RUF101 to RUF004 (#1020) 2022-12-03 22:46:13 -05:00
Charlie Marsh
8a3f29497b Add backwards compatible redirect map for U-to-UP rename (#1019) 2022-12-03 22:43:00 -05:00
Harutaka Kawamura
58e7baedd0 Implement consider-using-from-import (#1018) 2022-12-03 22:36:43 -05:00
Charlie Marsh
00eff2b09a Rename pyupgrade rules from UXXX to UPXXX (#957) 2022-12-03 22:35:42 -05:00
Charlie Marsh
b6b509811d Bump version to 0.0.154 2022-12-03 20:14:38 -05:00
Charlie Marsh
87bdda3cfa Make some flake8-return rules auto-fixable (#1017) 2022-12-03 20:13:17 -05:00
Charlie Marsh
291727a27d Implement flake8-return plugin (#1016) 2022-12-03 20:12:11 -05:00
Charlie Marsh
e66b786229 Bump version to 0.0.153 2022-12-03 17:05:39 -05:00
Harutaka Kawamura
e05e1cdf76 Implement consider-merging-isinstance (#1009) 2022-12-03 16:51:53 -05:00
Harutaka Kawamura
f92cc7a159 Fix clippy errors on main (#1010) 2022-12-03 10:02:36 -05:00
Harutaka Kawamura
ebd2181946 Implement unnecessary-direct-lambda-call (#1008) 2022-12-03 09:59:04 -05:00
Harutaka Kawamura
3efa1a03f2 Fix match_like_matches_macro in src/pylint/plugins.rs (#1007) 2022-12-03 00:21:32 -05:00
Harutaka Kawamura
115e85b47d Rename PLE0206 to PLR0206 (#1006) 2022-12-03 00:14:46 -05:00
Harutaka Kawamura
31a3314ebd Implement PLE0206 (#1005) 2022-12-03 00:04:43 -05:00
Charlie Marsh
bf33025ea9 Support whole-file noqa exclusions (#1001) 2022-12-02 23:56:56 -05:00
Charlie Marsh
1a33ee3fc4 Bump version to 0.0.152 2022-12-02 13:23:00 -05:00
Charlie Marsh
4722885910 Avoid recursing on nested deferred annotations (#1000)
Parse nested deferred annotations
2022-12-02 13:22:39 -05:00
Jonathan Plasse
117fcb6936 Add no-eval rule from pygrep-hooks (#994) 2022-12-02 12:59:06 -05:00
Charlie Marsh
1a24d78f67 Bump version to 0.0.151 2022-12-01 22:31:44 -05:00
Charlie Marsh
4a4082cf0e Track type definitions and annotations separately (#992) 2022-12-01 22:31:20 -05:00
Charlie Marsh
18b9fbd71e Improve docstring checks with empty trailing lines (#991) 2022-12-01 20:15:44 -05:00
182 changed files with 5278 additions and 756 deletions

View File

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

15
Cargo.lock generated
View File

@@ -703,7 +703,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.150-dev.0"
version = "0.0.158-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -1837,7 +1837,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.150"
version = "0.0.158"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1889,13 +1889,14 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.150"
version = "0.0.158"
dependencies = [
"anyhow",
"clap 4.0.22",
"codegen",
"itertools",
"libcst",
"once_cell",
"ruff",
"rustpython-ast",
"rustpython-common",
@@ -1925,7 +1926,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -1935,7 +1936,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -1958,7 +1959,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"bincode",
"bitflags",
@@ -1975,7 +1976,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ahash",
"anyhow",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.150"
version = "0.0.158"
edition = "2021"
rust-version = "1.65.0"
@@ -42,9 +42,9 @@ rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }

48
LICENSE
View File

@@ -363,6 +363,31 @@ are:
THE SOFTWARE.
"""
- flake8-return, licensed as follows:
"""
MIT License
Copyright (c) 2019 Afonasev Evgeniy
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.
"""
- isort, licensed as follows:
"""
The MIT License (MIT)
@@ -471,6 +496,29 @@ are:
SOFTWARE.
"""
- pygrep-hooks, licensed as follows:
"""
Copyright (c) 2018 Anthony Sottile
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.
"""
- pyupgrade, licensed as follows:
"""
Copyright (c) 2017 Anthony Sottile

360
README.md
View File

@@ -71,26 +71,28 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [Supported Rules](#supported-rules)
1. [Pyflakes (F)](#pyflakes)
1. [pycodestyle (E, W)](#pycodestyle)
1. [mccabe (C90)](#mccabe)
1. [isort (I)](#isort)
1. [pydocstyle (D)](#pydocstyle)
1. [pyupgrade (U)](#pyupgrade)
1. [pyupgrade (UP)](#pyupgrade)
1. [pep8-naming (N)](#pep8-naming)
1. [eradicate (ERA)](#eradicate)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-bandit (S)](#flake8-bandit)
1. [flake8-comprehensions (C)](#flake8-comprehensions)
1. [flake8-blind-except (BLE)](#flake8-blind-except)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap)
1. [flake8-bugbear (B)](#flake8-bugbear)
1. [flake8-builtins (A)](#flake8-builtins)
1. [flake8-debugger (T)](#flake8-debugger)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [flake8-print (T)](#flake8-print)
1. [flake8-comprehensions (C4)](#flake8-comprehensions)
1. [flake8-debugger (T10)](#flake8-debugger)
1. [flake8-print (T20)](#flake8-print)
1. [flake8-quotes (Q)](#flake8-quotes)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-blind-except (BLE)](#flake8-blind-except)
1. [mccabe (C90)](#mccabe)
1. [flake8-return (RET)](#flake8-return)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [eradicate (ERA)](#eradicate)
1. [pygrep-hooks (PGH)](#pygrep-hooks)
1. [Pylint (PL)](#pylint)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
1. [Meta rules (M)](#meta-rules)
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Development](#development)
@@ -143,7 +145,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.150
rev: v0.0.158
hooks:
- id: ruff
```
@@ -314,10 +316,10 @@ Options:
### Ignoring errors
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
either on the command-line or in your `project.toml` file.
To omit a lint check entirely, add it to the "ignore" list via [`ignore`](#ignore) or
[`extend-ignore`](#extend-ignore), either on the command-line or in your `project.toml` file.
To ignore an error in-line, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an error inline, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
```python
@@ -332,7 +334,7 @@ x = 1 # noqa
```
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
will apply to the entire body, like so:
will apply to the entire string, like so:
```python
"""Lorem ipsum dolor sit amet.
@@ -341,15 +343,24 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
""" # noqa: E501
```
To ignore all errors across an entire file, Ruff supports Flake8's `# flake8: noqa` directive (or,
equivalently, `# ruff: noqa`). Adding either of those directives to any part of a file will disable
error reporting for the entire file.
For targeted exclusions across entire files (e.g., "Ignore all F841 violations in
`/path/to/file.py`"), see the [`per-file-ignores`](#per-file-ignores) configuration setting.
### Automating `noqa` Directives
Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
First, Ruff provides a special error code, `RUF100`, to enforce that your `noqa` directives are
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
thus suppressed). You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
thus suppressed). You can run `ruff /path/to/file.py --extend-select RUF100` to flag unused `noqa`
directives.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
You can run `ruff /path/to/file.py --extend-select RUF100 --fix` to automatically remove unused
`noqa` directives.
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
@@ -439,6 +450,14 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
### mccabe
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
### isort
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
@@ -504,20 +523,20 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
| U009 | PEP3120UnnecessaryCodingComment | UTF-8 encoding declaration is unnecessary | 🛠 |
| U010 | UnnecessaryFutureImport | Unnecessary `__future__` import `...` for target Python version | 🛠 |
| U011 | UnnecessaryLRUCacheParams | Unnecessary parameters to `functools.lru_cache` | 🛠 |
| U012 | UnnecessaryEncodeUTF8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
| U013 | ConvertTypedDictFunctionalToClass | Convert `...` from `TypedDict` functional to class syntax | 🛠 |
| U014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
| U015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
| UP001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
| UP003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
| UP004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
| UP005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 |
| UP006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
| UP007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
| UP008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
| UP009 | PEP3120UnnecessaryCodingComment | UTF-8 encoding declaration is unnecessary | 🛠 |
| UP010 | UnnecessaryFutureImport | Unnecessary `__future__` import `...` for target Python version | 🛠 |
| UP011 | UnnecessaryLRUCacheParams | Unnecessary parameters to `functools.lru_cache` | 🛠 |
| UP012 | UnnecessaryEncodeUTF8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
| UP013 | ConvertTypedDictFunctionalToClass | Convert `...` from `TypedDict` functional to class syntax | 🛠 |
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
### pep8-naming
@@ -541,13 +560,40 @@ 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
### flake8-2020
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | |
| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | |
| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | |
| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | |
| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | |
| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | |
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
### flake8-annotations
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
### flake8-bandit
@@ -562,36 +608,13 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
### flake8-comprehensions
### flake8-blind-except
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
For more, see [flake8-blind-except](https://pypi.org/project/flake8-blind-except/0.2.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-debugger
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T100 | Debugger | Import for `...` found | |
| BLE001 | BlindExcept | Blind except Exception: statement | |
### flake8-boolean-trap
@@ -647,13 +670,36 @@ For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/)
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | |
### flake8-tidy-imports
### flake8-comprehensions
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-debugger
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T100 | Debugger | Import for `...` found | |
### flake8-print
@@ -675,56 +721,44 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### flake8-annotations
### flake8-return
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
For more, see [flake8-return](https://pypi.org/project/flake8-return/1.2.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
| RET501 | UnnecessaryReturnNone | Do not explicitly `return None` in function if it is the only possible return value | 🛠 |
| RET502 | ImplicitReturnValue | Do not implicitly `return None` in function able to return non-`None` value | 🛠 |
| RET503 | ImplicitReturn | Missing explicit `return` at the end of function able to return non-`None` value | 🛠 |
| RET504 | UnnecessaryAssign | Unnecessary variable assignment before `return` statement | |
| RET505 | SuperfluousElseReturn | Unnecessary `else` after `return` statement | |
| RET506 | SuperfluousElseRaise | Unnecessary `else` after `raise` statement | |
| RET507 | SuperfluousElseContinue | Unnecessary `else` after `continue` statement | |
| RET508 | SuperfluousElseBreak | Unnecessary `else` after `break` statement | |
### flake8-2020
### flake8-tidy-imports
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | |
| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | |
| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | |
| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | |
| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | |
| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | |
| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | |
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
| I252 | BannedRelativeImport | Relative imports are banned | |
### flake8-blind-except
### eradicate
For more, see [flake8-blind-except](https://pypi.org/project/flake8-blind-except/0.2.1/) on PyPI.
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| BLE001 | BlindExcept | Blind except Exception: statement | |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
### mccabe
### pygrep-hooks
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
| PGH001 | NoEval | No builtin `eval()` allowed | |
### Pylint
@@ -732,7 +766,15 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PLC0414 | UselessImportAlias | Import alias does not rename original package | 🛠 |
| PLC2201 | MisplacedComparisonConstant | Comparison should be ... | 🛠 |
| PLC3002 | UnnecessaryDirectLambdaCall | Lambda expression called directly. Execute the expression inline instead. | |
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
| PLR0402 | ConsiderUsingFromImport | Consider using `from ... import ...` | |
| PLR1701 | ConsiderMergingIsinstance | Consider merging these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 |
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
### Ruff-specific rules
@@ -741,13 +783,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
| RUF101 | ConvertExitToSysExit | `exit()` is only available in the interpreter, use `sys.exit()` instead | 🛠 |
### Meta rules
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| M001 | UnusedNOQA | Unused `noqa` directive | 🛠 |
| RUF100 | UnusedNOQA | Unused `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->
@@ -896,12 +932,14 @@ natively, including:
- [`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-return`](https://pypi.org/project/flake8-return/)
- [`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/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8:
@@ -942,12 +980,15 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`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-return`](https://pypi.org/project/flake8-return/)
- [`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),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33).
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10), and a subset of the rules
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
@@ -1229,25 +1270,6 @@ Summary
### Options
#### [`dummy_variable_rgx`](#dummy_variable_rgx)
A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating
(e.g.) unused-variable checks.
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`)
**Type**: `Regex`
**Example usage**:
```toml
[tool.ruff]
# Only ignore variables named "_".
dummy_variable_rgx = "^_$"
```
---
#### [`exclude`](#exclude)
A list of file patterns to exclude from linting.
@@ -1261,7 +1283,7 @@ Exclusions are based on globs, and can be either:
(to exclude any Python files in `directory`). Note that these paths are relative to the
project root (e.g., the directory containing your `pyproject.toml`).
Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify the excluded
Note that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded
paths.
**Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]`
@@ -1468,6 +1490,44 @@ line-length = 120
---
#### [`dummy-variable-rgx`](#dummy-variable-rgx)
A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating
(e.g.) unused-variable checks.
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`)
**Type**: `Regex`
**Example usage**:
```toml
[tool.ruff]
# Only ignore variables named "_".
dummy-variable-rgx = "^_$"
```
---
#### [`ignore-init-module-imports`](#ignore-init-module-imports)
Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be
flagged, but with a dedicated message suggesting that the import is either added to the module's
`__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff]
ignore-init-module-imports = true
```
---
#### [`format`](#format)
The style in which violation messages should be formatted: `"text"` (default), `"grouped"`
@@ -1747,7 +1807,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.
@@ -1765,7 +1825,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.
@@ -1783,7 +1843,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.
@@ -1798,6 +1858,50 @@ A list of modules to consider standard-library, in addition to those known to Ru
extra-standard-library = ["path"]
```
---
#### [`combine-as-imports`](#combine-as-imports)
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
option.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.isort]
combine-as-imports = true
```
---
#### [`force-wrap-aliases`](#force-wrap-aliases)
Force `import from` statements with multiple members and at least one alias (e.g., `import A as B`)
to wrap such that every line contains exactly one member. For example, this formatting would be
retained, rather than condensing to a single line:
```py
from .utils import (
test_directory as test_directory,
test_id as test_id
)
```
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.isort]
force-wrap-aliases = true
```
### `mccabe`
#### [`max-complexity`](#max-complexity)

11
create_plugin.sh Executable file
View File

@@ -0,0 +1,11 @@
set -euxo pipefail
NAME=$1
mkdir -p src/$1
mkdir -p resources/test/fixtures/$1
touch src/$1/mod.rs
sed -i "" "s/mod flake8_print;/mod flake8_print; mod flake8_return;/g" src/lib.rs
sed -i "" "s|// flake8-print|// flake8-return\n// flake8-print|g" src/checks.rs

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.150"
version = "0.0.158"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.150"
version = "0.0.158"
dependencies = [
"anyhow",
"bincode",
@@ -2028,7 +2028,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2038,7 +2038,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2061,7 +2061,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"bincode",
"bitflags",
@@ -2078,7 +2078,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ahash",
"anyhow",

View File

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

View File

@@ -253,6 +253,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -295,6 +296,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
@@ -337,6 +339,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
@@ -379,6 +382,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -421,6 +425,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -471,6 +476,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -548,6 +554,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![

View File

@@ -17,6 +17,7 @@ pub enum Plugin {
Flake8Eradicate,
Flake8Print,
Flake8Quotes,
Flake8Return,
Flake8TidyImports,
McCabe,
PEP8Naming,
@@ -39,6 +40,7 @@ impl FromStr for Plugin {
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-return" => Ok(Plugin::Flake8Return),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"mccabe" => Ok(Plugin::McCabe),
"pep8-naming" => Ok(Plugin::PEP8Naming),
@@ -62,6 +64,7 @@ impl Plugin {
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Return => CheckCodePrefix::RET,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PEP8Naming => CheckCodePrefix::N,
@@ -97,6 +100,7 @@ impl Plugin {
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Return => vec![CheckCodePrefix::RET],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
@@ -389,6 +393,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
Plugin::Flake8Eradicate,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Return,
Plugin::Flake8TidyImports,
Plugin::PEP8Naming,
Plugin::Pyupgrade,

View File

@@ -31,3 +31,7 @@ build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true
[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true

View File

@@ -2,6 +2,7 @@ breakpoint()
import pdb
import builtins
from builtins import breakpoint
from pdb import set_trace as st
from celery.contrib.rdb import set_trace

View File

@@ -0,0 +1,4 @@
def x(y):
if not y:
return
return None # error

View File

@@ -0,0 +1,65 @@
def x(y):
if not y:
return # error
return 1
# without return value
def x(y):
if not y:
return
return
def x(y):
if y:
return
print()
# with return value
def x(y):
if not y:
return 1
return 2
def x(y):
for i in range(10):
if i > 100:
return i
else:
return 1
def x(y):
try:
return 1
except:
return 2
def x(y):
try:
return 1
finally:
return 2
# inner function
def x(y):
if not y:
return 1
def inner():
return
return 2
def x(y):
if not y:
return
def inner():
return 1

View File

@@ -0,0 +1,116 @@
###
# Errors
###
# if/elif/else
def x(y):
if not y:
return 1
# error
def x(y):
if not y:
print() # error
else:
return 2
def x(y):
if not y:
return 1
print() # error
# for
def x(y):
for i in range(10):
if i > 10:
return i
# error
def x(y):
for i in range(10):
if i > 10:
return i
else:
print() # error
###
# Non-errors
###
# raise as last return
def x(y):
if not y:
return 1
raise Exception
# last line in while loop
def x(y):
while True:
if y > 0:
return 1
y += 1
# exclude empty functions
def x(y):
return None
# return inner with statement
def x(y):
with y:
return 1
async def function():
async with thing as foo:
return foo.bar
# assert as last return
def x(y):
if not y:
return 1
assert False, "Sanity check"
# return value within loop
def bar1(x, y, z):
for i in x:
if i > y:
break
return z
def bar3(x, y, z):
for i in x:
if i > y:
if z:
break
else:
return z
return None
def bar1(x, y, z):
for i in x:
if i < y:
continue
return z
def bar3(x, y, z):
for i in x:
if i < y:
if z:
continue
else:
return z
return None

View File

@@ -0,0 +1,232 @@
###
# Errors
###
def x():
a = 1
return a # error
def x():
b, a = 1, 2
print(b)
return a # error
def x():
a = 1
print()
return a # error
def x():
a = 1
print(a)
a = 2
return a # error
def x():
a = 1
if True:
return a # error
a = 2
print(a)
return a
# Can be refactored false positives
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
result = ""
if hasattr(obj, "bar"):
result = str(obj.bar)
return result
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
formatted = formatted.replace("()", "").replace(" ", " ").strip()
return formatted
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test, but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a hand-coded
# percent-encoded value.
if "%" in username:
username = quote(username)
return username
###
# Non-errors
###
def x(y):
a = 1
print(a)
return a
def x():
a = 1
if y:
return a
a = a + 2
print(a)
return a
def x():
a = {}
a["b"] = 2
return a
def x():
a = []
a.append(2)
return a
def x():
a = lambda x: x
a()
return a
# ignore unpacking
def x():
b, a = [1, 2]
return a
def x():
val = ""
for i in range(5):
val = val + str(i)
return val
def x():
val = ""
i = 5
while i:
val = val + str(i)
i = i - x
return val
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:
local_match = self.local_scope_re.match(url)
if local_match:
schema = get_schema(name=local_match.group(1))
self.store[url] = schema
return schema
raise NotImplementedError(...)
my_dict = {}
def my_func():
foo = calculate_foo()
my_dict["foo_result"] = foo
return foo
# Refactored incorrect false positives
# See test cases above marked: "Can be refactored false positives"
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
if hasattr(obj, "bar"):
return str(obj.bar)
return ""
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
return formatted.replace("()", "").replace(" ", " ").strip()
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test,
# but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a
# hand-coded percent-encoded value.
if "%" in username:
username = quote(username)
return username
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception_loop():
success = False
for _ in range(10):
try:
success = True
except Exception:
print("exception")
return success
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception():
success = False
try:
success = True
except Exception:
print("exception")
return success
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def exception():
success = True
try:
print("raising")
raise Exception
except Exception:
success = False
return success
# https://github.com/afonasev/flake8-return/issues/66
def close(self):
any_failed = False
for task in self.tasks:
try:
task()
except BaseException:
any_failed = True
report(traceback.format_exc())
return any_failed

View File

@@ -0,0 +1,141 @@
###
# Error
###
def foo2(x, y, w, z):
if x: # [no-else-return]
a = 1
return y
elif z:
b = 2
return w
else:
c = 3
return z
def foo5(x, y, z):
if x: # [no-else-return]
if y:
a = 4
else:
b = 2
return
elif z:
c = 2
else:
c = 3
return
def foo2(x, y, w, z):
if x:
a = 1
elif z:
b = 2
else:
c = 3
if x: # [no-else-return]
a = 1
return y
elif z:
b = 2
return w
else:
c = 3
return z
def foo1(x, y, z):
if x: # [no-else-return]
a = 1
return y
else:
b = 2
return z
def foo3(x, y, z):
if x:
a = 1
if y: # [no-else-return]
b = 2
return y
else:
c = 3
return x
else:
d = 4
return z
def foo4(x, y):
if x: # [no-else-return]
if y:
a = 4
else:
b = 2
return
else:
c = 3
return
def foo6(x, y):
if x:
if y: # [no-else-return]
a = 4
return
else:
b = 2
else:
c = 3
return
def bar4(x):
if x: # [no-else-return]
return True
else:
try:
return False
except ValueError:
return None
###
# Non-error
###
def bar1(x, y, z):
if x:
return y
return z
def bar2(w, x, y, z):
if x:
return y
if z:
a = 1
else:
return w
return None
def bar3(x, y, z):
if x:
if z:
return y
else:
return z
return None
x = 0
if x == 1:
y = "a"
elif x == 2:
y = "b"
else:
y = "c"

View File

@@ -0,0 +1,112 @@
###
# Errors
###
def foo2(x, y, w, z):
if x: # [no-else-raise]
a = 1
raise Exception(y)
elif z:
b = 2
raise Exception(w)
else:
c = 3
raise Exception(z)
def foo5(x, y, z):
if x: # [no-else-raise]
if y:
a = 4
else:
b = 2
raise Exception(x)
elif z:
raise Exception(y)
else:
c = 3
raise Exception(z)
def foo1(x, y, z):
if x: # [no-else-raise]
a = 1
raise Exception(y)
else:
b = 2
raise Exception(z)
def foo3(x, y, z):
if x:
a = 1
if y: # [no-else-raise]
b = 2
raise Exception(y)
else:
c = 3
raise Exception(x)
else:
d = 4
raise Exception(z)
def foo4(x, y):
if x: # [no-else-raise]
if y:
a = 4
else:
b = 2
raise Exception(x)
else:
c = 3
raise Exception(y)
def foo6(x, y):
if x:
if y: # [no-else-raise]
a = 4
raise Exception(x)
else:
b = 2
else:
c = 3
raise Exception(y)
def bar4(x):
if x: # [no-else-raise]
raise Exception(True)
else:
try:
raise Exception(False)
except ValueError:
raise Exception(None)
###
# Non-errors
###
def bar1(x, y, z):
if x:
raise Exception(y)
raise Exception(z)
def bar2(w, x, y, z):
if x:
raise Exception(y)
if z:
a = 1
else:
raise Exception(w)
raise Exception(None)
def bar3(x, y, z):
if x:
if z:
raise Exception(y)
else:
raise Exception(z)
raise Exception(None)

View File

@@ -0,0 +1,106 @@
###
# Errors
###
def foo2(x, y, w, z):
for i in x:
if i < y: # [no-else-continue]
continue
elif i < w:
continue
else:
a = z
def foo5(x, y, z):
for i in x:
if i < y: # [no-else-continue]
if y:
a = 4
else:
b = 2
continue
elif z:
c = 2
else:
c = 3
continue
###
# Non-errors
###
def foo1(x, y, z):
for i in x:
if i < y: # [no-else-continue]
continue
else:
a = z
def foo3(x, y, z):
for i in x:
if i < y:
a = 1
if z: # [no-else-continue]
b = 2
continue
else:
c = 3
continue
else:
d = 4
continue
def foo4(x, y):
for i in x:
if i < x: # [no-else-continue]
if y:
a = 4
else:
b = 2
continue
else:
c = 3
continue
def foo6(x, y):
for i in x:
if i < x:
if y: # [no-else-continue]
a = 4
continue
else:
b = 2
else:
c = 3
continue
def bar4(x):
for i in range(10):
if x: # [no-else-continue]
continue
else:
try:
return
except ValueError:
continue
def bar1(x, y, z):
for i in x:
if i < y:
continue
return z
def bar3(x, y, z):
for i in x:
if i < y:
if z:
continue
else:
return z
return None

View File

@@ -0,0 +1,138 @@
###
# Errors
###
def foo2(x, y, w, z):
for i in x:
if i > y: # [no-else-break]
break
elif i > w:
break
else:
a = z
def foo5(x, y, z):
for i in x:
if i > y: # [no-else-break]
if y:
a = 4
else:
b = 2
break
elif z:
c = 2
else:
c = 3
break
def foo1(x, y, z):
for i in x:
if i > y: # [no-else-break]
break
else:
a = z
def foo3(x, y, z):
for i in x:
if i > y:
a = 1
if z: # [no-else-break]
b = 2
break
else:
c = 3
break
else:
d = 4
break
def foo4(x, y):
for i in x:
if i > x: # [no-else-break]
if y:
a = 4
else:
b = 2
break
else:
c = 3
break
def foo6(x, y):
for i in x:
if i > x:
if y: # [no-else-break]
a = 4
break
else:
b = 2
else:
c = 3
break
def bar4(x):
for i in range(10):
if x: # [no-else-break]
break
else:
try:
return
except ValueError:
break
###
# Non-errors
###
def bar1(x, y, z):
for i in x:
if i > y:
break
return z
def bar2(w, x, y, z):
for i in w:
if i > x:
break
if z < i:
a = y
else:
break
return
def bar3(x, y, z):
for i in x:
if i > y:
if z:
break
else:
return z
return None
def nested1(x, y, z):
for i in x:
if i > x:
for j in y:
if j < z:
break
else:
a = z
def nested2(x, y, z):
for i in x:
if i > x:
for j in y:
if j > z:
break
break
else:
a = z

View File

@@ -0,0 +1,4 @@
from module import Class as C
from module import CONSTANT
from module import function
from module import function as f

View File

@@ -0,0 +1,3 @@
from .a import a1 as a1, a2 as a2
from .b import b1 as b1
from .c import c1

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from typing import Callable, List, Tuple, Optional, Sequence
from models import (
Fruit,
@@ -37,4 +37,12 @@ def f(x: int) -> List[int]:
return y
x: Optional[int] = None
x: Tuple[int, ...] = (1, 2)
def f(param: "Optional[Callable]" = None) -> "None":
pass
def f(param: Optional["Sequence"] = None) -> "None":
pass

View File

@@ -0,0 +1,9 @@
from ast import literal_eval
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View File

@@ -0,0 +1,11 @@
def eval(content: str) -> None:
pass
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View File

@@ -0,0 +1,37 @@
"""Checks use of consider-merging-isinstance"""
# pylint:disable=line-too-long, simplifiable-condition
def isinstances():
"Examples of isinstances"
var = range(10)
# merged
if isinstance(var[1], (int, float)):
pass
result = isinstance(var[2], (int, float))
# not merged
if isinstance(var[3], int) or isinstance(var[3], float) or isinstance(var[3], list) and True: # [consider-merging-isinstance]
pass
result = isinstance(var[4], int) or isinstance(var[4], float) or isinstance(var[5], list) and False # [consider-merging-isinstance]
result = isinstance(var[5], int) or True or isinstance(var[5], float) # [consider-merging-isinstance]
infered_isinstance = isinstance
result = infered_isinstance(var[6], int) or infered_isinstance(var[6], float) or infered_isinstance(var[6], list) and False # [consider-merging-isinstance]
result = isinstance(var[10], str) or isinstance(var[10], int) and var[8] * 14 or isinstance(var[10], float) and var[5] * 14.4 or isinstance(var[10], list) # [consider-merging-isinstance]
result = isinstance(var[11], int) or isinstance(var[11], int) or isinstance(var[11], float) # [consider-merging-isinstance]
result = isinstance(var[20])
result = isinstance()
# Combination merged and not merged
result = isinstance(var[12], (int, float)) or isinstance(var[12], list) # [consider-merging-isinstance]
# not merged but valid
result = isinstance(var[5], int) and var[5] * 14 or isinstance(var[5], float) and var[5] * 14.4
result = isinstance(var[7], int) or not isinstance(var[7], float)
result = isinstance(var[6], int) or isinstance(var[7], float)
result = isinstance(var[6], int) or isinstance(var[7], int)
return result

View File

@@ -1,5 +1,7 @@
exit(0)
quit(0)
def main():
exit(2)
quit(2)

View File

@@ -1,10 +1,12 @@
import sys
exit(0)
quit(0)
def main():
exit(1)
quit(1)
sys.exit(2)

View File

@@ -1,7 +1,9 @@
import sys as sys2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import exit
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import exit as exit2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import *
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,12 +1,19 @@
exit(0)
quit(0)
def exit(e):
pass
def quit(e):
pass
exit(1)
quit(1)
def main():
exit(2)
quit(2)

View File

@@ -0,0 +1,27 @@
# pylint: disable=unused-import, missing-docstring, invalid-name, reimported, import-error, wrong-import-order, no-name-in-module, shadowed-import
# Functional tests for import aliasing
# 1. useless-import-alias
# 2. consider-using-from-import
import collections as collections # [useless-import-alias]
from collections import OrderedDict as OrderedDict # [useless-import-alias]
from collections import OrderedDict as o_dict
import os.path as path # [consider-using-from-import]
import os.path as p
import foo.bar.foobar as foobar # [consider-using-from-import]
import os
import os as OS
from sys import version
from . import bar as bar # [useless-import-alias]
from . import bar as Bar
from . import bar
from ..foo import bar as bar # [useless-import-alias]
from ..foo.bar import foobar as foobar # [useless-import-alias]
from ..foo.bar import foobar as anotherfoobar
from . import foo as foo, foo2 as bar2 # [useless-import-alias]
from . import foo as bar, foo2 as foo2 # [useless-import-alias]
from . import foo as bar, foo2 as bar2
from foo.bar import foobar as foobar # [useless-import-alias]
from foo.bar import foobar as foo
from .foo.bar import f as foobar
from ............a import b # [relative-beyond-top-level]

View File

@@ -0,0 +1,50 @@
"""Check that the constants are on the right side of the comparisons"""
# pylint: disable=singleton-comparison, missing-docstring, too-few-public-methods
# pylint: disable=comparison-of-constants
class MyClass:
def __init__(self):
self.attr = 1
def dummy_return(self):
return self.attr
def dummy_return():
return 2
def bad_comparisons():
"""this is not ok"""
instance = MyClass()
for i in range(10):
if 5 <= i: # [misplaced-comparison-constant]
pass
if 1 == i: # [misplaced-comparison-constant]
pass
if 3 < dummy_return(): # [misplaced-comparison-constant]
pass
if 4 != instance.dummy_return(): # [misplaced-comparison-constant]
pass
if 1 == instance.attr: # [misplaced-comparison-constant]
pass
if "aaa" == instance.attr: # [misplaced-comparison-constant]
pass
def good_comparison():
"""this is ok"""
for i in range(10):
if i == 5:
pass
def double_comparison():
"""Check that we return early for non-binary comparison"""
for i in range(10):
if i == 1 == 2:
pass
if 2 <= i <= 8:
print("Between 2 and 8 inclusive")
def const_comparison():
"""Check that we return early for comparison of two constants"""
if 1 == 2:
pass

View File

@@ -0,0 +1,30 @@
# pylint: disable=missing-docstring, too-few-public-methods
from abc import ABCMeta, abstractmethod
class Cls:
@property
def attribute(self, param, param1): # [property-with-parameters]
return param + param1
@property
def attribute_keyword_only(self, *, param, param1): # [property-with-parameters]
return param + param1
@property
def attribute_positional_only(self, param, param1, /): # [property-with-parameters]
return param + param1
class MyClassBase(metaclass=ABCMeta):
"""MyClassBase."""
@property
@abstractmethod
def example(self):
"""Getter."""
@example.setter
@abstractmethod
def example(self, value):
"""Setter."""

View File

@@ -0,0 +1,5 @@
"""Test unnecessary direct calls to lambda expressions."""
# pylint: disable=undefined-variable, line-too-long
y = (lambda x: x**2 + 2*x + 1)(a) # [unnecessary-direct-lambda-call]
y = max((lambda x: x**2)(a), (lambda x: x+1)(a)) # [unnecessary-direct-lambda-call,unnecessary-direct-lambda-call]

View File

@@ -0,0 +1,103 @@
"""Check for else branches on loops with break and return only."""
def test_return_for():
"""else + return is not acceptable."""
for i in range(10):
if i % 2:
return i
else: # [useless-else-on-loop]
print("math is broken")
return None
def test_return_while():
"""else + return is not acceptable."""
while True:
return 1
else: # [useless-else-on-loop]
print("math is broken")
return None
while True:
def short_fun():
"""A function with a loop."""
for _ in range(10):
break
else: # [useless-else-on-loop]
print("or else!")
while True:
while False:
break
else: # [useless-else-on-loop]
print("or else!")
for j in range(10):
pass
else: # [useless-else-on-loop]
print("fat chance")
for j in range(10):
break
def test_return_for2():
"""no false positive for break in else
https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives
"""
for i in range(10):
for _ in range(i):
if i % 2:
break
else:
break
else:
print("great math")
def test_break_in_orelse_deep():
"""no false positive for break in else deeply nested"""
for _ in range(10):
if 1 < 2: # pylint: disable=comparison-of-constants
for _ in range(3):
if 3 < 2: # pylint: disable=comparison-of-constants
break
else:
break
else:
return True
return False
def test_break_in_orelse_deep2():
"""should rise a useless-else-on-loop message, as the break statement is only
for the inner for loop
"""
for _ in range(10):
if 1 < 2: # pylint: disable=comparison-of-constants
for _ in range(3):
if 3 < 2: # pylint: disable=comparison-of-constants
break
else:
print("all right")
else: # [useless-else-on-loop]
return True
return False
def test_break_in_orelse_deep3():
"""no false positive for break deeply nested in else"""
for _ in range(10):
for _ in range(3):
pass
else:
if 1 < 2: # pylint: disable=comparison-of-constants
break
else:
return True
return False

View File

@@ -1,3 +0,0 @@
# coding=utf8 # noqa: U009
print("Hello world")

View File

@@ -0,0 +1,3 @@
# coding=utf8 # noqa: UP009
print("Hello world")

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from typing import List, Optional, TypeAlias, Union
from models import (
Fruit,
@@ -38,3 +38,5 @@ def f(x: int) -> List[int]:
x: Optional[int] = None
MyList: TypeAlias = Union[List[int], List[str]]

View File

@@ -0,0 +1,7 @@
# flake8: noqa
import os
def f():
x = 1

View File

@@ -0,0 +1,9 @@
from typing import Optional
def f(x: Optional[str]) -> None: # noqa: U007
...
def f(x: Optional[str]) -> None: # noqa: UP007
...

View File

@@ -0,0 +1,7 @@
# ruff: noqa
import os
def f():
x = 1

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.150"
version = "0.0.158"
edition = "2021"
[dependencies]
@@ -9,9 +9,10 @@ clap = { version = "4.0.1", features = ["derive"] }
codegen = { version = "0.2.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }

View File

@@ -8,7 +8,7 @@ use anyhow::Result;
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::CheckCode;
use ruff::checks::{CheckCode, REDIRECTS};
use strum::IntoEnumIterator;
const FILE: &str = "src/checks_gen.rs";
@@ -26,18 +26,49 @@ pub fn main(cli: &Cli) -> Result<()> {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = BTreeMap::default();
for check_code in CheckCode::iter() {
let as_ref: String = check_code.as_ref().to_string();
let prefix_len = as_ref
let code_str: String = check_code.as_ref().to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
.count();
for i in prefix_len..=as_ref.len() {
let prefix = as_ref[..i].to_string();
let code_suffix_len = code_str.len() - code_prefix_len;
for i in 0..=code_suffix_len {
let prefix = code_str[..code_prefix_len + i].to_string();
let entry = prefix_to_codes.entry(prefix).or_default();
entry.insert(check_code.clone());
}
}
// Add any aliases (e.g., "U001" to "UP001").
for (alias, check_code) in REDIRECTS.iter() {
// Compute the length of the prefix and suffix for both codes.
let code_str: String = check_code.as_ref().to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let code_suffix_len = code_str.len() - code_prefix_len;
let alias_prefix_len = alias
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let alias_suffix_len = alias.len() - alias_prefix_len;
assert_eq!(code_suffix_len, alias_suffix_len);
for i in 0..=code_suffix_len {
let source = code_str[..code_prefix_len + i].to_string();
let destination = alias[..alias_prefix_len + i].to_string();
if source != destination {
prefix_to_codes.insert(
destination,
prefix_to_codes
.get(&source)
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
}
}
let mut scope = Scope::new();
// Create the `CheckCodePrefix` definition.
@@ -81,13 +112,27 @@ pub fn main(cli: &Cli) -> Result<()> {
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
if let Some(target) = REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been renamed to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target.as_ref(),
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
}
}
gen.line("}");
@@ -123,6 +168,8 @@ pub fn main(cli: &Cli) -> Result<()> {
.push_str("//! File automatically generated by `examples/generate_check_code_prefix.rs`.");
output.push('\n');
output.push('\n');
output.push_str("use colored::Colorize;");
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
output.push('\n');
output.push_str("use strum_macros::EnumString;");

View File

@@ -28,11 +28,12 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push('\n');
if let Some(url) = check_category.url() {
if let Some((url, platform)) = check_category.url() {
output.push_str(&format!(
"For more, see [{}]({}) on PyPI.",
"For more, see [{}]({}) on {}.",
check_category.title(),
url
url,
platform
));
output.push('\n');
output.push('\n');

View File

@@ -61,15 +61,6 @@ pub fn dealias_call_path<'a>(
}
}
/// Return `true` if the `Expr` is a name or attribute reference to `${target}`.
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => target == attr,
ExprKind::Name { id, .. } => target == id,
_ => false,
}
}
/// Return `true` if the `Expr` is a reference to `${module}.${target}`.
///
/// Useful for, e.g., ensuring that a `Union` reference represents

View File

@@ -4,8 +4,6 @@ use rustpython_parser::ast::{
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
};
use crate::ast::helpers::match_name_or_attr;
pub trait Visitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
walk_stmt(self, stmt);
@@ -150,11 +148,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
} => {
visitor.visit_annotation(annotation);
if let Some(expr) = value {
if match_name_or_attr(annotation, "TypeAlias") {
visitor.visit_annotation(expr);
} else {
visitor.visit_expr(expr);
}
visitor.visit_expr(expr);
}
visitor.visit_expr(target);
}

View File

@@ -1,3 +1,5 @@
use std::str::Lines;
use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
@@ -37,3 +39,38 @@ pub fn clean(indentation: &str) -> String {
.map(|char| if char.is_whitespace() { char } else { ' ' })
.collect()
}
/// Like `str#lines`, but includes a trailing newline as an empty line.
pub struct LinesWithTrailingNewline<'a> {
trailing: Option<&'a str>,
underlying: Lines<'a>,
}
impl<'a> LinesWithTrailingNewline<'a> {
pub fn from(input: &'a str) -> LinesWithTrailingNewline<'a> {
LinesWithTrailingNewline {
underlying: input.lines(),
trailing: if input.ends_with('\n') {
Some("")
} else {
None
},
}
}
}
impl<'a> Iterator for LinesWithTrailingNewline<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<&'a str> {
let mut next = self.underlying.next();
if next.is_none() {
if self.trailing.is_some() {
next = self.trailing;
self.trailing = None;
}
}
next
}
}

View File

@@ -36,12 +36,14 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pylint, pyupgrade, rules,
flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pygrep_hooks, pylint, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
type DeferralContext = (Vec<usize>, Vec<usize>);
#[allow(clippy::struct_excessive_bools)]
pub struct Checker<'a> {
// Input data.
@@ -66,17 +68,18 @@ pub struct Checker<'a> {
scopes: Vec<Scope<'a>>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
deferred_string_annotations: Vec<(Range, &'a str, Vec<usize>, Vec<usize>)>,
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>, VisibleScope)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_string_type_definitions: Vec<(Range, &'a str, bool, DeferralContext)>,
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext)>,
deferred_functions: Vec<(&'a Stmt, DeferralContext, VisibleScope)>,
deferred_lambdas: Vec<(&'a Expr, DeferralContext)>,
deferred_assignments: Vec<usize>,
// Internal, derivative state.
visible_scope: VisibleScope,
in_f_string: Option<Range>,
in_annotation: bool,
in_deferred_string_annotation: bool,
in_deferred_annotation: bool,
in_type_definition: bool,
in_deferred_string_type_definition: bool,
in_deferred_type_definition: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
@@ -110,8 +113,8 @@ impl<'a> Checker<'a> {
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![],
deferred_string_type_definitions: vec![],
deferred_type_definitions: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
deferred_assignments: vec![],
@@ -122,8 +125,9 @@ impl<'a> Checker<'a> {
},
in_f_string: None,
in_annotation: false,
in_deferred_string_annotation: false,
in_deferred_annotation: false,
in_type_definition: false,
in_deferred_string_type_definition: false,
in_deferred_type_definition: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
@@ -408,7 +412,7 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::U011)
if self.settings.enabled.contains(&CheckCode::UP011)
&& self.settings.target_version >= PythonVersion::Py38
{
pyupgrade::plugins::unnecessary_lru_cache_params(self, decorator_list);
@@ -417,9 +421,23 @@ where
if self.settings.enabled.contains(&CheckCode::B018) {
flake8_bugbear::plugins::useless_expression(self, body);
}
if self.settings.enabled.contains(&CheckCode::B019) {
flake8_bugbear::plugins::cached_instance_method(self, decorator_list);
}
if self.settings.enabled.contains(&CheckCode::RET501)
|| self.settings.enabled.contains(&CheckCode::RET502)
|| self.settings.enabled.contains(&CheckCode::RET503)
|| self.settings.enabled.contains(&CheckCode::RET504)
|| self.settings.enabled.contains(&CheckCode::RET505)
|| self.settings.enabled.contains(&CheckCode::RET506)
|| self.settings.enabled.contains(&CheckCode::RET507)
|| self.settings.enabled.contains(&CheckCode::RET508)
{
flake8_return::plugins::function(self, body);
}
if self.settings.enabled.contains(&CheckCode::C901) {
if let Some(check) = mccabe::checks::function_is_too_complex(
stmt,
@@ -437,6 +455,10 @@ where
);
}
if self.settings.enabled.contains(&CheckCode::PLR0206) {
pylint::plugins::property_with_parameters(self, stmt, decorator_list, args);
}
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
// Visit the decorators and arguments, but avoid the body, which will be
@@ -509,7 +531,7 @@ where
decorator_list,
body,
} => {
if self.settings.enabled.contains(&CheckCode::U004) {
if self.settings.enabled.contains(&CheckCode::UP004) {
pyupgrade::plugins::useless_object_inheritance(
self, stmt, name, bases, keywords,
);
@@ -646,6 +668,14 @@ where
}
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC0414) {
pylint::plugins::useless_import_alias(self, alias);
}
if self.settings.enabled.contains(&CheckCode::PLR0402) {
pylint::plugins::consider_using_from_import(self, alias);
}
if let Some(asname) = &alias.node.asname {
for alias in names {
if let Some(asname) = &alias.node.asname {
@@ -733,7 +763,7 @@ where
}
if let Some("__future__") = module.as_deref() {
if self.settings.enabled.contains(&CheckCode::U010) {
if self.settings.enabled.contains(&CheckCode::UP010) {
pyupgrade::plugins::unnecessary_future_import(self, stmt, names);
}
}
@@ -938,6 +968,11 @@ where
self.add_check(check);
}
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC0414) {
pylint::plugins::useless_import_alias(self, alias);
}
}
}
}
@@ -982,16 +1017,27 @@ where
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
}
}
StmtKind::While { .. } => {
StmtKind::While { body, orelse, .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
if self.settings.enabled.contains(&CheckCode::PLW0120) {
pylint::plugins::useless_else_on_loop(self, stmt, body, orelse);
}
}
StmtKind::For {
target, body, iter, ..
target,
body,
iter,
orelse,
..
}
| StmtKind::AsyncFor {
target, body, iter, ..
target,
body,
iter,
orelse,
..
} => {
if self.settings.enabled.contains(&CheckCode::B007) {
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
@@ -1002,6 +1048,9 @@ where
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
if self.settings.enabled.contains(&CheckCode::PLW0120) {
pylint::plugins::useless_else_on_loop(self, stmt, body, orelse);
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
@@ -1027,7 +1076,7 @@ where
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt);
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
if self.settings.enabled.contains(&CheckCode::UP001) {
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
}
if self.settings.enabled.contains(&CheckCode::B003) {
@@ -1040,12 +1089,12 @@ where
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::U013) {
if self.settings.enabled.contains(&CheckCode::UP013) {
pyupgrade::plugins::convert_typed_dict_functional_to_class(
self, stmt, targets, value,
);
}
if self.settings.enabled.contains(&CheckCode::U014) {
if self.settings.enabled.contains(&CheckCode::UP014) {
pyupgrade::plugins::convert_named_tuple_functional_to_class(
self, stmt, targets, value,
);
@@ -1087,8 +1136,7 @@ where
self.deferred_functions.push((
stmt,
self.scope_stack.clone(),
self.parent_stack.clone(),
(self.scope_stack.clone(), self.parent_stack.clone()),
self.visible_scope.clone(),
));
}
@@ -1135,6 +1183,24 @@ where
self.visit_stmt(stmt);
}
}
StmtKind::AnnAssign {
target,
annotation,
value,
..
} => {
self.visit_annotation(annotation);
if let Some(expr) = value {
if self.match_typing_expr(annotation, "TypeAlias") {
self.in_type_definition = true;
self.visit_expr(expr);
self.in_type_definition = false;
} else {
self.visit_expr(expr);
}
}
self.visit_expr(target);
}
_ => visitor::walk_stmt(self, stmt),
};
self.visible_scope = prev_visible_scope;
@@ -1157,33 +1223,39 @@ where
fn visit_annotation(&mut self, expr: &'b Expr) {
let prev_in_annotation = self.in_annotation;
let prev_in_type_definition = self.in_type_definition;
self.in_annotation = true;
self.in_type_definition = true;
self.visit_expr(expr);
self.in_annotation = prev_in_annotation;
self.in_type_definition = prev_in_type_definition;
}
fn visit_expr(&mut self, expr: &'b Expr) {
let prev_in_f_string = self.in_f_string;
let prev_in_literal = self.in_literal;
let prev_in_annotation = self.in_annotation;
let prev_in_type_definition = self.in_type_definition;
if self.in_annotation && self.annotations_future_enabled {
if !(self.in_deferred_type_definition || self.in_deferred_string_type_definition)
&& self.in_type_definition
&& self.annotations_future_enabled
{
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &expr.node
{
self.deferred_string_annotations.push((
self.deferred_string_type_definitions.push((
Range::from_located(expr),
value,
self.scope_stack.clone(),
self.parent_stack.clone(),
self.in_annotation,
(self.scope_stack.clone(), self.parent_stack.clone()),
));
} else {
self.deferred_annotations.push((
self.deferred_type_definitions.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
self.in_annotation,
(self.scope_stack.clone(), self.parent_stack.clone()),
));
}
return;
@@ -1193,13 +1265,13 @@ where
match &expr.node {
ExprKind::Subscript { value, slice, .. } => {
// Ex) Optional[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U007)
if !self.in_deferred_string_type_definition
&& self.settings.enabled.contains(&CheckCode::UP007)
&& (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))
&& self.in_annotation))
{
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
}
@@ -1236,13 +1308,13 @@ where
match ctx {
ExprContext::Load => {
// Ex) List[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
if !self.in_deferred_string_type_definition
&& self.settings.enabled.contains(&CheckCode::UP006)
&& (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))
&& self.in_annotation))
&& typing::is_pep585_builtin(
expr,
&self.from_imports,
@@ -1277,12 +1349,12 @@ where
}
ExprKind::Attribute { attr, .. } => {
// Ex) typing.List[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
if !self.in_deferred_string_type_definition
&& self.settings.enabled.contains(&CheckCode::UP006)
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& self.in_annotation))
&& typing::is_pep585_builtin(expr, &self.from_imports, &self.import_aliases)
{
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
@@ -1372,16 +1444,16 @@ where
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U005) {
if self.settings.enabled.contains(&CheckCode::UP005) {
pyupgrade::plugins::deprecated_unittest_alias(self, func);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::U008) {
if self.settings.enabled.contains(&CheckCode::UP008) {
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U012) {
if self.settings.enabled.contains(&CheckCode::UP012) {
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
}
@@ -1640,11 +1712,11 @@ where
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U003) {
if self.settings.enabled.contains(&CheckCode::UP003) {
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U015) {
if self.settings.enabled.contains(&CheckCode::UP015) {
pyupgrade::plugins::redundant_open_modes(self, expr);
}
@@ -1676,9 +1748,17 @@ where
}
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF101) {
rules::plugins::convert_exit_to_sys_exit(self, func);
// pygrep-hooks
if self.settings.enabled.contains(&CheckCode::PGH001) {
pygrep_hooks::checks::no_eval(self, func);
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC3002) {
pylint::plugins::unnecessary_direct_lambda_call(self, expr, func);
}
if self.settings.enabled.contains(&CheckCode::PLR1722) {
pylint::plugins::consider_using_sys_exit(self, func);
}
}
ExprKind::Dict { keys, .. } => {
@@ -1941,17 +2021,27 @@ where
.into_iter(),
);
}
if self.settings.enabled.contains(&CheckCode::PLC2201) {
pylint::plugins::misplaced_comparison_constant(
self,
expr,
left,
ops,
comparators,
);
}
}
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
if self.in_annotation && !self.in_literal {
self.deferred_string_annotations.push((
if self.in_type_definition && !self.in_literal {
self.deferred_string_type_definitions.push((
Range::from_located(expr),
value,
self.scope_stack.clone(),
self.parent_stack.clone(),
self.in_annotation,
(self.scope_stack.clone(), self.parent_stack.clone()),
));
}
if self.settings.enabled.contains(&CheckCode::S104) {
@@ -2022,17 +2112,19 @@ where
}
self.push_scope(Scope::new(ScopeKind::Generator));
}
ExprKind::BoolOp { op, values } => {
if self.settings.enabled.contains(&CheckCode::PLR1701) {
pylint::plugins::consider_merging_isinstance(self, expr, op, values);
}
}
_ => {}
};
// Recurse.
match &expr.node {
ExprKind::Lambda { .. } => {
self.deferred_lambdas.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
self.deferred_lambdas
.push((expr, (self.scope_stack.clone(), self.parent_stack.clone())));
}
ExprKind::Call {
func,
@@ -2043,12 +2135,16 @@ where
if self.match_typing_call_path(&call_path, "ForwardRef") {
self.visit_expr(func);
for expr in args {
self.visit_annotation(expr);
self.in_type_definition = true;
self.visit_expr(expr);
self.in_type_definition = prev_in_type_definition;
}
} else if self.match_typing_call_path(&call_path, "cast") {
self.visit_expr(func);
if !args.is_empty() {
self.visit_annotation(&args[0]);
self.in_type_definition = true;
self.visit_expr(&args[0]);
self.in_type_definition = prev_in_type_definition;
}
for expr in args.iter().skip(1) {
self.visit_expr(expr);
@@ -2056,22 +2152,28 @@ where
} else if self.match_typing_call_path(&call_path, "NewType") {
self.visit_expr(func);
for expr in args.iter().skip(1) {
self.visit_annotation(expr);
self.in_type_definition = true;
self.visit_expr(expr);
self.in_type_definition = prev_in_type_definition;
}
} else if self.match_typing_call_path(&call_path, "TypeVar") {
self.visit_expr(func);
for expr in args.iter().skip(1) {
self.visit_annotation(expr);
self.in_type_definition = true;
self.visit_expr(expr);
self.in_type_definition = prev_in_type_definition;
}
for keyword in keywords {
let KeywordData { arg, value } = &keyword.node;
if let Some(id) = arg {
if id == "bound" {
self.visit_annotation(value);
} else {
self.in_annotation = false;
self.in_type_definition = true;
self.visit_expr(value);
self.in_annotation = prev_in_annotation;
self.in_type_definition = prev_in_type_definition;
} else {
self.in_type_definition = false;
self.visit_expr(value);
self.in_type_definition = prev_in_type_definition;
}
}
}
@@ -2087,11 +2189,13 @@ where
ExprKind::List { elts, .. }
| ExprKind::Tuple { elts, .. } => {
if elts.len() == 2 {
self.in_annotation = false;
self.in_type_definition = false;
self.visit_expr(&elts[0]);
self.in_annotation = prev_in_annotation;
self.in_type_definition = prev_in_type_definition;
self.visit_annotation(&elts[1]);
self.in_type_definition = true;
self.visit_expr(&elts[1]);
self.in_type_definition = prev_in_type_definition;
}
}
_ => {}
@@ -2105,7 +2209,9 @@ where
// Ex) NamedTuple("a", a=int)
for keyword in keywords {
let KeywordData { value, .. } = &keyword.node;
self.visit_annotation(value);
self.in_type_definition = true;
self.visit_expr(value);
self.in_type_definition = prev_in_type_definition;
}
} else if self.match_typing_call_path(&call_path, "TypedDict") {
self.visit_expr(func);
@@ -2114,12 +2220,14 @@ where
if args.len() > 1 {
if let ExprKind::Dict { keys, values } = &args[1].node {
for key in keys {
self.in_annotation = false;
self.in_type_definition = false;
self.visit_expr(key);
self.in_annotation = prev_in_annotation;
self.in_type_definition = prev_in_type_definition;
}
for value in values {
self.visit_annotation(value);
self.in_type_definition = true;
self.visit_expr(value);
self.in_type_definition = prev_in_type_definition;
}
}
}
@@ -2127,7 +2235,9 @@ where
// Ex) TypedDict("a", a=int)
for keyword in keywords {
let KeywordData { value, .. } = &keyword.node;
self.visit_annotation(value);
self.in_type_definition = true;
self.visit_expr(value);
self.in_type_definition = prev_in_type_definition;
}
} else {
visitor::walk_expr(self, expr);
@@ -2157,7 +2267,9 @@ where
// Ex) Optional[int]
SubscriptKind::AnnotatedSubscript => {
self.visit_expr(value);
self.visit_annotation(slice);
self.in_type_definition = true;
self.visit_expr(slice);
self.in_type_definition = prev_in_type_definition;
self.visit_expr_context(ctx);
}
// Ex) Annotated[int, "Hello, world!"]
@@ -2169,11 +2281,11 @@ where
if let ExprKind::Tuple { elts, ctx } = &slice.node {
if let Some(expr) = elts.first() {
self.visit_expr(expr);
self.in_annotation = false;
self.in_type_definition = false;
for expr in elts.iter().skip(1) {
self.visit_expr(expr);
}
self.in_annotation = true;
self.in_type_definition = prev_in_type_definition;
self.visit_expr_context(ctx);
}
} else {
@@ -2205,7 +2317,7 @@ where
_ => {}
};
self.in_annotation = prev_in_annotation;
self.in_type_definition = prev_in_type_definition;
self.in_literal = prev_in_literal;
self.in_f_string = prev_in_f_string;
}
@@ -2777,28 +2889,33 @@ impl<'a> Checker<'a> {
docstring.is_some()
}
fn check_deferred_annotations(&mut self) {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
fn check_deferred_type_definitions(&mut self) {
while let Some((expr, in_annotation, (scopes, parents))) =
self.deferred_type_definitions.pop()
{
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_annotation = true;
self.in_annotation = in_annotation;
self.in_type_definition = true;
self.in_deferred_type_definition = true;
self.visit_expr(expr);
self.in_deferred_annotation = false;
self.in_deferred_type_definition = false;
self.in_type_definition = false;
}
}
fn check_deferred_string_annotations<'b>(&mut self, allocator: &'b mut Vec<Expr>)
fn check_deferred_string_type_definitions<'b>(&mut self, allocator: &'b mut Vec<Expr>)
where
'b: 'a,
{
let mut stacks = vec![];
while let Some((range, expression, scopes, parents)) =
self.deferred_string_annotations.pop()
while let Some((range, expression, in_annotation, context)) =
self.deferred_string_type_definitions.pop()
{
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
relocate_expr(&mut expr, range);
allocator.push(expr);
stacks.push((scopes, parents));
stacks.push((in_annotation, context));
} else {
if self.settings.enabled.contains(&CheckCode::F722) {
self.add_check(Check::new(
@@ -2808,19 +2925,22 @@ impl<'a> Checker<'a> {
}
}
}
for (expr, (scopes, parents)) in allocator.iter().zip(stacks) {
for (expr, (in_annotation, (scopes, parents))) in allocator.iter().zip(stacks) {
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_string_annotation = true;
self.in_annotation = in_annotation;
self.in_type_definition = true;
self.in_deferred_string_type_definition = true;
self.visit_expr(expr);
self.in_deferred_string_annotation = false;
self.in_deferred_string_type_definition = false;
self.in_type_definition = false;
}
}
fn check_deferred_functions(&mut self) {
while let Some((stmt, scopes, parents, visibility)) = self.deferred_functions.pop() {
self.parent_stack = parents;
while let Some((stmt, (scopes, parents), visibility)) = self.deferred_functions.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.visible_scope = visibility;
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope {
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
@@ -2846,9 +2966,9 @@ impl<'a> Checker<'a> {
}
fn check_deferred_lambdas(&mut self) {
while let Some((expr, scopes, parents)) = self.deferred_lambdas.pop() {
self.parent_stack = parents;
while let Some((expr, (scopes, parents))) = self.deferred_lambdas.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.push_scope(Scope::new(ScopeKind::Lambda));
if let ExprKind::Lambda { args, body } = &expr.node {
@@ -2975,8 +3095,9 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let in_init_py = self.path.ends_with("__init__.py");
let fix = if !in_init_py && self.patch(&CheckCode::F401) {
let ignore_init = self.settings.ignore_init_module_imports
&& self.path.ends_with("__init__.py");
let fix = if !ignore_init && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
@@ -3006,7 +3127,7 @@ impl<'a> Checker<'a> {
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(full_name.clone(), in_init_py),
CheckKind::UnusedImport(full_name.clone(), ignore_init),
*range,
);
if let Some(fix) = fix.as_ref() {
@@ -3185,9 +3306,9 @@ pub fn check_ast(
checker.check_deferred_functions();
checker.check_deferred_lambdas();
checker.check_deferred_assignments();
checker.check_deferred_annotations();
checker.check_deferred_type_definitions();
let mut allocator = vec![];
checker.check_deferred_string_annotations(&mut allocator);
checker.check_deferred_string_type_definitions(&mut allocator);
// Reset the scope to module-level, and check all consumed scopes.
checker.scope_stack = vec![GLOBAL_SCOPE_INDEX];

View File

@@ -7,12 +7,12 @@ use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::checks::{Check, CheckCode, CheckKind, REDIRECTS};
use crate::noqa;
use crate::noqa::Directive;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::Settings;
// Regex from PEP263
// Regex from PEP263.
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").unwrap());
@@ -43,9 +43,9 @@ pub fn check_lines(
autofix: bool,
ignore_noqa: bool,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::UP009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
let enforce_noqa = settings.enabled.contains(&CheckCode::RUF100);
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut line_checks = vec![];
@@ -58,8 +58,11 @@ pub fn check_lines(
}
macro_rules! add_if {
($check:expr, $noqa:expr) => {{
match $noqa {
($check:expr, $noqa_lineno:expr, $line:expr) => {{
match noqa_directives
.entry($noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive($line), vec![]))
{
(Directive::All(..), matches) => {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
@@ -67,7 +70,7 @@ pub fn check_lines(
}
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&$check.kind.code().as_ref()) {
if noqa::includes($check.kind.code(), codes) {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
@@ -83,12 +86,18 @@ pub fn check_lines(
let lines: Vec<&str> = contents.lines().collect();
for (lineno, line) in lines.iter().enumerate() {
// If we hit an exemption for the entire file, bail.
if is_file_exempt(line) {
checks.drain(..);
return;
}
// Grab the noqa (logical) line number for the current (physical) line.
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
// Enforce unnecessary coding comments (U009).
// Enforce unnecessary coding comments (UP009).
if enforce_unnecessary_coding_comment {
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
@@ -106,11 +115,7 @@ pub fn check_lines(
Location::new(lineno + 2, 0),
));
}
let noqa = noqa_directives.entry(noqa_lineno).or_insert_with(|| {
(noqa::extract_noqa_directive(lines[noqa_lineno]), vec![])
});
add_if!(check, noqa);
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
}
@@ -135,7 +140,7 @@ pub fn check_lines(
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
if noqa::includes(check.kind.code(), codes) {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
@@ -155,11 +160,7 @@ pub fn check_lines(
end_location: Location::new(lineno + 1, line_length),
},
);
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
}
@@ -179,14 +180,11 @@ pub fn check_lines(
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
add_if!(check, noqa_lineno, lines[noqa_lineno]);
}
}
// Enforce that the noqa directive was actually used (M001).
// Enforce that the noqa directive was actually used (RUF100).
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
@@ -212,6 +210,7 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
let code = REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
@@ -269,13 +268,12 @@ mod tests {
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let noqa_line_for: IntMap<usize, usize> = IntMap::default();
let check_with_max_line_length = |line_length: usize| {
let mut checks: Vec<Check> = vec![];
check_lines(
&mut checks,
line,
&noqa_line_for,
&IntMap::default(),
&Settings {
line_length,
..Settings::for_rule(CheckCode::E501)

View File

@@ -2,6 +2,8 @@ use std::fmt;
use std::str::FromStr;
use itertools::Itertools;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
@@ -90,8 +92,16 @@ pub enum CheckCode {
F831,
F841,
F901,
// pylint errors
// pylint
PLC0414,
PLC2201,
PLC3002,
PLE1142,
PLR0206,
PLR0402,
PLR1701,
PLR1722,
PLW0120,
// flake8-builtins
A001,
A002,
@@ -149,6 +159,15 @@ pub enum CheckCode {
C901,
// flake8-tidy-imports
I252,
// flake8-return
RET501,
RET502,
RET503,
RET504,
RET505,
RET506,
RET507,
RET508,
// flake8-print
T201,
T203,
@@ -181,20 +200,20 @@ pub enum CheckCode {
YTT302,
YTT303,
// pyupgrade
U001,
U003,
U004,
U005,
U006,
U007,
U008,
U009,
U010,
U011,
U012,
U013,
U014,
U015,
UP001,
UP003,
UP004,
UP005,
UP006,
UP007,
UP008,
UP009,
UP010,
UP011,
UP012,
UP013,
UP014,
UP015,
// pydocstyle
D100,
D101,
@@ -275,36 +294,51 @@ pub enum CheckCode {
RUF001,
RUF002,
RUF003,
RUF101,
// Meta
M001,
RUF100,
// pygrep-hooks
PGH001,
}
#[derive(EnumIter, Debug, PartialEq, Eq)]
pub enum CheckCategory {
Pyflakes,
Pycodestyle,
McCabe,
Isort,
Pydocstyle,
Pyupgrade,
PEP8Naming,
Eradicate,
Flake82020,
Flake8Annotations,
Flake8Bandit,
Flake8Comprehensions,
Flake8Debugger,
Flake8BlindExcept,
Flake8BooleanTrap,
Flake8Bugbear,
Flake8Builtins,
Flake8TidyImports,
Flake8Comprehensions,
Flake8Debugger,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Flake82020,
Flake8BlindExcept,
McCabe,
Flake8Return,
Flake8TidyImports,
Eradicate,
PygrepHooks,
Pylint,
Ruff,
Meta,
}
pub enum Platform {
PyPI,
GitHub,
}
impl fmt::Display for Platform {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Platform::PyPI => fmt.write_str("PyPI"),
Platform::GitHub => fmt.write_str("GitHub"),
}
}
}
impl CheckCategory {
@@ -322,60 +356,109 @@ impl CheckCategory {
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Return => "flake8-return",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
CheckCategory::Meta => "Meta rules",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::PygrepHooks => "pygrep-hooks",
CheckCategory::Pylint => "Pylint",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
}
pub fn url(&self) -> Option<&'static str> {
pub fn url(&self) -> Option<(&'static str, &'static Platform)> {
match self {
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::Eradicate => {
Some(("https://pypi.org/project/eradicate/2.1.0/", &Platform::PyPI))
}
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::Flake82020 => Some((
"https://pypi.org/project/flake8-2020/1.7.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8Annotations => Some((
"https://pypi.org/project/flake8-annotations/2.9.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8Bandit => Some((
"https://pypi.org/project/flake8-bandit/4.1.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8BlindExcept => Some((
"https://pypi.org/project/flake8-blind-except/0.2.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8BooleanTrap => Some((
"https://pypi.org/project/flake8-boolean-trap/0.1.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8Bugbear => Some((
"https://pypi.org/project/flake8-bugbear/22.10.27/",
&Platform::PyPI,
)),
CheckCategory::Flake8Builtins => Some((
"https://pypi.org/project/flake8-builtins/2.0.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8Comprehensions => Some((
"https://pypi.org/project/flake8-comprehensions/3.10.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8Debugger => Some((
"https://pypi.org/project/flake8-debugger/4.1.2/",
&Platform::PyPI,
)),
CheckCategory::Flake8Print => Some((
"https://pypi.org/project/flake8-print/5.0.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8Quotes => Some((
"https://pypi.org/project/flake8-quotes/3.3.1/",
&Platform::PyPI,
)),
CheckCategory::Flake8Return => Some((
"https://pypi.org/project/flake8-return/1.2.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8TidyImports => Some((
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
&Platform::PyPI,
)),
CheckCategory::Isort => {
Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI))
}
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
CheckCategory::McCabe => {
Some(("https://pypi.org/project/mccabe/0.7.0/", &Platform::PyPI))
}
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
CheckCategory::PEP8Naming => Some((
"https://pypi.org/project/pep8-naming/0.13.2/",
&Platform::PyPI,
)),
CheckCategory::Pycodestyle => Some((
"https://pypi.org/project/pycodestyle/2.9.1/",
&Platform::PyPI,
)),
CheckCategory::Pydocstyle => Some((
"https://pypi.org/project/pydocstyle/6.1.1/",
&Platform::PyPI,
)),
CheckCategory::Pyflakes => {
Some(("https://pypi.org/project/pyflakes/2.5.0/", &Platform::PyPI))
}
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
CheckCategory::Pylint => {
Some(("https://pypi.org/project/pylint/2.15.7/", &Platform::PyPI))
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
CheckCategory::PygrepHooks => Some((
"https://github.com/pre-commit/pygrep-hooks",
&Platform::GitHub,
)),
CheckCategory::Pyupgrade => {
Some(("https://pypi.org/project/pyupgrade/3.2.0/", &Platform::PyPI))
}
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::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
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::Pylint => Some("https://pypi.org/project/pylint/2.15.7/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Ruff => None,
}
}
@@ -413,6 +496,21 @@ impl fmt::Display for DeferralKeyword {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Branch {
Elif,
Else,
}
impl fmt::Display for Branch {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Branch::Elif => fmt.write_str("elif"),
Branch::Else => fmt.write_str("else"),
}
}
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
@@ -476,8 +574,16 @@ pub enum CheckKind {
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction(DeferralKeyword),
// pylint errors
// pylint
ConsiderMergingIsinstance(String, Vec<String>),
UselessImportAlias,
MisplacedComparisonConstant(String),
UnnecessaryDirectLambdaCall,
PropertyWithParameters,
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
UselessElseOnLoop,
ConsiderUsingSysExit,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
@@ -533,6 +639,15 @@ pub enum CheckKind {
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedRelativeImport(Strictness),
// flake8-return
UnnecessaryReturnNone,
ImplicitReturnValue,
ImplicitReturn,
UnnecessaryAssign,
SuperfluousElseReturn(Branch),
SuperfluousElseRaise(Branch),
SuperfluousElseContinue(Branch),
SuperfluousElseBreak(Branch),
// flake8-print
PrintFound,
PPrintFound,
@@ -657,12 +772,12 @@ pub enum CheckKind {
BooleanPositionalArgInFunctionDefinition,
BooleanDefaultValueInFunctionDefinition,
BooleanPositionalValueInFunctionCall,
// pygrep-hooks
NoEval,
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
ConvertExitToSysExit,
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -671,7 +786,7 @@ impl CheckCode {
/// physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
CheckCode::E501 | CheckCode::W292 | CheckCode::RUF100 | CheckCode::UP009 => {
&LintSource::Lines
}
CheckCode::ERA001
@@ -759,8 +874,20 @@ impl CheckCode {
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// pylint errors
// pylint
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
CheckCode::PLC2201 => CheckKind::MisplacedComparisonConstant("...".to_string()),
CheckCode::PLC3002 => CheckKind::UnnecessaryDirectLambdaCall,
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
CheckCode::PLR0402 => {
CheckKind::ConsiderUsingFromImport("...".to_string(), "...".to_string())
}
CheckCode::PLR0206 => CheckKind::PropertyWithParameters,
CheckCode::PLR1701 => {
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
}
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
@@ -831,6 +958,15 @@ impl CheckCode {
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-return
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
CheckCode::RET502 => CheckKind::ImplicitReturnValue,
CheckCode::RET503 => CheckKind::ImplicitReturn,
CheckCode::RET504 => CheckKind::UnnecessaryAssign,
CheckCode::RET505 => CheckKind::SuperfluousElseReturn(Branch::Else),
CheckCode::RET506 => CheckKind::SuperfluousElseRaise(Branch::Else),
CheckCode::RET507 => CheckKind::SuperfluousElseContinue(Branch::Else),
CheckCode::RET508 => CheckKind::SuperfluousElseBreak(Branch::Else),
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
@@ -865,23 +1001,23 @@ impl CheckCode {
// flake8-blind-except
CheckCode::BLE001 => CheckKind::BlindExcept,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::DeprecatedUnittestAlias(
CheckCode::UP001 => CheckKind::UselessMetaclassType,
CheckCode::UP003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::UP004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::UP005 => CheckKind::DeprecatedUnittestAlias(
"assertEquals".to_string(),
"assertEqual".to_string(),
),
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
CheckCode::U009 => CheckKind::PEP3120UnnecessaryCodingComment,
CheckCode::U010 => CheckKind::UnnecessaryFutureImport(vec!["...".to_string()]),
CheckCode::U011 => CheckKind::UnnecessaryLRUCacheParams,
CheckCode::U012 => CheckKind::UnnecessaryEncodeUTF8,
CheckCode::U013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()),
CheckCode::U014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
CheckCode::U015 => CheckKind::RedundantOpenModes,
CheckCode::UP006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::UP007 => CheckKind::UsePEP604Annotation,
CheckCode::UP008 => CheckKind::SuperCallWithParameters,
CheckCode::UP009 => CheckKind::PEP3120UnnecessaryCodingComment,
CheckCode::UP010 => CheckKind::UnnecessaryFutureImport(vec!["...".to_string()]),
CheckCode::UP011 => CheckKind::UnnecessaryLRUCacheParams,
CheckCode::UP012 => CheckKind::UnnecessaryEncodeUTF8,
CheckCode::UP013 => CheckKind::ConvertTypedDictFunctionalToClass("...".to_string()),
CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
CheckCode::UP015 => CheckKind::RedundantOpenModes,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -975,13 +1111,13 @@ impl CheckCode {
CheckCode::FBT001 => CheckKind::BooleanPositionalArgInFunctionDefinition,
CheckCode::FBT002 => CheckKind::BooleanDefaultValueInFunctionDefinition,
CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall,
// pygrep-hooks
CheckCode::PGH001 => CheckKind::NoEval,
// Ruff
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
CheckCode::RUF101 => CheckKind::ConvertExitToSysExit,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
CheckCode::RUF100 => CheckKind::UnusedNOQA(None),
}
}
@@ -1153,7 +1289,6 @@ impl CheckCode {
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,
@@ -1169,15 +1304,32 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::PGH001 => CheckCategory::PygrepHooks,
CheckCode::PLC0414 => CheckCategory::Pylint,
CheckCode::PLC2201 => CheckCategory::Pylint,
CheckCode::PLC3002 => CheckCategory::Pylint,
CheckCode::PLE1142 => CheckCategory::Pylint,
CheckCode::PLR0206 => CheckCategory::Pylint,
CheckCode::PLR0402 => CheckCategory::Pylint,
CheckCode::PLR1701 => CheckCategory::Pylint,
CheckCode::PLR1722 => CheckCategory::Pylint,
CheckCode::PLW0120 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::RET501 => CheckCategory::Flake8Return,
CheckCode::RET502 => CheckCategory::Flake8Return,
CheckCode::RET503 => CheckCategory::Flake8Return,
CheckCode::RET504 => CheckCategory::Flake8Return,
CheckCode::RET505 => CheckCategory::Flake8Return,
CheckCode::RET506 => CheckCategory::Flake8Return,
CheckCode::RET507 => CheckCategory::Flake8Return,
CheckCode::RET508 => CheckCategory::Flake8Return,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::RUF100 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
CheckCode::S104 => CheckCategory::Flake8Bandit,
@@ -1187,20 +1339,20 @@ impl CheckCode {
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::UP001 => CheckCategory::Pyupgrade,
CheckCode::UP003 => CheckCategory::Pyupgrade,
CheckCode::UP004 => CheckCategory::Pyupgrade,
CheckCode::UP005 => CheckCategory::Pyupgrade,
CheckCode::UP006 => CheckCategory::Pyupgrade,
CheckCode::UP007 => CheckCategory::Pyupgrade,
CheckCode::UP008 => CheckCategory::Pyupgrade,
CheckCode::UP009 => CheckCategory::Pyupgrade,
CheckCode::UP010 => CheckCategory::Pyupgrade,
CheckCode::UP011 => CheckCategory::Pyupgrade,
CheckCode::UP012 => CheckCategory::Pyupgrade,
CheckCode::UP013 => CheckCategory::Pyupgrade,
CheckCode::UP014 => CheckCategory::Pyupgrade,
CheckCode::UP015 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1281,8 +1433,16 @@ impl CheckKind {
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
// pylint errors
// pylint
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
@@ -1338,6 +1498,15 @@ impl CheckKind {
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
CheckKind::ImplicitReturn => &CheckCode::RET503,
CheckKind::UnnecessaryAssign => &CheckCode::RET504,
CheckKind::SuperfluousElseReturn(_) => &CheckCode::RET505,
CheckKind::SuperfluousElseRaise(_) => &CheckCode::RET506,
CheckKind::SuperfluousElseContinue(_) => &CheckCode::RET507,
CheckKind::SuperfluousElseBreak(_) => &CheckCode::RET508,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@@ -1370,20 +1539,20 @@ impl CheckKind {
CheckKind::SysVersionCmpStr10 => &CheckCode::YTT302,
CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::DeprecatedUnittestAlias(..) => &CheckCode::U005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::U009,
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::U010,
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::U011,
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::U012,
CheckKind::ConvertTypedDictFunctionalToClass(_) => &CheckCode::U013,
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::U014,
CheckKind::RedundantOpenModes => &CheckCode::U015,
CheckKind::TypeOfPrimitive(_) => &CheckCode::UP003,
CheckKind::UselessMetaclassType => &CheckCode::UP001,
CheckKind::DeprecatedUnittestAlias(..) => &CheckCode::UP005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::UP006,
CheckKind::UsePEP604Annotation => &CheckCode::UP007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::UP004,
CheckKind::SuperCallWithParameters => &CheckCode::UP008,
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::UP009,
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::UP010,
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::UP011,
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::UP012,
CheckKind::ConvertTypedDictFunctionalToClass(_) => &CheckCode::UP013,
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::UP014,
CheckKind::RedundantOpenModes => &CheckCode::UP015,
// pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
@@ -1456,19 +1625,19 @@ impl CheckKind {
CheckKind::HardcodedPasswordString(..) => &CheckCode::S105,
CheckKind::HardcodedPasswordFuncArg(..) => &CheckCode::S106,
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
// McCabe
// mccabe
CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901,
// flake8-boolean-trap
CheckKind::BooleanPositionalArgInFunctionDefinition => &CheckCode::FBT001,
CheckKind::BooleanDefaultValueInFunctionDefinition => &CheckCode::FBT002,
CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003,
// pygrep-hooks
CheckKind::NoEval => &CheckCode::PGH001,
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
CheckKind::ConvertExitToSysExit => &CheckCode::RUF101,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
CheckKind::UnusedNOQA(_) => &CheckCode::RUF100,
}
}
@@ -1634,9 +1803,12 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name, in_init_py) => {
if *in_init_py {
format!("`{name}` imported but unused and missing from `__all__`")
CheckKind::UnusedImport(name, ignore_init) => {
if *ignore_init {
format!(
"`{name}` imported but unused; consider adding to `__all__` or using a \
redundant alias"
)
} else {
format!("`{name}` imported but unused")
}
@@ -1652,10 +1824,33 @@ impl CheckKind {
CheckKind::InvalidEscapeSequence(char) => {
format!("Invalid escape sequence: '\\{char}'")
}
// pylint errors
// pylint
CheckKind::UselessImportAlias => {
"Import alias does not rename original package".to_string()
}
CheckKind::ConsiderMergingIsinstance(obj, types) => {
let types = types.join(", ");
format!("Consider merging these isinstance calls: `isinstance({obj}, ({types}))`")
}
CheckKind::MisplacedComparisonConstant(comprison) => {
format!("Comparison should be {comprison}")
}
CheckKind::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
the expression inline instead."
.to_string(),
CheckKind::PropertyWithParameters => {
"Cannot have defined parameters for properties".to_string()
}
CheckKind::ConsiderUsingFromImport(module, name) => {
format!("Consider using `from {module} import {name}`")
}
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
}
CheckKind::UselessElseOnLoop => "Else clause on loop without a break statement, \
remove the else and de-indent all the code inside it"
.to_string(),
CheckKind::ConsiderUsingSysExit => "Consider using `sys.exit()`".to_string(),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -1866,6 +2061,31 @@ impl CheckKind {
}
Strictness::All => "Relative imports are banned".to_string(),
},
// flake8-return
CheckKind::UnnecessaryReturnNone => "Do not explicitly `return None` in function if \
it is the only possible return value"
.to_string(),
CheckKind::ImplicitReturnValue => "Do not implicitly `return None` in function able \
to return non-`None` value"
.to_string(),
CheckKind::ImplicitReturn => "Missing explicit `return` at the end of function able \
to return non-`None` value"
.to_string(),
CheckKind::UnnecessaryAssign => {
"Unnecessary variable assignment before `return` statement".to_string()
}
CheckKind::SuperfluousElseReturn(branch) => {
format!("Unnecessary `{branch}` after `return` statement")
}
CheckKind::SuperfluousElseRaise(branch) => {
format!("Unnecessary `{branch}` after `raise` statement")
}
CheckKind::SuperfluousElseContinue(branch) => {
format!("Unnecessary `{branch}` after `continue` statement")
}
CheckKind::SuperfluousElseBreak(branch) => {
format!("Unnecessary `{branch}` after `break` statement")
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
@@ -2183,7 +2403,7 @@ impl CheckKind {
}
// flake8-blind-except
CheckKind::BlindExcept => "Blind except Exception: statement".to_string(),
// McCabe
// mccabe
CheckKind::FunctionIsTooComplex(name, complexity) => {
format!("`{name}` is too complex ({complexity})")
}
@@ -2197,6 +2417,8 @@ impl CheckKind {
CheckKind::BooleanPositionalValueInFunctionCall => {
"Boolean positional value in function call".to_string()
}
// pygrep-hooks
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
format!(
@@ -2216,10 +2438,6 @@ impl CheckKind {
'{representant}'?)"
)
}
CheckKind::ConvertExitToSysExit => "`exit()` is only available in the interpreter, \
use `sys.exit()` instead"
.to_string(),
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
Some(codes) => {
@@ -2271,7 +2489,7 @@ impl CheckKind {
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConsiderUsingSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
@@ -2280,7 +2498,10 @@ impl CheckKind {
| CheckKind::DoNotAssignLambda
| CheckKind::DuplicateHandlerException(..)
| CheckKind::GetAttrWithConstant
| CheckKind::ImplicitReturn
| CheckKind::ImplicitReturnValue
| CheckKind::IsLiteral
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
@@ -2299,6 +2520,7 @@ impl CheckKind {
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::RaiseNotImplemented
| CheckKind::RedundantOpenModes
| CheckKind::RedundantTupleInExceptionHandler(..)
| CheckKind::SectionNameEndsInColon(..)
| CheckKind::SectionNotOverIndented(..)
@@ -2313,7 +2535,6 @@ impl CheckKind {
| CheckKind::UnnecessaryComprehension(..)
| CheckKind::UnnecessaryEncodeUTF8
| CheckKind::UnnecessaryFutureImport(..)
| CheckKind::RedundantOpenModes
| CheckKind::UnnecessaryGeneratorDict
| CheckKind::UnnecessaryGeneratorList
| CheckKind::UnnecessaryGeneratorSet
@@ -2325,12 +2546,14 @@ impl CheckKind {
| CheckKind::UnnecessaryLiteralSet(..)
| CheckKind::UnnecessaryLiteralWithinListCall(..)
| CheckKind::UnnecessaryLiteralWithinTupleCall(..)
| CheckKind::UnnecessaryReturnNone
| CheckKind::UnsortedImports
| CheckKind::UnusedImport(_, false)
| CheckKind::UnusedLoopControlVariable(..)
| CheckKind::UnusedNOQA(..)
| CheckKind::UsePEP585Annotation(..)
| CheckKind::UsePEP604Annotation
| CheckKind::UselessImportAlias
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(..)
)
@@ -2360,6 +2583,27 @@ impl Check {
}
}
/// A hash map from deprecated to latest `CheckCode`.
pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", CheckCode::UP001),
("U003", CheckCode::UP003),
("U004", CheckCode::UP004),
("U005", CheckCode::UP005),
("U006", CheckCode::UP006),
("U007", CheckCode::UP007),
("U008", CheckCode::UP008),
("U009", CheckCode::UP009),
("U010", CheckCode::UP010),
("U011", CheckCode::UP011),
("U012", CheckCode::UP012),
("U013", CheckCode::UP013),
("U014", CheckCode::UP014),
("U015", CheckCode::UP015),
])
});
#[cfg(test)]
mod tests {
use std::str::FromStr;

View File

@@ -1,5 +1,6 @@
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
use colored::Colorize;
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;
@@ -255,10 +256,6 @@ pub enum CheckCodePrefix {
I2,
I25,
I252,
M,
M0,
M00,
M001,
N,
N8,
N80,
@@ -278,11 +275,47 @@ pub enum CheckCodePrefix {
N816,
N817,
N818,
PGH,
PGH0,
PGH00,
PGH001,
PLC,
PLC0,
PLC04,
PLC041,
PLC0414,
PLC2,
PLC22,
PLC220,
PLC2201,
PLC3,
PLC30,
PLC300,
PLC3002,
PLE,
PLE1,
PLE11,
PLE114,
PLE1142,
PLR,
PLR0,
PLR02,
PLR020,
PLR0206,
PLR04,
PLR040,
PLR0402,
PLR1,
PLR17,
PLR170,
PLR1701,
PLR172,
PLR1722,
PLW,
PLW0,
PLW01,
PLW012,
PLW0120,
Q,
Q0,
Q00,
@@ -290,6 +323,17 @@ pub enum CheckCodePrefix {
Q001,
Q002,
Q003,
RET,
RET5,
RET50,
RET501,
RET502,
RET503,
RET504,
RET505,
RET506,
RET507,
RET508,
RUF,
RUF0,
RUF00,
@@ -298,7 +342,7 @@ pub enum CheckCodePrefix {
RUF003,
RUF1,
RUF10,
RUF101,
RUF100,
S,
S1,
S10,
@@ -334,6 +378,24 @@ pub enum CheckCodePrefix {
U013,
U014,
U015,
UP,
UP0,
UP00,
UP001,
UP003,
UP004,
UP005,
UP006,
UP007,
UP008,
UP009,
UP01,
UP010,
UP011,
UP012,
UP013,
UP014,
UP015,
W,
W2,
W29,
@@ -1076,10 +1138,6 @@ impl CheckCodePrefix {
CheckCodePrefix::I2 => vec![CheckCode::I252],
CheckCodePrefix::I25 => vec![CheckCode::I252],
CheckCodePrefix::I252 => vec![CheckCode::I252],
CheckCodePrefix::M => vec![CheckCode::M001],
CheckCodePrefix::M0 => vec![CheckCode::M001],
CheckCodePrefix::M00 => vec![CheckCode::M001],
CheckCodePrefix::M001 => vec![CheckCode::M001],
CheckCodePrefix::N => vec![
CheckCode::N801,
CheckCode::N802,
@@ -1148,11 +1206,54 @@ impl CheckCodePrefix {
CheckCodePrefix::N816 => vec![CheckCode::N816],
CheckCodePrefix::N817 => vec![CheckCode::N817],
CheckCodePrefix::N818 => vec![CheckCode::N818],
CheckCodePrefix::PGH => vec![CheckCode::PGH001],
CheckCodePrefix::PGH0 => vec![CheckCode::PGH001],
CheckCodePrefix::PGH00 => vec![CheckCode::PGH001],
CheckCodePrefix::PGH001 => vec![CheckCode::PGH001],
CheckCodePrefix::PLC => {
vec![CheckCode::PLC0414, CheckCode::PLC2201, CheckCode::PLC3002]
}
CheckCodePrefix::PLC0 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC04 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC041 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC0414 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC2 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC22 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC220 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC2201 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC3 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC30 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC300 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC3002 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLE => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE1 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE1142 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLR => vec![
CheckCode::PLR0206,
CheckCode::PLR0402,
CheckCode::PLR1701,
CheckCode::PLR1722,
],
CheckCodePrefix::PLR0 => vec![CheckCode::PLR0206, CheckCode::PLR0402],
CheckCodePrefix::PLR02 => vec![CheckCode::PLR0206],
CheckCodePrefix::PLR020 => vec![CheckCode::PLR0206],
CheckCodePrefix::PLR0206 => vec![CheckCode::PLR0206],
CheckCodePrefix::PLR04 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR040 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR0402 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR1 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR17 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR170 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW012 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0120 => vec![CheckCode::PLW0120],
CheckCodePrefix::Q => vec![
CheckCode::Q000,
CheckCode::Q001,
@@ -1175,20 +1276,58 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::RET => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET5 => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET50 => vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
],
CheckCodePrefix::RET501 => vec![CheckCode::RET501],
CheckCodePrefix::RET502 => vec![CheckCode::RET502],
CheckCodePrefix::RET503 => vec![CheckCode::RET503],
CheckCodePrefix::RET504 => vec![CheckCode::RET504],
CheckCodePrefix::RET505 => vec![CheckCode::RET505],
CheckCodePrefix::RET506 => vec![CheckCode::RET506],
CheckCodePrefix::RET507 => vec![CheckCode::RET507],
CheckCodePrefix::RET508 => vec![CheckCode::RET508],
CheckCodePrefix::RUF => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF101,
CheckCode::RUF100,
],
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
CheckCodePrefix::RUF1 => vec![CheckCode::RUF101],
CheckCodePrefix::RUF10 => vec![CheckCode::RUF101],
CheckCodePrefix::RUF101 => vec![CheckCode::RUF101],
CheckCodePrefix::RUF1 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF10 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF100 => vec![CheckCode::RUF100],
CheckCodePrefix::S => vec![
CheckCode::S101,
CheckCode::S102,
@@ -1228,69 +1367,245 @@ impl CheckCodePrefix {
CheckCodePrefix::T201 => vec![CheckCode::T201],
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::U => vec![
CheckCode::U001,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
CheckCode::U012,
CheckCode::U013,
CheckCode::U014,
CheckCode::U015,
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U0 => vec![
CheckCode::U001,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
CheckCode::U012,
CheckCode::U013,
CheckCode::U014,
CheckCode::U015,
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U00 => vec![
CheckCode::U001,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
],
CheckCodePrefix::U001 => vec![CheckCode::U001],
CheckCodePrefix::U003 => vec![CheckCode::U003],
CheckCodePrefix::U004 => vec![CheckCode::U004],
CheckCodePrefix::U005 => vec![CheckCode::U005],
CheckCodePrefix::U006 => vec![CheckCode::U006],
CheckCodePrefix::U007 => vec![CheckCode::U007],
CheckCodePrefix::U008 => vec![CheckCode::U008],
CheckCodePrefix::U009 => vec![CheckCode::U009],
CheckCodePrefix::U001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U001` has been renamed to `UP001`".bold()
);
vec![CheckCode::UP001]
}
CheckCodePrefix::U003 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U003` has been renamed to `UP003`".bold()
);
vec![CheckCode::UP003]
}
CheckCodePrefix::U004 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U004` has been renamed to `UP004`".bold()
);
vec![CheckCode::UP004]
}
CheckCodePrefix::U005 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U005` has been renamed to `UP005`".bold()
);
vec![CheckCode::UP005]
}
CheckCodePrefix::U006 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U006` has been renamed to `UP006`".bold()
);
vec![CheckCode::UP006]
}
CheckCodePrefix::U007 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U007` has been renamed to `UP007`".bold()
);
vec![CheckCode::UP007]
}
CheckCodePrefix::U008 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U008` has been renamed to `UP008`".bold()
);
vec![CheckCode::UP008]
}
CheckCodePrefix::U009 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U009` has been renamed to `UP009`".bold()
);
vec![CheckCode::UP009]
}
CheckCodePrefix::U01 => vec![
CheckCode::U010,
CheckCode::U011,
CheckCode::U012,
CheckCode::U013,
CheckCode::U014,
CheckCode::U015,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U010 => vec![CheckCode::U010],
CheckCodePrefix::U011 => vec![CheckCode::U011],
CheckCodePrefix::U012 => vec![CheckCode::U012],
CheckCodePrefix::U013 => vec![CheckCode::U013],
CheckCodePrefix::U014 => vec![CheckCode::U014],
CheckCodePrefix::U015 => vec![CheckCode::U015],
CheckCodePrefix::U010 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U010` has been renamed to `UP010`".bold()
);
vec![CheckCode::UP010]
}
CheckCodePrefix::U011 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U011` has been renamed to `UP011`".bold()
);
vec![CheckCode::UP011]
}
CheckCodePrefix::U012 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U012` has been renamed to `UP012`".bold()
);
vec![CheckCode::UP012]
}
CheckCodePrefix::U013 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U013` has been renamed to `UP013`".bold()
);
vec![CheckCode::UP013]
}
CheckCodePrefix::U014 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U014` has been renamed to `UP014`".bold()
);
vec![CheckCode::UP014]
}
CheckCodePrefix::U015 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U015` has been renamed to `UP015`".bold()
);
vec![CheckCode::UP015]
}
CheckCodePrefix::UP => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
],
CheckCodePrefix::UP001 => vec![CheckCode::UP001],
CheckCodePrefix::UP003 => vec![CheckCode::UP003],
CheckCodePrefix::UP004 => vec![CheckCode::UP004],
CheckCodePrefix::UP005 => vec![CheckCode::UP005],
CheckCodePrefix::UP006 => vec![CheckCode::UP006],
CheckCodePrefix::UP007 => vec![CheckCode::UP007],
CheckCodePrefix::UP008 => vec![CheckCode::UP008],
CheckCodePrefix::UP009 => vec![CheckCode::UP009],
CheckCodePrefix::UP01 => vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
CheckCodePrefix::UP012 => vec![CheckCode::UP012],
CheckCodePrefix::UP013 => vec![CheckCode::UP013],
CheckCodePrefix::UP014 => vec![CheckCode::UP014],
CheckCodePrefix::UP015 => vec![CheckCode::UP015],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -1592,10 +1907,6 @@ impl CheckCodePrefix {
CheckCodePrefix::I2 => SuffixLength::One,
CheckCodePrefix::I25 => SuffixLength::Two,
CheckCodePrefix::I252 => SuffixLength::Three,
CheckCodePrefix::M => SuffixLength::Zero,
CheckCodePrefix::M0 => SuffixLength::One,
CheckCodePrefix::M00 => SuffixLength::Two,
CheckCodePrefix::M001 => SuffixLength::Three,
CheckCodePrefix::N => SuffixLength::Zero,
CheckCodePrefix::N8 => SuffixLength::One,
CheckCodePrefix::N80 => SuffixLength::Two,
@@ -1615,11 +1926,47 @@ impl CheckCodePrefix {
CheckCodePrefix::N816 => SuffixLength::Three,
CheckCodePrefix::N817 => SuffixLength::Three,
CheckCodePrefix::N818 => SuffixLength::Three,
CheckCodePrefix::PGH => SuffixLength::Zero,
CheckCodePrefix::PGH0 => SuffixLength::One,
CheckCodePrefix::PGH00 => SuffixLength::Two,
CheckCodePrefix::PGH001 => SuffixLength::Three,
CheckCodePrefix::PLC => SuffixLength::Zero,
CheckCodePrefix::PLC0 => SuffixLength::One,
CheckCodePrefix::PLC04 => SuffixLength::Two,
CheckCodePrefix::PLC041 => SuffixLength::Three,
CheckCodePrefix::PLC0414 => SuffixLength::Four,
CheckCodePrefix::PLC2 => SuffixLength::One,
CheckCodePrefix::PLC22 => SuffixLength::Two,
CheckCodePrefix::PLC220 => SuffixLength::Three,
CheckCodePrefix::PLC2201 => SuffixLength::Four,
CheckCodePrefix::PLC3 => SuffixLength::One,
CheckCodePrefix::PLC30 => SuffixLength::Two,
CheckCodePrefix::PLC300 => SuffixLength::Three,
CheckCodePrefix::PLC3002 => SuffixLength::Four,
CheckCodePrefix::PLE => SuffixLength::Zero,
CheckCodePrefix::PLE1 => SuffixLength::One,
CheckCodePrefix::PLE11 => SuffixLength::Two,
CheckCodePrefix::PLE114 => SuffixLength::Three,
CheckCodePrefix::PLE1142 => SuffixLength::Four,
CheckCodePrefix::PLR => SuffixLength::Zero,
CheckCodePrefix::PLR0 => SuffixLength::One,
CheckCodePrefix::PLR02 => SuffixLength::Two,
CheckCodePrefix::PLR020 => SuffixLength::Three,
CheckCodePrefix::PLR0206 => SuffixLength::Four,
CheckCodePrefix::PLR04 => SuffixLength::Two,
CheckCodePrefix::PLR040 => SuffixLength::Three,
CheckCodePrefix::PLR0402 => SuffixLength::Four,
CheckCodePrefix::PLR1 => SuffixLength::One,
CheckCodePrefix::PLR17 => SuffixLength::Two,
CheckCodePrefix::PLR170 => SuffixLength::Three,
CheckCodePrefix::PLR1701 => SuffixLength::Four,
CheckCodePrefix::PLR172 => SuffixLength::Three,
CheckCodePrefix::PLR1722 => SuffixLength::Four,
CheckCodePrefix::PLW => SuffixLength::Zero,
CheckCodePrefix::PLW0 => SuffixLength::One,
CheckCodePrefix::PLW01 => SuffixLength::Two,
CheckCodePrefix::PLW012 => SuffixLength::Three,
CheckCodePrefix::PLW0120 => SuffixLength::Four,
CheckCodePrefix::Q => SuffixLength::Zero,
CheckCodePrefix::Q0 => SuffixLength::One,
CheckCodePrefix::Q00 => SuffixLength::Two,
@@ -1627,6 +1974,17 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => SuffixLength::Three,
CheckCodePrefix::Q002 => SuffixLength::Three,
CheckCodePrefix::Q003 => SuffixLength::Three,
CheckCodePrefix::RET => SuffixLength::Zero,
CheckCodePrefix::RET5 => SuffixLength::One,
CheckCodePrefix::RET50 => SuffixLength::Two,
CheckCodePrefix::RET501 => SuffixLength::Three,
CheckCodePrefix::RET502 => SuffixLength::Three,
CheckCodePrefix::RET503 => SuffixLength::Three,
CheckCodePrefix::RET504 => SuffixLength::Three,
CheckCodePrefix::RET505 => SuffixLength::Three,
CheckCodePrefix::RET506 => SuffixLength::Three,
CheckCodePrefix::RET507 => SuffixLength::Three,
CheckCodePrefix::RET508 => SuffixLength::Three,
CheckCodePrefix::RUF => SuffixLength::Zero,
CheckCodePrefix::RUF0 => SuffixLength::One,
CheckCodePrefix::RUF00 => SuffixLength::Two,
@@ -1635,7 +1993,7 @@ impl CheckCodePrefix {
CheckCodePrefix::RUF003 => SuffixLength::Three,
CheckCodePrefix::RUF1 => SuffixLength::One,
CheckCodePrefix::RUF10 => SuffixLength::Two,
CheckCodePrefix::RUF101 => SuffixLength::Three,
CheckCodePrefix::RUF100 => SuffixLength::Three,
CheckCodePrefix::S => SuffixLength::Zero,
CheckCodePrefix::S1 => SuffixLength::One,
CheckCodePrefix::S10 => SuffixLength::Two,
@@ -1671,6 +2029,24 @@ impl CheckCodePrefix {
CheckCodePrefix::U013 => SuffixLength::Three,
CheckCodePrefix::U014 => SuffixLength::Three,
CheckCodePrefix::U015 => SuffixLength::Three,
CheckCodePrefix::UP => SuffixLength::Zero,
CheckCodePrefix::UP0 => SuffixLength::One,
CheckCodePrefix::UP00 => SuffixLength::Two,
CheckCodePrefix::UP001 => SuffixLength::Three,
CheckCodePrefix::UP003 => SuffixLength::Three,
CheckCodePrefix::UP004 => SuffixLength::Three,
CheckCodePrefix::UP005 => SuffixLength::Three,
CheckCodePrefix::UP006 => SuffixLength::Three,
CheckCodePrefix::UP007 => SuffixLength::Three,
CheckCodePrefix::UP008 => SuffixLength::Three,
CheckCodePrefix::UP009 => SuffixLength::Three,
CheckCodePrefix::UP01 => SuffixLength::Two,
CheckCodePrefix::UP010 => SuffixLength::Three,
CheckCodePrefix::UP011 => SuffixLength::Three,
CheckCodePrefix::UP012 => SuffixLength::Three,
CheckCodePrefix::UP013 => SuffixLength::Three,
CheckCodePrefix::UP014 => SuffixLength::Three,
CheckCodePrefix::UP015 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,
@@ -1711,14 +2087,19 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::M,
CheckCodePrefix::N,
CheckCodePrefix::PGH,
CheckCodePrefix::PLC,
CheckCodePrefix::PLE,
CheckCodePrefix::PLR,
CheckCodePrefix::PLW,
CheckCodePrefix::Q,
CheckCodePrefix::RET,
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::UP,
CheckCodePrefix::W,
CheckCodePrefix::YTT,
];

View File

@@ -211,7 +211,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(!visibility::is_staticmethod(stmt)),
usize::from(!visibility::is_staticmethod(checker, stmt)),
)
{
// ANN401 for dynamically typed arguments
@@ -283,10 +283,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
}
// ANN101, ANN102
if !visibility::is_staticmethod(stmt) {
if !visibility::is_staticmethod(checker, stmt) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
if visibility::is_classmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
@@ -319,14 +319,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
return;
}
if visibility::is_classmethod(stmt) {
if visibility::is_classmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_staticmethod(stmt) {
} else if visibility::is_staticmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::helpers::match_module_member;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -12,8 +12,14 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
if let ExprKind::Call { func, args, .. } = &item_context.node {
if args.len() == 1
&& item.optional_vars.is_none()
&& match_name_or_attr(func, "assertRaises")
&& match_name_or_attr(args.first().unwrap(), "Exception")
&& matches!(&func.node, ExprKind::Attribute { attr, .. } if attr == "assertRaises")
&& match_module_member(
args.first().unwrap(),
"",
"Exception",
&checker.from_imports,
&checker.import_aliases,
)
{
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,

View File

@@ -41,6 +41,12 @@ pub fn debugger_call(
/// Checks for the presence of a debugger import.
pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Check> {
// Special-case: allow `import builtins`, which is far more general than (e.g.)
// `import celery.contrib.rdb`).
if module.is_none() && name == "builtins" {
return None;
}
if let Some(module) = module {
if let Some((module_name, member)) = DEBUGGERS
.iter()

View File

@@ -26,70 +26,70 @@ expression: checks
Debugger:
Import: builtins.breakpoint
location:
row: 5
row: 6
column: 0
end_location:
row: 5
row: 6
column: 31
fix: ~
- kind:
Debugger:
Import: pdb.set_trace
location:
row: 6
row: 7
column: 0
end_location:
row: 6
row: 7
column: 31
fix: ~
- kind:
Debugger:
Import: celery.contrib.rdb.set_trace
location:
row: 7
row: 8
column: 0
end_location:
row: 7
row: 8
column: 40
fix: ~
- kind:
Debugger:
Import: celery.contrib.rdb
location:
row: 9
row: 10
column: 0
end_location:
row: 9
row: 10
column: 25
fix: ~
- kind:
Debugger:
Call: breakpoint
location:
row: 12
row: 13
column: 0
end_location:
row: 12
row: 13
column: 12
fix: ~
- kind:
Debugger:
Call: set_trace
location:
row: 13
row: 14
column: 0
end_location:
row: 13
row: 14
column: 4
fix: ~
- kind:
Debugger:
Call: set_trace
location:
row: 14
row: 15
column: 0
end_location:
row: 14
row: 15
column: 11
fix: ~

View File

@@ -0,0 +1,18 @@
use rustpython_ast::{Constant, Expr, ExprKind, Stmt};
/// Return `true` if a function's return statement include at least one
/// non-`None` value.
pub fn result_exists(returns: &[(&Stmt, Option<&Expr>)]) -> bool {
returns.iter().any(|(_, expr)| {
expr.map(|expr| {
!matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
)
})
.unwrap_or(false)
})
}

37
src/flake8_return/mod.rs Normal file
View File

@@ -0,0 +1,37 @@
mod helpers;
pub mod plugins;
mod visitor;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::Settings;
#[test_case(CheckCode::RET501, Path::new("RET501.py"); "RET501")]
#[test_case(CheckCode::RET502, Path::new("RET502.py"); "RET502")]
#[test_case(CheckCode::RET503, Path::new("RET503.py"); "RET503")]
#[test_case(CheckCode::RET504, Path::new("RET504.py"); "RET504")]
#[test_case(CheckCode::RET505, Path::new("RET505.py"); "RET505")]
#[test_case(CheckCode::RET506, Path::new("RET506.py"); "RET506")]
#[test_case(CheckCode::RET507, Path::new("RET507.py"); "RET507")]
#[test_case(CheckCode::RET508, Path::new("RET508.py"); "RET508")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_return")
.join(path)
.as_path(),
&Settings::for_rule(check_code),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -0,0 +1,343 @@
use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::whitespace::indentation;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Branch, CheckCode, CheckKind};
use crate::flake8_return::helpers::result_exists;
use crate::flake8_return::visitor::{ReturnVisitor, Stack};
use crate::Check;
/// RET501
fn unnecessary_return_none(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if let Some(expr) = expr {
if matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
let mut check =
Check::new(CheckKind::UnnecessaryReturnNone, Range::from_located(stmt));
if checker.patch(&CheckCode::RET501) {
check.amend(Fix::replacement(
"return".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
}
/// RET502
fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if expr.is_none() {
let mut check = Check::new(CheckKind::ImplicitReturnValue, Range::from_located(stmt));
if checker.patch(&CheckCode::RET502) {
check.amend(Fix::replacement(
"return None".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
/// RET503
fn implicit_return(checker: &mut Checker, last_stmt: &Stmt) {
match &last_stmt.node {
StmtKind::If { body, orelse, .. } => {
if body.is_empty() || orelse.is_empty() {
checker.add_check(Check::new(
CheckKind::ImplicitReturn,
Range::from_located(last_stmt),
));
return;
}
if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
if let Some(last_stmt) = orelse.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::For { body, orelse, .. } | StmtKind::AsyncFor { body, orelse, .. } => {
if let Some(last_stmt) = orelse.last() {
implicit_return(checker, last_stmt);
} else if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => {
if let Some(last_stmt) = body.last() {
implicit_return(checker, last_stmt);
}
}
StmtKind::Assert { test, .. }
if matches!(
test.node,
ExprKind::Constant {
value: Constant::Bool(false),
..
}
) => {}
StmtKind::Return { .. }
| StmtKind::While { .. }
| StmtKind::Raise { .. }
| StmtKind::Try { .. } => {}
_ => {
let mut check = Check::new(CheckKind::ImplicitReturn, Range::from_located(last_stmt));
if checker.patch(&CheckCode::RET503) {
let mut content = String::new();
content.push_str(&indentation(checker, last_stmt));
content.push_str("return None");
content.push('\n');
check.amend(Fix::insertion(
content,
Location::new(last_stmt.end_location.unwrap().row() + 1, 0),
));
}
checker.add_check(check);
}
}
}
fn has_refs_before_next_assign(id: &str, return_location: Location, stack: &Stack) -> bool {
let mut before_assign: &Location = &Location::default();
let mut after_assign: Option<&Location> = None;
if let Some(assigns) = stack.assigns.get(&id) {
for location in assigns.iter().sorted() {
if location.row() > return_location.row() {
after_assign = Some(location);
break;
}
if location.row() <= return_location.row() {
before_assign = location;
}
}
}
if let Some(refs) = stack.refs.get(&id) {
for location in refs {
if location.row() == return_location.row() {
continue;
}
if let Some(after_assign) = after_assign {
if before_assign.row() < location.row() && location.row() <= after_assign.row() {
return true;
}
} else if before_assign.row() < location.row() {
return true;
}
}
}
false
}
fn has_refs_or_assigns_within_try_or_loop(id: &str, stack: &Stack) -> bool {
if let Some(refs) = stack.refs.get(&id) {
for location in refs {
for (try_location, try_end_location) in &stack.tries {
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
return true;
}
}
for (loop_location, loop_end_location) in &stack.loops {
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
{
return true;
}
}
}
}
if let Some(refs) = stack.assigns.get(&id) {
for location in refs {
for (try_location, try_end_location) in &stack.tries {
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
return true;
}
}
for (loop_location, loop_end_location) in &stack.loops {
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
{
return true;
}
}
}
}
false
}
/// RET504
fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
if let ExprKind::Name { id, .. } = &expr.node {
if !stack.assigns.contains_key(id.as_str()) {
return;
}
if !stack.refs.contains_key(id.as_str()) {
checker.add_check(Check::new(
CheckKind::UnnecessaryAssign,
Range::from_located(expr),
));
return;
}
if has_refs_before_next_assign(id, expr.location, stack)
|| has_refs_or_assigns_within_try_or_loop(id, stack)
{
return;
}
checker.add_check(Check::new(
CheckKind::UnnecessaryAssign,
Range::from_located(expr),
));
}
}
/// RET505, RET506, RET507, RET508
fn superfluous_else_node(checker: &mut Checker, stmt: &Stmt, branch: Branch) -> bool {
if let StmtKind::If { body, .. } = &stmt.node {
for child in body {
if matches!(child.node, StmtKind::Return { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET505) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseReturn(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Break) {
if checker.settings.enabled.contains(&CheckCode::RET508) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseBreak(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Raise { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET506) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseRaise(branch),
Range::from_located(stmt),
));
}
return true;
}
if matches!(child.node, StmtKind::Continue) {
if checker.settings.enabled.contains(&CheckCode::RET507) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseContinue(branch),
Range::from_located(stmt),
));
}
return true;
}
}
}
false
}
/// RET505, RET506, RET507, RET508
fn superfluous_elif(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.elifs {
if superfluous_else_node(checker, stmt, Branch::Elif) {
return true;
}
}
false
}
/// RET505, RET506, RET507, RET508
fn superfluous_else(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.ifs {
if let StmtKind::If { orelse, .. } = &stmt.node {
if !orelse.is_empty() {
if superfluous_else_node(checker, stmt, Branch::Else) {
return true;
}
}
}
}
false
}
/// Run all checks from the `flake8-return` plugin.
pub fn function(checker: &mut Checker, body: &[Stmt]) {
// Skip empty functions.
if body.is_empty() {
return;
}
// Find the last statement in the function.
let last_stmt = body.last().unwrap();
// Skip functions that consist of a single return statement.
if body.len() == 1 && matches!(last_stmt.node, StmtKind::Return { .. }) {
return;
}
// Traverse the function body, to collect the stack.
let stack = {
let mut visitor = ReturnVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.stack
};
if checker.settings.enabled.contains(&CheckCode::RET505)
|| checker.settings.enabled.contains(&CheckCode::RET506)
|| checker.settings.enabled.contains(&CheckCode::RET507)
|| checker.settings.enabled.contains(&CheckCode::RET508)
{
if superfluous_elif(checker, &stack) {
return;
}
if superfluous_else(checker, &stack) {
return;
}
}
// Skip any functions without return statements.
if stack.returns.is_empty() {
return;
}
if !result_exists(&stack.returns) {
if checker.settings.enabled.contains(&CheckCode::RET501) {
unnecessary_return_none(checker, &stack);
}
return;
}
if checker.settings.enabled.contains(&CheckCode::RET502) {
implicit_return_value(checker, &stack);
}
if checker.settings.enabled.contains(&CheckCode::RET503) {
implicit_return(checker, last_stmt);
}
if checker.settings.enabled.contains(&CheckCode::RET504) {
for (_, expr) in &stack.returns {
if let Some(expr) = expr {
unnecessary_assign(checker, &stack, expr);
}
}
}
}

View File

@@ -0,0 +1,20 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: UnnecessaryReturnNone
location:
row: 4
column: 4
end_location:
row: 4
column: 15
fix:
content: return
location:
row: 4
column: 4
end_location:
row: 4
column: 15

View File

@@ -0,0 +1,20 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: ImplicitReturnValue
location:
row: 3
column: 8
end_location:
row: 3
column: 14
fix:
content: return None
location:
row: 3
column: 8
end_location:
row: 3
column: 14

View File

@@ -0,0 +1,66 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: ImplicitReturn
location:
row: 7
column: 4
end_location:
row: 12
column: 0
fix: ~
- kind: ImplicitReturn
location:
row: 14
column: 8
end_location:
row: 14
column: 15
fix:
content: " return None\n"
location:
row: 15
column: 0
end_location:
row: 15
column: 0
- kind: ImplicitReturn
location:
row: 23
column: 4
end_location:
row: 23
column: 11
fix:
content: " return None\n"
location:
row: 24
column: 0
end_location:
row: 24
column: 0
- kind: ImplicitReturn
location:
row: 29
column: 8
end_location:
row: 34
column: 0
fix: ~
- kind: ImplicitReturn
location:
row: 39
column: 8
end_location:
row: 39
column: 15
fix:
content: " return None\n"
location:
row: 40
column: 0
end_location:
row: 40
column: 0

View File

@@ -0,0 +1,61 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind: UnnecessaryAssign
location:
row: 6
column: 11
end_location:
row: 6
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 12
column: 11
end_location:
row: 12
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 18
column: 11
end_location:
row: 18
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 25
column: 11
end_location:
row: 25
column: 12
fix: ~
- kind: UnnecessaryAssign
location:
row: 31
column: 15
end_location:
row: 31
column: 16
fix: ~
- kind: UnnecessaryAssign
location:
row: 43
column: 11
end_location:
row: 43
column: 17
fix: ~
- kind: UnnecessaryAssign
location:
row: 51
column: 11
end_location:
row: 51
column: 20
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseReturn: Elif
location:
row: 5
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Elif
location:
row: 17
column: 4
end_location:
row: 27
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Elif
location:
row: 38
column: 4
end_location:
row: 49
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 50
column: 4
end_location:
row: 58
column: 0
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 61
column: 8
end_location:
row: 67
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 73
column: 4
end_location:
row: 81
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 86
column: 8
end_location:
row: 91
column: 4
fix: ~
- kind:
SuperfluousElseReturn: Else
location:
row: 97
column: 4
end_location:
row: 109
column: 0
fix: ~

View File

@@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseRaise: Elif
location:
row: 5
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind:
SuperfluousElseRaise: Elif
location:
row: 17
column: 4
end_location:
row: 27
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 31
column: 4
end_location:
row: 39
column: 0
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 42
column: 8
end_location:
row: 48
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 54
column: 4
end_location:
row: 62
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 67
column: 8
end_location:
row: 72
column: 4
fix: ~
- kind:
SuperfluousElseRaise: Else
location:
row: 78
column: 4
end_location:
row: 90
column: 0
fix: ~

View File

@@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseContinue: Elif
location:
row: 6
column: 8
end_location:
row: 14
column: 0
fix: ~
- kind:
SuperfluousElseContinue: Elif
location:
row: 16
column: 8
end_location:
row: 26
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 34
column: 8
end_location:
row: 40
column: 0
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 44
column: 12
end_location:
row: 50
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 57
column: 8
end_location:
row: 65
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 71
column: 12
end_location:
row: 76
column: 8
fix: ~
- kind:
SuperfluousElseContinue: Else
location:
row: 83
column: 8
end_location:
row: 92
column: 0
fix: ~

View File

@@ -0,0 +1,68 @@
---
source: src/flake8_return/mod.rs
expression: checks
---
- kind:
SuperfluousElseBreak: Elif
location:
row: 6
column: 8
end_location:
row: 14
column: 0
fix: ~
- kind:
SuperfluousElseBreak: Elif
location:
row: 16
column: 8
end_location:
row: 26
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 31
column: 8
end_location:
row: 37
column: 0
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 41
column: 12
end_location:
row: 47
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 54
column: 8
end_location:
row: 62
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 68
column: 12
end_location:
row: 73
column: 8
fix: ~
- kind:
SuperfluousElseBreak: Else
location:
row: 80
column: 8
end_location:
row: 92
column: 0
fix: ~

View File

@@ -0,0 +1,119 @@
use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
#[derive(Default)]
pub struct Stack<'a> {
pub returns: Vec<(&'a Stmt, Option<&'a Expr>)>,
pub ifs: Vec<&'a Stmt>,
pub elifs: Vec<&'a Stmt>,
pub refs: FxHashMap<&'a str, Vec<Location>>,
pub assigns: FxHashMap<&'a str, Vec<Location>>,
pub loops: Vec<(Location, Location)>,
pub tries: Vec<(Location, Location)>,
}
#[derive(Default)]
pub struct ReturnVisitor<'a> {
pub stack: Stack<'a>,
}
impl<'a> ReturnVisitor<'a> {
fn visit_assign_target(&mut self, expr: &'a Expr) {
match &expr.node {
ExprKind::Tuple { elts, .. } => {
for elt in elts {
self.visit_assign_target(elt);
}
return;
}
ExprKind::Name { id, .. } => {
self.stack
.assigns
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
return;
}
_ => {}
}
visitor::walk_expr(self, expr);
}
}
impl<'a> Visitor<'a> for ReturnVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// Don't recurse.
}
StmtKind::Return { value } => {
self.stack
.returns
.push((stmt, value.as_ref().map(|expr| &**expr)));
visitor::walk_stmt(self, stmt);
}
StmtKind::If { orelse, .. } => {
if orelse.len() == 1 && matches!(orelse.first().unwrap().node, StmtKind::If { .. })
{
self.stack.elifs.push(stmt);
} else {
self.stack.ifs.push(stmt);
}
visitor::walk_stmt(self, stmt);
}
StmtKind::Assign { targets, value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
self.stack
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(value.location);
}
visitor::walk_expr(self, value);
if let Some(target) = targets.first() {
// Skip unpacking assignments, like `x, y = my_object`.
if matches!(target.node, ExprKind::Tuple { .. })
&& !matches!(value.node, ExprKind::Tuple { .. })
{
return;
}
self.visit_assign_target(target);
}
}
StmtKind::For { .. } | StmtKind::AsyncFor { .. } | StmtKind::While { .. } => {
self.stack
.loops
.push((stmt.location, stmt.end_location.unwrap()));
visitor::walk_stmt(self, stmt);
}
StmtKind::Try { .. } => {
self.stack
.tries
.push((stmt.location, stmt.end_location.unwrap()));
visitor::walk_stmt(self, stmt);
}
_ => {
visitor::walk_stmt(self, stmt);
}
}
}
fn visit_expr(&mut self, expr: &'a Expr) {
match &expr.node {
ExprKind::Name { id, .. } => {
self.stack
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
}
_ => visitor::walk_expr(self, expr),
}
}
}

View File

@@ -39,6 +39,7 @@ pub fn format_import_from(
comments: &CommentSet,
aliases: &[(AliasData, CommentSet)],
line_length: usize,
force_wrap_aliases: bool,
is_first: bool,
) -> String {
// We can only inline if: (1) none of the aliases have atop comments, and (3)
@@ -51,6 +52,9 @@ pub fn format_import_from(
.rev()
.skip(1)
.all(|(_, CommentSet { inline, .. })| inline.is_empty())
&& (!force_wrap_aliases
|| aliases.len() == 1
|| aliases.iter().all(|(alias, _)| alias.asname.is_none()))
{
let (single_line, import_length) =
format_single_line(import_from, comments, aliases, is_first);

View File

@@ -145,7 +145,7 @@ fn annotate_imports<'a>(
annotated
}
fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool) -> ImportBlock {
let mut block = ImportBlock::default();
for import in imports {
match import {
@@ -191,7 +191,7 @@ fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
} => {
// Associate the comments with the first alias (best effort).
if let Some(alias) = names.first() {
if alias.asname.is_none() {
if alias.asname.is_none() || combine_as_imports {
let entry = &mut block
.import_from
.entry(ImportFromData { module, level })
@@ -225,7 +225,7 @@ fn normalize_imports(imports: Vec<AnnotatedImport>) -> ImportBlock {
// Create an entry for every alias.
for alias in names {
if alias.asname.is_none() {
if alias.asname.is_none() || combine_as_imports {
let entry = block
.import_from
.entry(ImportFromData { module, level })
@@ -397,6 +397,7 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
ordered
}
#[allow(clippy::too_many_arguments)]
pub fn format_imports(
block: &[&Stmt],
comments: Vec<Comment>,
@@ -405,11 +406,13 @@ pub fn format_imports(
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
combine_as_imports: bool,
force_wrap_aliases: bool,
) -> String {
let block = annotate_imports(block, comments);
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
let block = normalize_imports(block);
let block = normalize_imports(block, combine_as_imports);
// Categorize by type (e.g., first-party vs. third-party).
let block_by_type = categorize_imports(
@@ -449,6 +452,7 @@ pub fn format_imports(
comments,
aliases,
line_length,
force_wrap_aliases,
is_first_statement,
));
is_first_statement = false;
@@ -466,14 +470,16 @@ mod tests {
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::Settings;
use crate::{isort, Settings};
#[test_case(Path::new("add_newline_before_comments.py"))]
#[test_case(Path::new("combine_as_imports.py"))]
#[test_case(Path::new("combine_import_froms.py"))]
#[test_case(Path::new("comments.py"))]
#[test_case(Path::new("deduplicate_imports.py"))]
#[test_case(Path::new("fit_line_length.py"))]
#[test_case(Path::new("fit_line_length_comment.py"))]
#[test_case(Path::new("force_wrap_aliases.py"))]
#[test_case(Path::new("import_from_after_import.py"))]
#[test_case(Path::new("leading_prefix.py"))]
#[test_case(Path::new("no_reorder_within_section.py"))]
@@ -490,7 +496,7 @@ mod tests {
#[test_case(Path::new("sort_similar_imports.py"))]
#[test_case(Path::new("trailing_suffix.py"))]
#[test_case(Path::new("type_comments.py"))]
fn isort(path: &Path) -> Result<()> {
fn default(path: &Path) -> Result<()> {
let snapshot = format!("{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/isort")
@@ -506,4 +512,49 @@ mod tests {
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("combine_as_imports.py"))]
fn combine_as_imports(path: &Path) -> Result<()> {
let snapshot = format!("combine_as_imports_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/isort")
.join(path)
.as_path(),
&Settings {
isort: isort::settings::Settings {
combine_as_imports: true,
..isort::settings::Settings::default()
},
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("force_wrap_aliases.py"))]
fn force_wrap_aliases(path: &Path) -> Result<()> {
let snapshot = format!("force_wrap_aliases_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/isort")
.join(path)
.as_path(),
&Settings {
isort: isort::settings::Settings {
force_wrap_aliases: true,
combine_as_imports: true,
..isort::settings::Settings::default()
},
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -60,6 +60,8 @@ pub fn check_imports(
&settings.isort.known_first_party,
&settings.isort.known_third_party,
&settings.isort.extra_standard_library,
settings.isort.combine_as_imports,
settings.isort.force_wrap_aliases,
);
if has_leading_content || has_trailing_content {

View File

@@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub combine_as_imports: Option<bool>,
pub force_wrap_aliases: Option<bool>,
pub known_first_party: Option<Vec<String>>,
pub known_third_party: Option<Vec<String>>,
pub extra_standard_library: Option<Vec<String>>,
@@ -14,6 +16,8 @@ pub struct Options {
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub combine_as_imports: bool,
pub force_wrap_aliases: bool,
pub known_first_party: BTreeSet<String>,
pub known_third_party: BTreeSet<String>,
pub extra_standard_library: BTreeSet<String>,
@@ -22,6 +26,8 @@ pub struct Settings {
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
combine_as_imports: options.combine_as_imports.unwrap_or_default(),
force_wrap_aliases: options.force_wrap_aliases.unwrap_or_default(),
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
extra_standard_library: BTreeSet::from_iter(

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 5
column: 0
fix:
content: "from module import CONSTANT, function\nfrom module import Class as C\nfrom module import function as f\n"
location:
row: 1
column: 0
end_location:
row: 5
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 5
column: 0
fix:
content: "from module import CONSTANT, Class as C, function, function as f\n"
location:
row: 1
column: 0
end_location:
row: 5
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 4
column: 0
fix:
content: "from .a import a1 as a1\nfrom .a import a2 as a2\nfrom .b import b1 as b1\nfrom .c import c1\n"
location:
row: 1
column: 0
end_location:
row: 4
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 4
column: 0
fix:
content: "from .a import (\n a1 as a1,\n a2 as a2,\n)\nfrom .b import b1 as b1\nfrom .c import c1\n"
location:
row: 1
column: 0
end_location:
row: 4
column: 0

View File

@@ -51,6 +51,7 @@ mod flake8_comprehensions;
mod flake8_debugger;
mod flake8_print;
pub mod flake8_quotes;
mod flake8_return;
pub mod flake8_tidy_imports;
pub mod fs;
mod isort;
@@ -65,6 +66,7 @@ pub mod printer;
mod pycodestyle;
mod pydocstyle;
mod pyflakes;
mod pygrep_hooks;
mod pylint;
mod python;
mod pyupgrade;

View File

@@ -8,9 +8,9 @@ use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::checks::{Check, CheckCode};
use crate::checks::{Check, CheckCode, REDIRECTS};
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
static NO_QA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
)
@@ -18,6 +18,18 @@ static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
});
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
/// Return `true` if a file is exempt from checking based on the contents of the
/// given line.
pub fn is_file_exempt(line: &str) -> bool {
let line = line.trim_start();
line.starts_with("# flake8: noqa")
|| line.starts_with("# flake8: NOQA")
|| line.starts_with("# flake8: NoQA")
|| line.starts_with("# ruff: noqa")
|| line.starts_with("# ruff: NOQA")
|| line.starts_with("# ruff: NoQA")
}
#[derive(Debug)]
pub enum Directive<'a> {
None,
@@ -25,8 +37,9 @@ pub enum Directive<'a> {
Codes(usize, usize, usize, Vec<&'a str>),
}
/// Extract the noqa `Directive` from a line of Python source code.
pub fn extract_noqa_directive(line: &str) -> Directive {
match NO_QA_REGEX.captures(line) {
match NO_QA_LINE_REGEX.captures(line) {
Some(caps) => match caps.name("spaces") {
Some(spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
@@ -52,6 +65,19 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
}
}
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &CheckCode, haystack: &[&str]) -> bool {
let needle: &str = needle.as_ref();
haystack.iter().any(|candidate| {
if let Some(candidate) = REDIRECTS.get(candidate) {
needle == candidate.as_ref()
} else {
&needle == candidate
}
})
}
pub fn add_noqa(
path: &Path,
checks: &[Check],
@@ -70,9 +96,13 @@ fn add_noqa_inner(
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();
for lineno in 0..lines.len() {
for (lineno, line) in contents.lines().enumerate() {
// If we hit an exemption for the entire file, bail.
if is_file_exempt(line) {
return (0, contents.to_string());
}
let mut codes: BTreeSet<&CheckCode> = BTreeSet::new();
for check in checks {
if check.location.row() == lineno + 1 {
@@ -93,7 +123,7 @@ fn add_noqa_inner(
let mut count: usize = 0;
let mut output = String::new();
for (lineno, line) in lines.iter().enumerate() {
for (lineno, line) in contents.lines().enumerate() {
match matches_by_line.get(&lineno) {
None => {
output.push_str(line);
@@ -155,7 +185,7 @@ fn add_noqa_inner(
output.push('\n');
// Only count if the new line is an actual edit.
if &formatted != line {
if formatted != line {
count += 1;
}
}
@@ -176,20 +206,20 @@ mod tests {
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::noqa::{add_noqa_inner, NO_QA_REGEX};
use crate::noqa::{add_noqa_inner, NO_QA_LINE_REGEX};
#[test]
fn regex() {
assert!(NO_QA_REGEX.is_match("# noqa"));
assert!(NO_QA_REGEX.is_match("# NoQA"));
assert!(NO_QA_LINE_REGEX.is_match("# noqa"));
assert!(NO_QA_LINE_REGEX.is_match("# NoQA"));
assert!(NO_QA_REGEX.is_match("# noqa: F401"));
assert!(NO_QA_REGEX.is_match("# NoQA: F401"));
assert!(NO_QA_REGEX.is_match("# noqa: F401, E501"));
assert!(NO_QA_LINE_REGEX.is_match("# noqa: F401"));
assert!(NO_QA_LINE_REGEX.is_match("# NoQA: F401"));
assert!(NO_QA_LINE_REGEX.is_match("# noqa: F401, E501"));
assert!(NO_QA_REGEX.is_match("# noqa:F401"));
assert!(NO_QA_REGEX.is_match("# NoQA:F401"));
assert!(NO_QA_REGEX.is_match("# noqa:F401, E501"));
assert!(NO_QA_LINE_REGEX.is_match("# noqa:F401"));
assert!(NO_QA_LINE_REGEX.is_match("# NoQA:F401"));
assert!(NO_QA_LINE_REGEX.is_match("# noqa:F401, E501"));
}
#[test]

View File

@@ -8,6 +8,7 @@ use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
use crate::ast::types::Range;
use crate::ast::whitespace;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
@@ -75,7 +76,7 @@ pub fn not_missing(
false
}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
if is_overload(stmt) {
if is_overload(checker, stmt) {
true
} else {
if checker.settings.enabled.contains(&CheckCode::D103) {
@@ -88,7 +89,7 @@ pub fn not_missing(
}
}
DefinitionKind::Method(stmt) => {
if is_overload(stmt) || is_override(stmt) {
if is_overload(checker, stmt) || is_override(checker, stmt) {
true
} else if is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::D105) {
@@ -126,7 +127,7 @@ pub fn one_liner(checker: &mut Checker, definition: &Definition) {
{
let mut line_count = 0;
let mut non_empty_line_count = 0;
for line in string.lines() {
for line in LinesWithTrailingNewline::from(string) {
line_count += 1;
if !line.trim().is_empty() {
non_empty_line_count += 1;
@@ -136,7 +137,7 @@ pub fn one_liner(checker: &mut Checker, definition: &Definition) {
}
}
if non_empty_line_count == 1 && line_count > 1 {
if non_empty_line_count == 1 && (line_count > 1) {
checker.add_check(Check::new(
CheckKind::FitsOnOneLine,
Range::from_located(docstring),
@@ -366,11 +367,24 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
Range::from_located(docstring),
);
if checker.patch(check.kind.code()) {
// Find the "summary" line (defined as the first non-blank line).
let mut summary_line = 0;
for line in string.lines() {
if line.trim().is_empty() {
summary_line += 1;
} else {
break;
}
}
// Insert one blank line after the summary (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.location.row() + 1, 0),
Location::new(docstring.location.row() + 1 + blanks_count, 0),
Location::new(docstring.location.row() + summary_line + 1, 0),
Location::new(
docstring.location.row() + summary_line + 1 + blanks_count,
0,
),
));
}
checker.add_check(check);
@@ -387,12 +401,14 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
..
} = &docstring.node
{
let lines: Vec<&str> = string.lines().collect();
// Split the docstring into lines.
let lines: Vec<&str> = LinesWithTrailingNewline::from(string).collect();
if lines.len() <= 1 {
return;
}
let docstring_indent = whitespace::indentation(checker, docstring);
let mut has_seen_tab = docstring_indent.contains('\t');
let mut is_over_indented = true;
let mut over_indented_lines = vec![];
@@ -418,7 +434,9 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
if checker.settings.enabled.contains(&CheckCode::D207) {
// We report under-indentation on every line. This isn't great, but enables
// autofix.
if !is_blank && line_indent.len() < docstring_indent.len() {
if (i == lines.len() - 1 || !is_blank)
&& line_indent.len() < docstring_indent.len()
{
let mut check = Check::new(
CheckKind::NoUnderIndentation,
Range {
@@ -524,7 +542,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
} = &docstring.node
{
let mut line_count = 0;
for line in string.lines() {
for line in LinesWithTrailingNewline::from(string) {
if !line.trim().is_empty() {
line_count += 1;
}
@@ -570,7 +588,7 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
..
} = &docstring.node
{
let mut lines = string.lines();
let mut lines = LinesWithTrailingNewline::from(string);
if let Some(line) = lines.next() {
let trimmed = line.trim();
if trimmed.is_empty() {
@@ -627,7 +645,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
..
} = &docstring.node
{
if string.lines().nth(1).is_some() {
if LinesWithTrailingNewline::from(string).nth(1).is_some() {
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
@@ -830,7 +848,7 @@ pub fn if_needed(checker: &mut Checker, definition: &Definition) {
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind
{
if is_overload(stmt) {
if is_overload(checker, stmt) {
checker.add_check(Check::new(
CheckKind::SkipDocstring,
Range::from_located(stmt),
@@ -871,7 +889,7 @@ pub fn sections(checker: &mut Checker, definition: &Definition) {
..
} = &docstring.node
{
let lines: Vec<&str> = string.lines().collect();
let lines: Vec<&str> = LinesWithTrailingNewline::from(string).collect();
if lines.len() < 2 {
return;
}
@@ -1314,7 +1332,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
// If this is a non-static method, skip `cls` or `self`.
usize::from(
matches!(definition.kind, DefinitionKind::Method(_))
&& !is_staticmethod(parent),
&& !is_staticmethod(checker, parent),
),
)
{

Some files were not shown because too many files have changed in this diff Show More